新增16个AI技能:包含图像生成、视频剪辑、数据分析、智能查询等功能模块
This commit is contained in:
189
.opencode/skills/uni-agent/README.md
Normal file
189
.opencode/skills/uni-agent/README.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# UniAgent - 统一智能体协议适配层
|
||||
|
||||
"Connect Any Agent, Any Protocol"
|
||||
|
||||
一套 API 调用所有 Agent 协议(ANP/MCP/A2A/AITP/LMOS/Agent Protocol)。
|
||||
|
||||
## 一键部署
|
||||
|
||||
```bash
|
||||
# 1. 运行安装脚本
|
||||
./setup.sh
|
||||
|
||||
# 2. 开始使用
|
||||
python scripts/uni_cli.py list
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 调用 Agent
|
||||
|
||||
```bash
|
||||
# Agent ID 格式: <name>@<protocol>
|
||||
|
||||
# ANP - 去中心化 Agent 网络
|
||||
python scripts/uni_cli.py call amap@anp maps_weather '{"city":"北京"}'
|
||||
python scripts/uni_cli.py call amap@anp maps_text_search '{"keywords":"咖啡厅","city":"上海"}'
|
||||
|
||||
# MCP - LLM 工具调用 (需配置)
|
||||
python scripts/uni_cli.py call filesystem@mcp read_file '{"path":"/tmp/a.txt"}'
|
||||
|
||||
# A2A - Google Agent 协作 (需配置)
|
||||
python scripts/uni_cli.py call assistant@a2a tasks/send '{"message":{"role":"user","content":"hello"}}'
|
||||
|
||||
# AITP - NEAR 交互交易 (需配置)
|
||||
python scripts/uni_cli.py call shop@aitp message '{"content":"我要买咖啡"}'
|
||||
|
||||
# Agent Protocol - REST API (需配置)
|
||||
python scripts/uni_cli.py call autogpt@ap create_task '{"input":"写一个hello world"}'
|
||||
|
||||
# LMOS - 企业级 Agent (需配置)
|
||||
python scripts/uni_cli.py call sales@lmos invoke '{"capability":"sales","input":{}}'
|
||||
```
|
||||
|
||||
### 查看 Agent 方法
|
||||
|
||||
```bash
|
||||
python scripts/uni_cli.py methods amap@anp
|
||||
```
|
||||
|
||||
### 发现 Agent
|
||||
|
||||
```bash
|
||||
python scripts/uni_cli.py discover weather
|
||||
```
|
||||
|
||||
### 列出已注册 Agent
|
||||
|
||||
```bash
|
||||
python scripts/uni_cli.py list
|
||||
```
|
||||
|
||||
## 支持的协议
|
||||
|
||||
| 协议 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| **ANP** | ✅ 已实现 | Agent Network Protocol - 去中心化身份 + Agent 网络 |
|
||||
| **MCP** | ✅ 已实现 | Model Context Protocol - LLM 工具调用 |
|
||||
| **A2A** | ✅ 已实现 | Agent-to-Agent - Google 的 Agent 间协作协议 |
|
||||
| **AITP** | ✅ 已实现 | Agent Interaction & Transaction - 交互 + 交易 |
|
||||
| **Agent Protocol** | ✅ 已实现 | AI Engineer Foundation REST API 标准 |
|
||||
| **LMOS** | ✅ 已实现 | Language Model OS - Eclipse 企业级 Agent 平台 |
|
||||
|
||||
## 内置 ANP Agent
|
||||
|
||||
| ID | 名称 | 功能 |
|
||||
|----|------|------|
|
||||
| amap@anp | 高德地图 | 地点搜索、路线规划、天气查询 |
|
||||
| kuaidi@anp | 快递查询 | 快递单号追踪 |
|
||||
| hotel@anp | 酒店预订 | 搜索酒店、查询房价 |
|
||||
| juhe@anp | 聚合查询 | 多种生活服务 |
|
||||
| navigation@anp | Agent导航 | 发现更多 Agent |
|
||||
|
||||
## 添加自定义 Agent
|
||||
|
||||
编辑 `config/agents.yaml`:
|
||||
|
||||
```yaml
|
||||
agents:
|
||||
# ANP Agent
|
||||
- id: my_agent
|
||||
protocol: anp
|
||||
name: 我的 Agent
|
||||
ad_url: https://example.com/ad.json
|
||||
|
||||
# MCP Server
|
||||
- id: filesystem
|
||||
protocol: mcp
|
||||
name: 文件系统
|
||||
command: npx
|
||||
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
|
||||
# A2A Agent
|
||||
- id: assistant
|
||||
protocol: a2a
|
||||
name: AI Assistant
|
||||
endpoint: https://example.com/.well-known/agent.json
|
||||
auth:
|
||||
type: api_key
|
||||
api_key: "${A2A_API_KEY}"
|
||||
|
||||
# AITP Agent
|
||||
- id: shop
|
||||
protocol: aitp
|
||||
name: NEAR Shop
|
||||
endpoint: https://shop.near.ai/api
|
||||
wallet:
|
||||
type: near
|
||||
account_id: "${NEAR_ACCOUNT_ID}"
|
||||
|
||||
# Agent Protocol
|
||||
- id: autogpt
|
||||
protocol: agent_protocol # 或 ap
|
||||
name: AutoGPT
|
||||
endpoint: http://localhost:8000
|
||||
|
||||
# LMOS Agent
|
||||
- id: sales
|
||||
protocol: lmos
|
||||
name: 销售 Agent
|
||||
endpoint: http://sales.internal:8080
|
||||
```
|
||||
|
||||
## 架构设计
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ UniAgent │
|
||||
│ 统一调用接口 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ call(agent_id, method, params) -> result │
|
||||
└────────────────────────┬────────────────────────────────┘
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ Protocol Router │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
┌─────────┬───────────┼───────────┬─────────┬─────────┐
|
||||
▼ ▼ ▼ ▼ ▼ ▼
|
||||
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
|
||||
│ ANP │ │ MCP │ │ A2A │ │ AITP │ │ AP │ │ LMOS │
|
||||
└──────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
uni-agent/
|
||||
├── README.md
|
||||
├── SKILL.md # AI 助手技能描述
|
||||
├── setup.sh # 一键安装
|
||||
├── requirements.txt
|
||||
├── config/
|
||||
│ ├── agents.yaml # Agent 注册表
|
||||
│ └── .gitignore
|
||||
├── adapters/
|
||||
│ ├── __init__.py # 适配器注册
|
||||
│ ├── base.py # 适配器基类
|
||||
│ ├── anp.py # ANP 适配器
|
||||
│ ├── mcp.py # MCP 适配器
|
||||
│ ├── a2a.py # A2A 适配器
|
||||
│ ├── aitp.py # AITP 适配器
|
||||
│ ├── agent_protocol.py # Agent Protocol 适配器
|
||||
│ └── lmos.py # LMOS 适配器
|
||||
└── scripts/
|
||||
└── uni_cli.py # CLI 工具
|
||||
```
|
||||
|
||||
## 扩展新协议
|
||||
|
||||
1. 创建 `adapters/new_protocol.py`
|
||||
2. 继承 `ProtocolAdapter` 基类
|
||||
3. 实现 `connect`、`call`、`discover`、`close` 方法
|
||||
4. 在 `adapters/__init__.py` 注册
|
||||
|
||||
详见 [SKILL.md](SKILL.md)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
279
.opencode/skills/uni-agent/SKILL.md
Normal file
279
.opencode/skills/uni-agent/SKILL.md
Normal file
@@ -0,0 +1,279 @@
|
||||
---
|
||||
name: uni-agent
|
||||
description: 统一智能体协议适配层。一套 API 调用所有 Agent 协议(ANP/MCP/A2A/AITP 等)。当用户需要调用 Agent、跨协议通信、连接工具时触发此技能。
|
||||
---
|
||||
|
||||
# UniAgent - 统一智能体协议适配层
|
||||
|
||||
"Connect Any Agent, Any Protocol"
|
||||
|
||||
## 设计理念
|
||||
|
||||
### 问题
|
||||
当前 Agent 协议生态割裂:
|
||||
- **MCP**:Anthropic 的工具调用协议
|
||||
- **A2A**:Google 的 Agent 间协作协议
|
||||
- **ANP**:去中心化身份 + Agent 网络协议
|
||||
- **AITP**:NEAR 的交互交易协议
|
||||
- ...
|
||||
|
||||
开发者需要为每个协议学习不同的 SDK、实现不同的调用逻辑。
|
||||
|
||||
### 解决方案
|
||||
UniAgent 提供统一抽象层,一套 API 适配所有协议:
|
||||
|
||||
```python
|
||||
from uni_agent import UniAgent
|
||||
|
||||
agent = UniAgent()
|
||||
|
||||
# 调用 ANP Agent
|
||||
agent.call("amap@anp", "maps_weather", {"city": "北京"})
|
||||
|
||||
# 调用 MCP Server
|
||||
agent.call("filesystem@mcp", "read_file", {"path": "/tmp/a.txt"})
|
||||
|
||||
# 调用 A2A Agent
|
||||
agent.call("assistant@a2a", "chat", {"message": "hello"})
|
||||
|
||||
# 调用 AITP Agent(带支付)
|
||||
agent.call("shop@aitp", "purchase", {"item": "coffee", "amount": 10})
|
||||
```
|
||||
|
||||
## 架构设计
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ UniAgent │
|
||||
│ 统一调用接口 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ call(agent_id, method, params) -> result │
|
||||
│ discover(capability) -> List[Agent] │
|
||||
│ connect(agent_id) -> Connection │
|
||||
└────────────────────────┬────────────────────────────────┘
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ Protocol Router │
|
||||
│ 协议路由 & 适配 │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
┌─────────┬───────────┼───────────┬─────────┐
|
||||
▼ ▼ ▼ ▼ ▼
|
||||
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
|
||||
│ ANP │ │ MCP │ │ A2A │ │ AITP │ │ ... │
|
||||
│Adapter│ │Adapter│ │Adapter│ │Adapter│ │Adapter│
|
||||
└──────┘ └──────┘ └──────┘ └──────┘ └──────┘
|
||||
```
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 1. Agent ID 格式
|
||||
```
|
||||
<agent_name>@<protocol>
|
||||
|
||||
示例:
|
||||
- amap@anp # ANP 协议的高德地图 Agent
|
||||
- filesystem@mcp # MCP 协议的文件系统 Server
|
||||
- gemini@a2a # A2A 协议的 Gemini Agent
|
||||
- shop@aitp # AITP 协议的商店 Agent
|
||||
```
|
||||
|
||||
### 2. 统一调用接口
|
||||
```python
|
||||
result = agent.call(
|
||||
agent_id="amap@anp", # Agent 标识
|
||||
method="maps_weather", # 方法名
|
||||
params={"city": "北京"}, # 参数
|
||||
timeout=30 # 可选超时
|
||||
)
|
||||
```
|
||||
|
||||
### 3. 能力发现
|
||||
```python
|
||||
# 发现所有能提供天气服务的 Agent
|
||||
agents = agent.discover("weather")
|
||||
# 返回: [
|
||||
# {"id": "amap@anp", "protocol": "anp", "methods": [...]},
|
||||
# {"id": "weather@mcp", "protocol": "mcp", "methods": [...]}
|
||||
# ]
|
||||
```
|
||||
|
||||
### 4. 协议适配器接口
|
||||
```python
|
||||
class ProtocolAdapter(ABC):
|
||||
"""协议适配器基类"""
|
||||
|
||||
@abstractmethod
|
||||
def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def call(self, connection: Connection, method: str, params: dict) -> dict:
|
||||
"""调用方法"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def discover(self, capability: str) -> List[AgentInfo]:
|
||||
"""发现 Agent"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def close(self, connection: Connection):
|
||||
"""关闭连接"""
|
||||
pass
|
||||
```
|
||||
|
||||
## 支持的协议
|
||||
|
||||
| 协议 | 状态 | 适配器 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| ANP | ✅ 已实现 | `adapters/anp.py` | 去中心化身份 + Agent 网络 |
|
||||
| MCP | ✅ 已实现 | `adapters/mcp.py` | LLM 工具调用 |
|
||||
| A2A | ✅ 已实现 | `adapters/a2a.py` | Agent 间协作 |
|
||||
| AITP | ✅ 已实现 | `adapters/aitp.py` | 交互 + 交易 |
|
||||
| Agent Protocol | ✅ 已实现 | `adapters/agent_protocol.py` | REST API |
|
||||
| LMOS | ✅ 已实现 | `adapters/lmos.py` | 企业级平台 |
|
||||
|
||||
## 使用方式
|
||||
|
||||
### CLI 调用
|
||||
|
||||
```bash
|
||||
# 调用 ANP Agent
|
||||
python scripts/uni_cli.py call amap@anp maps_weather '{"city":"北京"}'
|
||||
|
||||
# 调用 MCP Server
|
||||
python scripts/uni_cli.py call filesystem@mcp read_file '{"path":"/tmp/a.txt"}'
|
||||
|
||||
# 发现 Agent
|
||||
python scripts/uni_cli.py discover weather
|
||||
|
||||
# 列出已注册 Agent
|
||||
python scripts/uni_cli.py list
|
||||
```
|
||||
|
||||
### Python SDK
|
||||
|
||||
```python
|
||||
from uni_agent import UniAgent
|
||||
|
||||
# 初始化
|
||||
agent = UniAgent(config_path="config/agents.yaml")
|
||||
|
||||
# 调用
|
||||
result = agent.call("amap@anp", "maps_weather", {"city": "北京"})
|
||||
print(result)
|
||||
|
||||
# 批量调用
|
||||
results = agent.batch_call([
|
||||
("amap@anp", "maps_weather", {"city": "北京"}),
|
||||
("amap@anp", "maps_weather", {"city": "上海"}),
|
||||
])
|
||||
```
|
||||
|
||||
## 配置文件
|
||||
|
||||
### config/agents.yaml
|
||||
```yaml
|
||||
agents:
|
||||
# ANP Agents
|
||||
- id: amap
|
||||
protocol: anp
|
||||
ad_url: https://agent-connect.ai/mcp/agents/amap/ad.json
|
||||
|
||||
- id: hotel
|
||||
protocol: anp
|
||||
ad_url: https://agent-connect.ai/agents/hotel-assistant/ad.json
|
||||
|
||||
# MCP Servers
|
||||
- id: filesystem
|
||||
protocol: mcp
|
||||
command: npx
|
||||
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
|
||||
- id: github
|
||||
protocol: mcp
|
||||
command: npx
|
||||
args: ["-y", "@modelcontextprotocol/server-github"]
|
||||
env:
|
||||
GITHUB_TOKEN: "${GITHUB_TOKEN}"
|
||||
|
||||
# A2A Agents
|
||||
- id: assistant
|
||||
protocol: a2a
|
||||
endpoint: https://example.com/.well-known/agent.json
|
||||
```
|
||||
|
||||
### config/identity.yaml
|
||||
```yaml
|
||||
# 身份配置(跨协议通用)
|
||||
identity:
|
||||
# ANP DID 身份
|
||||
anp:
|
||||
did_document: config/did.json
|
||||
private_key: config/private-key.pem
|
||||
|
||||
# A2A 认证
|
||||
a2a:
|
||||
auth_type: oauth2
|
||||
client_id: "${A2A_CLIENT_ID}"
|
||||
client_secret: "${A2A_CLIENT_SECRET}"
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
uni-agent/
|
||||
├── SKILL.md # 本文件
|
||||
├── README.md # 使用文档
|
||||
├── setup.sh # 一键安装
|
||||
├── requirements.txt # Python 依赖
|
||||
├── config/
|
||||
│ ├── agents.yaml # Agent 注册表
|
||||
│ ├── identity.yaml # 身份配置
|
||||
│ └── .gitignore
|
||||
├── adapters/
|
||||
│ ├── __init__.py
|
||||
│ ├── base.py # 适配器基类
|
||||
│ ├── anp.py # ANP 适配器
|
||||
│ ├── mcp.py # MCP 适配器
|
||||
│ ├── a2a.py # A2A 适配器
|
||||
│ └── aitp.py # AITP 适配器
|
||||
├── scripts/
|
||||
│ └── uni_cli.py # CLI 工具
|
||||
└── docs/
|
||||
├── architecture.md # 架构文档
|
||||
└── adapters.md # 适配器开发指南
|
||||
```
|
||||
|
||||
## 扩展新协议
|
||||
|
||||
1. 创建适配器文件 `adapters/new_protocol.py`
|
||||
2. 继承 `ProtocolAdapter` 基类
|
||||
3. 实现 `connect`、`call`、`discover`、`close` 方法
|
||||
4. 在 `adapters/__init__.py` 注册
|
||||
|
||||
```python
|
||||
# adapters/new_protocol.py
|
||||
from .base import ProtocolAdapter
|
||||
|
||||
class NewProtocolAdapter(ProtocolAdapter):
|
||||
protocol_name = "new_protocol"
|
||||
|
||||
def connect(self, agent_config):
|
||||
# 实现连接逻辑
|
||||
pass
|
||||
|
||||
def call(self, connection, method, params):
|
||||
# 实现调用逻辑
|
||||
pass
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
## 依赖
|
||||
|
||||
```bash
|
||||
pip install anp aiohttp mcp pyyaml
|
||||
```
|
||||
60
.opencode/skills/uni-agent/adapters/__init__.py
Normal file
60
.opencode/skills/uni-agent/adapters/__init__.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
UniAgent 协议适配器
|
||||
|
||||
支持的协议:
|
||||
- ANP: Agent Network Protocol (去中心化身份 + Agent 网络)
|
||||
- MCP: Model Context Protocol (LLM 工具调用)
|
||||
- A2A: Agent-to-Agent (Google Agent 间协作)
|
||||
- AITP: Agent Interaction & Transaction Protocol (交互 + 交易)
|
||||
- Agent Protocol: 统一 REST API
|
||||
- LMOS: Language Model OS (企业级 Agent 平台)
|
||||
"""
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
from .anp import ANPAdapter
|
||||
from .mcp import MCPAdapter
|
||||
from .a2a import A2AAdapter
|
||||
from .aitp import AITPAdapter
|
||||
from .agent_protocol import AgentProtocolAdapter
|
||||
from .lmos import LMOSAdapter
|
||||
|
||||
ADAPTERS = {
|
||||
"anp": ANPAdapter,
|
||||
"mcp": MCPAdapter,
|
||||
"a2a": A2AAdapter,
|
||||
"aitp": AITPAdapter,
|
||||
"agent_protocol": AgentProtocolAdapter,
|
||||
"ap": AgentProtocolAdapter,
|
||||
"lmos": LMOSAdapter,
|
||||
}
|
||||
|
||||
def get_adapter(protocol: str) -> ProtocolAdapter:
|
||||
"""获取协议适配器"""
|
||||
adapter_class = ADAPTERS.get(protocol)
|
||||
if not adapter_class:
|
||||
raise ValueError(f"不支持的协议: {protocol},可用协议: {list(ADAPTERS.keys())}")
|
||||
return adapter_class()
|
||||
|
||||
def register_adapter(protocol: str, adapter_class: type):
|
||||
"""注册新的协议适配器"""
|
||||
ADAPTERS[protocol] = adapter_class
|
||||
|
||||
def list_protocols() -> list:
|
||||
"""列出所有支持的协议"""
|
||||
return list(set(ADAPTERS.keys()))
|
||||
|
||||
__all__ = [
|
||||
"ProtocolAdapter",
|
||||
"Connection",
|
||||
"AgentInfo",
|
||||
"ANPAdapter",
|
||||
"MCPAdapter",
|
||||
"A2AAdapter",
|
||||
"AITPAdapter",
|
||||
"AgentProtocolAdapter",
|
||||
"LMOSAdapter",
|
||||
"get_adapter",
|
||||
"register_adapter",
|
||||
"list_protocols",
|
||||
"ADAPTERS",
|
||||
]
|
||||
225
.opencode/skills/uni-agent/adapters/a2a.py
Normal file
225
.opencode/skills/uni-agent/adapters/a2a.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""
|
||||
A2A (Agent-to-Agent) 适配器
|
||||
Google 提出的 Agent 间协作协议
|
||||
|
||||
参考: https://github.com/google/a2a
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
|
||||
class A2AAdapter(ProtocolAdapter):
|
||||
"""A2A 协议适配器"""
|
||||
|
||||
protocol_name = "a2a"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._agent_cards: Dict[str, dict] = {}
|
||||
|
||||
async def _fetch_agent_card(self, endpoint: str) -> dict:
|
||||
"""获取 Agent Card"""
|
||||
if endpoint in self._agent_cards:
|
||||
return self._agent_cards[endpoint]
|
||||
|
||||
agent_json_url = endpoint.rstrip("/")
|
||||
if not agent_json_url.endswith("agent.json"):
|
||||
agent_json_url = f"{agent_json_url}/.well-known/agent.json"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(agent_json_url, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
||||
if resp.status == 200:
|
||||
card = await resp.json()
|
||||
self._agent_cards[endpoint] = card
|
||||
return card
|
||||
raise Exception(f"获取 Agent Card 失败: HTTP {resp.status}")
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接"""
|
||||
endpoint = agent_config.get("endpoint")
|
||||
if not endpoint:
|
||||
raise ValueError("A2A Agent 配置必须包含 endpoint")
|
||||
|
||||
agent_card = await self._fetch_agent_card(endpoint)
|
||||
|
||||
rpc_url = None
|
||||
if "url" in agent_card:
|
||||
rpc_url = agent_card["url"]
|
||||
elif "capabilities" in agent_card:
|
||||
caps = agent_card.get("capabilities", {})
|
||||
if "streaming" in caps:
|
||||
rpc_url = caps.get("streaming", {}).get("streamingUrl")
|
||||
|
||||
if not rpc_url:
|
||||
rpc_url = endpoint.rstrip("/") + "/rpc"
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_config.get("id", ""),
|
||||
protocol=self.protocol_name,
|
||||
endpoint=rpc_url,
|
||||
session=None,
|
||||
metadata={
|
||||
"agent_card": agent_card,
|
||||
"original_endpoint": endpoint,
|
||||
"auth": agent_config.get("auth", {}),
|
||||
}
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 A2A Agent 方法"""
|
||||
rpc_url = connection.endpoint
|
||||
auth_config = connection.metadata.get("auth", {})
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
if auth_config.get("type") == "api_key":
|
||||
headers["Authorization"] = f"Bearer {auth_config.get('api_key', '')}"
|
||||
elif auth_config.get("type") == "oauth2":
|
||||
token = await self._get_oauth_token(auth_config)
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
task_id = str(uuid.uuid4())
|
||||
|
||||
if method == "tasks/send":
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": task_id,
|
||||
"method": "tasks/send",
|
||||
"params": {
|
||||
"id": task_id,
|
||||
"message": params.get("message", {}),
|
||||
}
|
||||
}
|
||||
elif method == "tasks/get":
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": task_id,
|
||||
"method": "tasks/get",
|
||||
"params": {
|
||||
"id": params.get("task_id", task_id),
|
||||
}
|
||||
}
|
||||
else:
|
||||
payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": task_id,
|
||||
"method": method,
|
||||
"params": params,
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
rpc_url,
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {
|
||||
"success": True,
|
||||
"result": result.get("result", result),
|
||||
"task_id": task_id,
|
||||
}
|
||||
else:
|
||||
error_text = await resp.text()
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"HTTP {resp.status}: {error_text}",
|
||||
}
|
||||
|
||||
async def _get_oauth_token(self, auth_config: dict) -> str:
|
||||
"""获取 OAuth2 令牌"""
|
||||
token_url = auth_config.get("token_url")
|
||||
client_id = auth_config.get("client_id")
|
||||
client_secret = auth_config.get("client_secret")
|
||||
|
||||
if not all([token_url, client_id, client_secret]):
|
||||
raise ValueError("OAuth2 配置不完整")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
token_url,
|
||||
data={
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": client_id,
|
||||
"client_secret": client_secret,
|
||||
}
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return result.get("access_token", "")
|
||||
raise Exception(f"获取 OAuth2 令牌失败: HTTP {resp.status}")
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
agents = []
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "a2a":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@a2a",
|
||||
protocol="a2a",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=agent.get("endpoint", ""),
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
return agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接"""
|
||||
pass
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取 Agent 支持的方法(从 Agent Card 的 skills)"""
|
||||
agent_card = connection.metadata.get("agent_card", {})
|
||||
skills = agent_card.get("skills", [])
|
||||
|
||||
methods = []
|
||||
for skill in skills:
|
||||
methods.append({
|
||||
"name": skill.get("id", skill.get("name", "unknown")),
|
||||
"description": skill.get("description", ""),
|
||||
"inputSchema": skill.get("inputSchema", {}),
|
||||
"outputSchema": skill.get("outputSchema", {}),
|
||||
})
|
||||
|
||||
methods.extend([
|
||||
{"name": "tasks/send", "description": "发送任务消息"},
|
||||
{"name": "tasks/get", "description": "获取任务状态"},
|
||||
{"name": "tasks/cancel", "description": "取消任务"},
|
||||
])
|
||||
|
||||
return methods
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "endpoint" in agent_config
|
||||
211
.opencode/skills/uni-agent/adapters/agent_protocol.py
Normal file
211
.opencode/skills/uni-agent/adapters/agent_protocol.py
Normal file
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
Agent Protocol 适配器
|
||||
AI Engineer Foundation 提出的 Agent 统一 REST API
|
||||
|
||||
参考: https://agentprotocol.ai
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
|
||||
class AgentProtocolAdapter(ProtocolAdapter):
|
||||
"""Agent Protocol 适配器"""
|
||||
|
||||
protocol_name = "agent_protocol"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._tasks: Dict[str, dict] = {}
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接"""
|
||||
endpoint = agent_config.get("endpoint")
|
||||
if not endpoint:
|
||||
raise ValueError("Agent Protocol 配置必须包含 endpoint")
|
||||
|
||||
endpoint = endpoint.rstrip("/")
|
||||
if not endpoint.endswith("/ap/v1"):
|
||||
endpoint = f"{endpoint}/ap/v1"
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_config.get("id", ""),
|
||||
protocol=self.protocol_name,
|
||||
endpoint=endpoint,
|
||||
session=None,
|
||||
metadata=agent_config
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 Agent Protocol API"""
|
||||
endpoint = connection.endpoint
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
api_key = connection.metadata.get("api_key")
|
||||
if api_key:
|
||||
headers["Authorization"] = f"Bearer {api_key}"
|
||||
|
||||
if method == "create_task":
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{endpoint}/agent/tasks",
|
||||
json={"input": params.get("input", "")},
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status in [200, 201]:
|
||||
result = await resp.json()
|
||||
task_id = result.get("task_id")
|
||||
self._tasks[task_id] = result
|
||||
return {"success": True, "result": result, "task_id": task_id}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
|
||||
elif method == "execute_step":
|
||||
task_id = params.get("task_id")
|
||||
if not task_id:
|
||||
return {"success": False, "error": "缺少 task_id"}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{endpoint}/agent/tasks/{task_id}/steps",
|
||||
json={"input": params.get("input", "")},
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status in [200, 201]:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
|
||||
elif method == "get_task":
|
||||
task_id = params.get("task_id")
|
||||
if not task_id:
|
||||
return {"success": False, "error": "缺少 task_id"}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{endpoint}/agent/tasks/{task_id}",
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
|
||||
elif method == "list_tasks":
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{endpoint}/agent/tasks",
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
|
||||
elif method == "get_artifacts":
|
||||
task_id = params.get("task_id")
|
||||
if not task_id:
|
||||
return {"success": False, "error": "缺少 task_id"}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{endpoint}/agent/tasks/{task_id}/artifacts",
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
|
||||
else:
|
||||
return {"success": False, "error": f"未知方法: {method}"}
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
agents = []
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "agent_protocol":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@agent_protocol",
|
||||
protocol="agent_protocol",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=agent.get("endpoint", ""),
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
return agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接"""
|
||||
pass
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取支持的方法"""
|
||||
return [
|
||||
{
|
||||
"name": "create_task",
|
||||
"description": "创建新任务",
|
||||
"inputSchema": {"input": "string"},
|
||||
},
|
||||
{
|
||||
"name": "execute_step",
|
||||
"description": "执行任务步骤",
|
||||
"inputSchema": {"task_id": "string", "input": "string"},
|
||||
},
|
||||
{
|
||||
"name": "get_task",
|
||||
"description": "获取任务状态",
|
||||
"inputSchema": {"task_id": "string"},
|
||||
},
|
||||
{
|
||||
"name": "list_tasks",
|
||||
"description": "列出所有任务",
|
||||
"inputSchema": {},
|
||||
},
|
||||
{
|
||||
"name": "get_artifacts",
|
||||
"description": "获取任务产物",
|
||||
"inputSchema": {"task_id": "string"},
|
||||
},
|
||||
]
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "endpoint" in agent_config
|
||||
217
.opencode/skills/uni-agent/adapters/aitp.py
Normal file
217
.opencode/skills/uni-agent/adapters/aitp.py
Normal file
@@ -0,0 +1,217 @@
|
||||
"""
|
||||
AITP (Agent Interaction & Transaction Protocol) 适配器
|
||||
NEAR 基金会提出的 Agent 交互与交易协议
|
||||
|
||||
参考: https://aitp.dev
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
|
||||
class AITPAdapter(ProtocolAdapter):
|
||||
"""AITP 协议适配器"""
|
||||
|
||||
protocol_name = "aitp"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._threads: Dict[str, dict] = {}
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接 - 创建 Thread"""
|
||||
endpoint = agent_config.get("endpoint")
|
||||
if not endpoint:
|
||||
raise ValueError("AITP Agent 配置必须包含 endpoint")
|
||||
|
||||
thread_id = str(uuid.uuid4())
|
||||
|
||||
self._threads[thread_id] = {
|
||||
"id": thread_id,
|
||||
"messages": [],
|
||||
"status": "open",
|
||||
}
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_config.get("id", ""),
|
||||
protocol=self.protocol_name,
|
||||
endpoint=endpoint,
|
||||
session=thread_id,
|
||||
metadata={
|
||||
"thread_id": thread_id,
|
||||
"wallet": agent_config.get("wallet", {}),
|
||||
}
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 AITP Agent"""
|
||||
endpoint = connection.endpoint
|
||||
thread_id = connection.session
|
||||
wallet_config = connection.metadata.get("wallet", {})
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
if method == "message":
|
||||
payload = {
|
||||
"thread_id": thread_id,
|
||||
"message": {
|
||||
"role": "user",
|
||||
"content": params.get("content", ""),
|
||||
"parts": params.get("parts", []),
|
||||
}
|
||||
}
|
||||
elif method == "payment":
|
||||
payload = {
|
||||
"thread_id": thread_id,
|
||||
"capability": "aitp-01",
|
||||
"payment_request": {
|
||||
"amount": params.get("amount"),
|
||||
"currency": params.get("currency", "NEAR"),
|
||||
"recipient": params.get("recipient"),
|
||||
"memo": params.get("memo", ""),
|
||||
}
|
||||
}
|
||||
|
||||
if wallet_config.get("type") == "near":
|
||||
payload["wallet"] = {
|
||||
"type": "near",
|
||||
"account_id": wallet_config.get("account_id"),
|
||||
}
|
||||
elif method == "decision":
|
||||
payload = {
|
||||
"thread_id": thread_id,
|
||||
"capability": "aitp-02",
|
||||
"decision_request": {
|
||||
"question": params.get("question"),
|
||||
"options": params.get("options", []),
|
||||
"allow_custom": params.get("allow_custom", False),
|
||||
}
|
||||
}
|
||||
elif method == "data_request":
|
||||
payload = {
|
||||
"thread_id": thread_id,
|
||||
"capability": "aitp-03",
|
||||
"data_request": {
|
||||
"schema": params.get("schema", {}),
|
||||
"description": params.get("description", ""),
|
||||
}
|
||||
}
|
||||
else:
|
||||
payload = {
|
||||
"thread_id": thread_id,
|
||||
"method": method,
|
||||
"params": params,
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{endpoint}/threads/{thread_id}/messages",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
|
||||
if thread_id in self._threads:
|
||||
self._threads[thread_id]["messages"].append(payload)
|
||||
self._threads[thread_id]["messages"].append(result)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"result": result,
|
||||
"thread_id": thread_id,
|
||||
}
|
||||
else:
|
||||
error_text = await resp.text()
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"HTTP {resp.status}: {error_text}",
|
||||
}
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
agents = []
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "aitp":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@aitp",
|
||||
protocol="aitp",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=agent.get("endpoint", ""),
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
return agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接 - 关闭 Thread"""
|
||||
thread_id = connection.session
|
||||
if thread_id in self._threads:
|
||||
self._threads[thread_id]["status"] = "closed"
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取支持的方法(AITP 能力)"""
|
||||
return [
|
||||
{
|
||||
"name": "message",
|
||||
"description": "发送对话消息",
|
||||
"inputSchema": {"content": "string"},
|
||||
},
|
||||
{
|
||||
"name": "payment",
|
||||
"description": "AITP-01: 发起支付请求",
|
||||
"inputSchema": {
|
||||
"amount": "number",
|
||||
"currency": "string",
|
||||
"recipient": "string",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "decision",
|
||||
"description": "AITP-02: 请求用户决策",
|
||||
"inputSchema": {
|
||||
"question": "string",
|
||||
"options": "array",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "data_request",
|
||||
"description": "AITP-03: 请求结构化数据",
|
||||
"inputSchema": {
|
||||
"schema": "object",
|
||||
"description": "string",
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "endpoint" in agent_config
|
||||
191
.opencode/skills/uni-agent/adapters/anp.py
Normal file
191
.opencode/skills/uni-agent/adapters/anp.py
Normal file
@@ -0,0 +1,191 @@
|
||||
"""
|
||||
ANP (Agent Network Protocol) 适配器
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
try:
|
||||
from anp.anp_crawler import ANPCrawler
|
||||
HAS_ANP = True
|
||||
except ImportError:
|
||||
HAS_ANP = False
|
||||
|
||||
|
||||
class ANPAdapter(ProtocolAdapter):
|
||||
"""ANP 协议适配器"""
|
||||
|
||||
protocol_name = "anp"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._crawler = None
|
||||
self._ad_cache: Dict[str, dict] = {}
|
||||
self._endpoint_cache: Dict[str, str] = {}
|
||||
|
||||
def _get_crawler(self) -> "ANPCrawler":
|
||||
"""获取 ANP Crawler 实例"""
|
||||
if not HAS_ANP:
|
||||
raise ImportError("请安装 anp 库: pip install anp")
|
||||
|
||||
if self._crawler is None:
|
||||
did_path = self.config_dir / "did.json"
|
||||
key_path = self.config_dir / "private-key.pem"
|
||||
|
||||
if did_path.exists() and key_path.exists():
|
||||
self._crawler = ANPCrawler(
|
||||
did_document_path=str(did_path),
|
||||
private_key_path=str(key_path)
|
||||
)
|
||||
else:
|
||||
raise FileNotFoundError(
|
||||
f"DID 配置文件不存在: {did_path} 或 {key_path}\n"
|
||||
"请运行 setup.sh 生成本地身份"
|
||||
)
|
||||
|
||||
return self._crawler
|
||||
|
||||
async def _fetch_ad(self, ad_url: str) -> dict:
|
||||
"""获取 Agent Description 文档"""
|
||||
if ad_url in self._ad_cache:
|
||||
return self._ad_cache[ad_url]
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(ad_url, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
||||
if resp.status == 200:
|
||||
ad = await resp.json()
|
||||
self._ad_cache[ad_url] = ad
|
||||
return ad
|
||||
raise Exception(f"获取 AD 失败: HTTP {resp.status}")
|
||||
|
||||
async def _get_endpoint(self, ad_url: str) -> str:
|
||||
"""从 AD 获取 RPC 端点"""
|
||||
if ad_url in self._endpoint_cache:
|
||||
return self._endpoint_cache[ad_url]
|
||||
|
||||
ad = await self._fetch_ad(ad_url)
|
||||
interfaces = ad.get("interfaces", [])
|
||||
|
||||
if not interfaces:
|
||||
raise ValueError(f"AD 中没有定义接口: {ad_url}")
|
||||
|
||||
interface_url = interfaces[0].get("url")
|
||||
if not interface_url:
|
||||
raise ValueError(f"接口 URL 为空: {ad_url}")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(interface_url, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
||||
if resp.status == 200:
|
||||
interface_doc = await resp.json()
|
||||
servers = interface_doc.get("servers", [])
|
||||
if servers:
|
||||
endpoint = servers[0].get("url")
|
||||
self._endpoint_cache[ad_url] = endpoint
|
||||
return endpoint
|
||||
|
||||
raise ValueError(f"无法获取 RPC 端点: {ad_url}")
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接"""
|
||||
ad_url = agent_config.get("ad_url")
|
||||
if not ad_url:
|
||||
raise ValueError("ANP Agent 配置必须包含 ad_url")
|
||||
|
||||
ad = await self._fetch_ad(ad_url)
|
||||
endpoint = await self._get_endpoint(ad_url)
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_config.get("id", ""),
|
||||
protocol=self.protocol_name,
|
||||
endpoint=endpoint,
|
||||
session=self._get_crawler(),
|
||||
metadata={
|
||||
"ad_url": ad_url,
|
||||
"ad": ad,
|
||||
"name": ad.get("name", ""),
|
||||
}
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 Agent 方法"""
|
||||
crawler = connection.session
|
||||
endpoint = connection.endpoint
|
||||
|
||||
result = await crawler.execute_json_rpc(
|
||||
endpoint=endpoint,
|
||||
method=method,
|
||||
params=params
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent(从本地配置)"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
agents = []
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "anp":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@anp",
|
||||
protocol="anp",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=agent.get("ad_url", ""),
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
return agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接"""
|
||||
pass
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取 Agent 支持的方法"""
|
||||
ad_url = connection.metadata.get("ad_url")
|
||||
if not ad_url:
|
||||
return []
|
||||
|
||||
ad = await self._fetch_ad(ad_url)
|
||||
interfaces = ad.get("interfaces", [])
|
||||
|
||||
if not interfaces:
|
||||
return []
|
||||
|
||||
interface_url = interfaces[0].get("url")
|
||||
if not interface_url:
|
||||
return []
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(interface_url, timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
||||
if resp.status == 200:
|
||||
interface_doc = await resp.json()
|
||||
return interface_doc.get("methods", [])
|
||||
|
||||
return []
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "ad_url" in agent_config
|
||||
120
.opencode/skills/uni-agent/adapters/base.py
Normal file
120
.opencode/skills/uni-agent/adapters/base.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
协议适配器基类
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentInfo:
|
||||
"""Agent 信息"""
|
||||
id: str
|
||||
protocol: str
|
||||
name: str = ""
|
||||
description: str = ""
|
||||
methods: List[str] = field(default_factory=list)
|
||||
endpoint: str = ""
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Connection:
|
||||
"""连接对象"""
|
||||
agent_id: str
|
||||
protocol: str
|
||||
endpoint: str = ""
|
||||
session: Any = None
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def is_active(self) -> bool:
|
||||
return self.session is not None
|
||||
|
||||
|
||||
class ProtocolAdapter(ABC):
|
||||
"""协议适配器基类"""
|
||||
|
||||
protocol_name: str = "base"
|
||||
|
||||
@abstractmethod
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""
|
||||
建立与 Agent 的连接
|
||||
|
||||
Args:
|
||||
agent_config: Agent 配置信息
|
||||
|
||||
Returns:
|
||||
Connection 对象
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""
|
||||
调用 Agent 方法
|
||||
|
||||
Args:
|
||||
connection: 连接对象
|
||||
method: 方法名
|
||||
params: 参数
|
||||
timeout: 超时时间(秒)
|
||||
|
||||
Returns:
|
||||
调用结果
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""
|
||||
发现 Agent
|
||||
|
||||
Args:
|
||||
capability: 能力关键词(可选)
|
||||
|
||||
Returns:
|
||||
Agent 信息列表
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def close(self, connection: Connection):
|
||||
"""
|
||||
关闭连接
|
||||
|
||||
Args:
|
||||
connection: 连接对象
|
||||
"""
|
||||
pass
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""
|
||||
获取 Agent 支持的方法列表
|
||||
|
||||
Args:
|
||||
connection: 连接对象
|
||||
|
||||
Returns:
|
||||
方法列表
|
||||
"""
|
||||
return []
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""
|
||||
验证 Agent 配置
|
||||
|
||||
Args:
|
||||
agent_config: Agent 配置
|
||||
|
||||
Returns:
|
||||
是否有效
|
||||
"""
|
||||
return True
|
||||
215
.opencode/skills/uni-agent/adapters/lmos.py
Normal file
215
.opencode/skills/uni-agent/adapters/lmos.py
Normal file
@@ -0,0 +1,215 @@
|
||||
"""
|
||||
LMOS (Language Model Operating System) 适配器
|
||||
Eclipse 基金会孵化的企业级多 Agent 平台
|
||||
|
||||
参考: https://eclipse.dev/lmos/
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
|
||||
class LMOSAdapter(ProtocolAdapter):
|
||||
"""LMOS 协议适配器"""
|
||||
|
||||
protocol_name = "lmos"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._registry_cache: Dict[str, List[dict]] = {}
|
||||
|
||||
async def _discover_via_mdns(self) -> List[dict]:
|
||||
"""通过 mDNS 发现本地 Agent(简化实现)"""
|
||||
return []
|
||||
|
||||
async def _query_registry(self, registry_url: str, capability: str = "") -> List[dict]:
|
||||
"""查询 Agent 注册中心"""
|
||||
if registry_url in self._registry_cache:
|
||||
return self._registry_cache[registry_url]
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
params = {}
|
||||
if capability:
|
||||
params["capability"] = capability
|
||||
|
||||
async with session.get(
|
||||
f"{registry_url}/agents",
|
||||
params=params,
|
||||
timeout=aiohttp.ClientTimeout(total=15)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
agents = result.get("agents", [])
|
||||
self._registry_cache[registry_url] = agents
|
||||
return agents
|
||||
return []
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接"""
|
||||
endpoint = agent_config.get("endpoint")
|
||||
registry_url = agent_config.get("registry_url")
|
||||
|
||||
if not endpoint and not registry_url:
|
||||
raise ValueError("LMOS Agent 配置必须包含 endpoint 或 registry_url")
|
||||
|
||||
if registry_url and not endpoint:
|
||||
agent_id = agent_config.get("id")
|
||||
agents = await self._query_registry(registry_url)
|
||||
for agent in agents:
|
||||
if agent.get("id") == agent_id:
|
||||
endpoint = agent.get("endpoint")
|
||||
break
|
||||
|
||||
if not endpoint:
|
||||
raise ValueError(f"在注册中心未找到 Agent: {agent_id}")
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_config.get("id", ""),
|
||||
protocol=self.protocol_name,
|
||||
endpoint=endpoint,
|
||||
session=None,
|
||||
metadata={
|
||||
"registry_url": registry_url,
|
||||
"group": agent_config.get("group"),
|
||||
}
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 LMOS Agent"""
|
||||
endpoint = connection.endpoint
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
if method == "invoke":
|
||||
payload = {
|
||||
"capability": params.get("capability"),
|
||||
"input": params.get("input", {}),
|
||||
"context": params.get("context", {}),
|
||||
}
|
||||
elif method == "route":
|
||||
payload = {
|
||||
"query": params.get("query"),
|
||||
"context": params.get("context", {}),
|
||||
}
|
||||
elif method == "describe":
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
f"{endpoint}/capabilities",
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
return {"success": False, "error": f"HTTP {resp.status}"}
|
||||
else:
|
||||
payload = {
|
||||
"method": method,
|
||||
"params": params,
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{endpoint}/invoke",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=timeout)
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
result = await resp.json()
|
||||
return {"success": True, "result": result}
|
||||
else:
|
||||
error_text = await resp.text()
|
||||
return {"success": False, "error": f"HTTP {resp.status}: {error_text}"}
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
all_agents = []
|
||||
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "lmos":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
all_agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@lmos",
|
||||
protocol="lmos",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=agent.get("endpoint", ""),
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "lmos":
|
||||
continue
|
||||
|
||||
registry_url = agent.get("registry_url")
|
||||
if registry_url:
|
||||
try:
|
||||
remote_agents = await self._query_registry(registry_url, capability)
|
||||
for ra in remote_agents:
|
||||
all_agents.append(AgentInfo(
|
||||
id=f"{ra['id']}@lmos",
|
||||
protocol="lmos",
|
||||
name=ra.get("name", ra["id"]),
|
||||
endpoint=ra.get("endpoint", ""),
|
||||
metadata=ra
|
||||
))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return all_agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接"""
|
||||
pass
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取支持的方法"""
|
||||
result = await self.call(connection, "describe", {})
|
||||
|
||||
if result.get("success"):
|
||||
capabilities = result.get("result", {}).get("capabilities", [])
|
||||
return [
|
||||
{
|
||||
"name": cap.get("id", cap.get("name")),
|
||||
"description": cap.get("description", ""),
|
||||
"inputSchema": cap.get("inputSchema", {}),
|
||||
}
|
||||
for cap in capabilities
|
||||
]
|
||||
|
||||
return [
|
||||
{"name": "invoke", "description": "调用 Agent 能力"},
|
||||
{"name": "route", "description": "智能路由到最佳 Agent"},
|
||||
{"name": "describe", "description": "获取 Agent 能力描述"},
|
||||
]
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "endpoint" in agent_config or "registry_url" in agent_config
|
||||
159
.opencode/skills/uni-agent/adapters/mcp.py
Normal file
159
.opencode/skills/uni-agent/adapters/mcp.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
MCP (Model Context Protocol) 适配器
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .base import ProtocolAdapter, Connection, AgentInfo
|
||||
|
||||
try:
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
HAS_MCP = True
|
||||
except ImportError:
|
||||
HAS_MCP = False
|
||||
|
||||
|
||||
class MCPAdapter(ProtocolAdapter):
|
||||
"""MCP 协议适配器"""
|
||||
|
||||
protocol_name = "mcp"
|
||||
|
||||
def __init__(self, config_dir: Optional[Path] = None):
|
||||
self.config_dir = config_dir or Path(__file__).parent.parent / "config"
|
||||
self._sessions: Dict[str, Any] = {}
|
||||
|
||||
async def connect(self, agent_config: dict) -> Connection:
|
||||
"""建立连接"""
|
||||
if not HAS_MCP:
|
||||
raise ImportError("请安装 mcp 库: pip install mcp")
|
||||
|
||||
command = agent_config.get("command")
|
||||
args = agent_config.get("args", [])
|
||||
env = agent_config.get("env", {})
|
||||
|
||||
if not command:
|
||||
raise ValueError("MCP Agent 配置必须包含 command")
|
||||
|
||||
full_env = os.environ.copy()
|
||||
for k, v in env.items():
|
||||
if v.startswith("${") and v.endswith("}"):
|
||||
env_var = v[2:-1]
|
||||
full_env[k] = os.environ.get(env_var, "")
|
||||
else:
|
||||
full_env[k] = v
|
||||
|
||||
server_params = StdioServerParameters(
|
||||
command=command,
|
||||
args=args,
|
||||
env=full_env
|
||||
)
|
||||
|
||||
read, write = await stdio_client(server_params).__aenter__()
|
||||
session = ClientSession(read, write)
|
||||
await session.__aenter__()
|
||||
await session.initialize()
|
||||
|
||||
agent_id = agent_config.get("id", "")
|
||||
self._sessions[agent_id] = {
|
||||
"session": session,
|
||||
"read": read,
|
||||
"write": write,
|
||||
}
|
||||
|
||||
return Connection(
|
||||
agent_id=agent_id,
|
||||
protocol=self.protocol_name,
|
||||
endpoint=f"{command} {' '.join(args)}",
|
||||
session=session,
|
||||
metadata=agent_config
|
||||
)
|
||||
|
||||
async def call(
|
||||
self,
|
||||
connection: Connection,
|
||||
method: str,
|
||||
params: dict,
|
||||
timeout: float = 30.0
|
||||
) -> dict:
|
||||
"""调用 MCP 工具"""
|
||||
session: ClientSession = connection.session
|
||||
|
||||
result = await asyncio.wait_for(
|
||||
session.call_tool(method, params),
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
if hasattr(result, "content"):
|
||||
content = result.content
|
||||
if isinstance(content, list) and len(content) > 0:
|
||||
first = content[0]
|
||||
if hasattr(first, "text"):
|
||||
return {"success": True, "result": first.text}
|
||||
return {"success": True, "result": str(first)}
|
||||
return {"success": True, "result": content}
|
||||
|
||||
return {"success": True, "result": result}
|
||||
|
||||
async def discover(self, capability: str = "") -> List[AgentInfo]:
|
||||
"""发现 Agent(从本地配置)"""
|
||||
agents_file = self.config_dir / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return []
|
||||
|
||||
import yaml
|
||||
with open(agents_file) as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
agents = []
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("protocol") != "mcp":
|
||||
continue
|
||||
|
||||
if capability and capability.lower() not in agent.get("id", "").lower():
|
||||
continue
|
||||
|
||||
agents.append(AgentInfo(
|
||||
id=f"{agent['id']}@mcp",
|
||||
protocol="mcp",
|
||||
name=agent.get("name", agent["id"]),
|
||||
endpoint=f"{agent.get('command', '')} {' '.join(agent.get('args', []))}",
|
||||
metadata=agent
|
||||
))
|
||||
|
||||
return agents
|
||||
|
||||
async def close(self, connection: Connection):
|
||||
"""关闭连接"""
|
||||
agent_id = connection.agent_id
|
||||
if agent_id in self._sessions:
|
||||
session_info = self._sessions.pop(agent_id)
|
||||
session = session_info.get("session")
|
||||
if session:
|
||||
await session.__aexit__(None, None, None)
|
||||
|
||||
async def get_methods(self, connection: Connection) -> List[dict]:
|
||||
"""获取 MCP Server 支持的工具"""
|
||||
session: ClientSession = connection.session
|
||||
|
||||
result = await session.list_tools()
|
||||
|
||||
tools = []
|
||||
if hasattr(result, "tools"):
|
||||
for tool in result.tools:
|
||||
tools.append({
|
||||
"name": tool.name,
|
||||
"description": getattr(tool, "description", ""),
|
||||
"inputSchema": getattr(tool, "inputSchema", {}),
|
||||
})
|
||||
|
||||
return tools
|
||||
|
||||
def validate_config(self, agent_config: dict) -> bool:
|
||||
"""验证配置"""
|
||||
return "command" in agent_config
|
||||
121
.opencode/skills/uni-agent/config/agents.yaml
Normal file
121
.opencode/skills/uni-agent/config/agents.yaml
Normal file
@@ -0,0 +1,121 @@
|
||||
# UniAgent 配置文件
|
||||
# Agent 注册表
|
||||
|
||||
agents:
|
||||
# ==================== ANP Agents ====================
|
||||
- id: amap
|
||||
protocol: anp
|
||||
name: 高德地图
|
||||
ad_url: https://agent-connect.ai/mcp/agents/amap/ad.json
|
||||
description: 地点搜索、路线规划、天气查询、周边搜索
|
||||
|
||||
- id: kuaidi
|
||||
protocol: anp
|
||||
name: 快递查询
|
||||
ad_url: https://agent-connect.ai/mcp/agents/kuaidi/ad.json
|
||||
description: 快递单号追踪
|
||||
|
||||
- id: hotel
|
||||
protocol: anp
|
||||
name: 酒店预订
|
||||
ad_url: https://agent-connect.ai/agents/hotel-assistant/ad.json
|
||||
description: 搜索酒店、查询房价
|
||||
|
||||
- id: juhe
|
||||
protocol: anp
|
||||
name: 聚合查询
|
||||
ad_url: https://agent-connect.ai/mcp/agents/juhe/ad.json
|
||||
description: 多种生活服务查询
|
||||
|
||||
- id: navigation
|
||||
protocol: anp
|
||||
name: Agent导航
|
||||
ad_url: https://agent-search.ai/agents/navigation/ad.json
|
||||
description: 发现更多 ANP Agent
|
||||
|
||||
# ==================== MCP Servers ====================
|
||||
# - id: filesystem
|
||||
# protocol: mcp
|
||||
# name: 文件系统
|
||||
# command: npx
|
||||
# args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
# description: 文件读写操作
|
||||
|
||||
# - id: github
|
||||
# protocol: mcp
|
||||
# name: GitHub
|
||||
# command: npx
|
||||
# args: ["-y", "@modelcontextprotocol/server-github"]
|
||||
# env:
|
||||
# GITHUB_TOKEN: "${GITHUB_TOKEN}"
|
||||
# description: GitHub 仓库操作
|
||||
|
||||
# - id: sqlite
|
||||
# protocol: mcp
|
||||
# name: SQLite
|
||||
# command: npx
|
||||
# args: ["-y", "@modelcontextprotocol/server-sqlite", "/tmp/test.db"]
|
||||
# description: SQLite 数据库操作
|
||||
|
||||
# ==================== A2A Agents ====================
|
||||
# Google Agent-to-Agent 协议
|
||||
# - id: gemini_assistant
|
||||
# protocol: a2a
|
||||
# name: Gemini Assistant
|
||||
# endpoint: https://example.com/.well-known/agent.json
|
||||
# auth:
|
||||
# type: api_key
|
||||
# api_key: "${A2A_API_KEY}"
|
||||
|
||||
# - id: vertexai_agent
|
||||
# protocol: a2a
|
||||
# name: VertexAI Agent
|
||||
# endpoint: https://your-project.cloudfunctions.net/agent
|
||||
# auth:
|
||||
# type: oauth2
|
||||
# token_url: https://oauth2.googleapis.com/token
|
||||
# client_id: "${GOOGLE_CLIENT_ID}"
|
||||
# client_secret: "${GOOGLE_CLIENT_SECRET}"
|
||||
|
||||
# ==================== AITP Agents ====================
|
||||
# NEAR Agent Interaction & Transaction Protocol
|
||||
# - id: near_shop
|
||||
# protocol: aitp
|
||||
# name: NEAR Shop
|
||||
# endpoint: https://example.near.ai/api
|
||||
# wallet:
|
||||
# type: near
|
||||
# account_id: "${NEAR_ACCOUNT_ID}"
|
||||
|
||||
# - id: payment_agent
|
||||
# protocol: aitp
|
||||
# name: Payment Agent
|
||||
# endpoint: https://pay.example.com/aitp
|
||||
# description: 支持 NEAR/ETH 支付的 Agent
|
||||
|
||||
# ==================== Agent Protocol ====================
|
||||
# AI Engineer Foundation REST API 标准
|
||||
# - id: autogpt
|
||||
# protocol: agent_protocol
|
||||
# name: AutoGPT
|
||||
# endpoint: http://localhost:8000
|
||||
# api_key: "${AUTOGPT_API_KEY}"
|
||||
|
||||
# - id: smol_developer
|
||||
# protocol: ap
|
||||
# name: Smol Developer
|
||||
# endpoint: http://localhost:8080
|
||||
|
||||
# ==================== LMOS Agents ====================
|
||||
# Eclipse 企业级 Agent 平台
|
||||
# - id: customer_service
|
||||
# protocol: lmos
|
||||
# name: 客服 Agent
|
||||
# registry_url: http://lmos-registry.internal:8080
|
||||
# group: customer-agents
|
||||
|
||||
# - id: sales_agent
|
||||
# protocol: lmos
|
||||
# name: 销售 Agent
|
||||
# endpoint: http://sales-agent.internal:8080
|
||||
# description: 处理销售咨询
|
||||
14
.opencode/skills/uni-agent/requirements.txt
Normal file
14
.opencode/skills/uni-agent/requirements.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
# UniAgent 依赖
|
||||
|
||||
# 核心
|
||||
pyyaml>=6.0
|
||||
aiohttp>=3.8.0
|
||||
|
||||
# ANP 协议
|
||||
anp>=0.1.0
|
||||
|
||||
# MCP 协议 (可选)
|
||||
# mcp>=0.1.0
|
||||
|
||||
# A2A 协议 (待实现)
|
||||
# google-a2a>=0.1.0
|
||||
282
.opencode/skills/uni-agent/scripts/test_adapters.py
Normal file
282
.opencode/skills/uni-agent/scripts/test_adapters.py
Normal file
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
UniAgent 适配器测试脚本
|
||||
测试所有协议适配器的基本功能
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from adapters import get_adapter, ADAPTERS, list_protocols
|
||||
|
||||
|
||||
class TestResult:
|
||||
def __init__(self, protocol: str):
|
||||
self.protocol = protocol
|
||||
self.passed = 0
|
||||
self.failed = 0
|
||||
self.skipped = 0
|
||||
self.errors = []
|
||||
|
||||
def pass_(self, msg: str):
|
||||
self.passed += 1
|
||||
print(f" ✅ {msg}")
|
||||
|
||||
def fail(self, msg: str, error: str = ""):
|
||||
self.failed += 1
|
||||
self.errors.append(f"{msg}: {error}")
|
||||
print(f" ❌ {msg}: {error[:100]}")
|
||||
|
||||
def skip(self, msg: str):
|
||||
self.skipped += 1
|
||||
print(f" ⏭️ {msg} (跳过)")
|
||||
|
||||
def summary(self) -> str:
|
||||
status = "✅" if self.failed == 0 else "❌"
|
||||
return f"{status} {self.protocol}: {self.passed} passed, {self.failed} failed, {self.skipped} skipped"
|
||||
|
||||
|
||||
async def test_anp() -> TestResult:
|
||||
"""测试 ANP 适配器"""
|
||||
result = TestResult("ANP")
|
||||
print("\n[ANP] 测试 Agent Network Protocol...\n")
|
||||
|
||||
try:
|
||||
adapter = get_adapter("anp")
|
||||
result.pass_("获取适配器")
|
||||
except Exception as e:
|
||||
result.fail("获取适配器", str(e))
|
||||
return result
|
||||
|
||||
agent_config = {
|
||||
"id": "amap",
|
||||
"protocol": "anp",
|
||||
"ad_url": "https://agent-connect.ai/mcp/agents/amap/ad.json"
|
||||
}
|
||||
|
||||
try:
|
||||
connection = await adapter.connect(agent_config)
|
||||
result.pass_(f"建立连接: {connection.endpoint[:50]}...")
|
||||
except Exception as e:
|
||||
result.fail("建立连接", str(e))
|
||||
return result
|
||||
|
||||
try:
|
||||
methods = await adapter.get_methods(connection)
|
||||
result.pass_(f"获取方法列表: {len(methods)} 个方法")
|
||||
except Exception as e:
|
||||
result.fail("获取方法列表", str(e))
|
||||
|
||||
try:
|
||||
res = await adapter.call(connection, "maps_weather", {"city": "北京"})
|
||||
if res.get("success") or res.get("result"):
|
||||
city = res.get("result", {}).get("city", "")
|
||||
result.pass_(f"调用 maps_weather: {city}")
|
||||
else:
|
||||
result.fail("调用 maps_weather", str(res))
|
||||
except Exception as e:
|
||||
result.fail("调用 maps_weather", str(e))
|
||||
|
||||
try:
|
||||
res = await adapter.call(connection, "maps_text_search", {"keywords": "咖啡厅", "city": "上海"})
|
||||
if res.get("success") or res.get("result"):
|
||||
pois = res.get("result", {}).get("pois", [])
|
||||
result.pass_(f"调用 maps_text_search: 找到 {len(pois)} 个结果")
|
||||
else:
|
||||
result.fail("调用 maps_text_search", str(res))
|
||||
except Exception as e:
|
||||
result.fail("调用 maps_text_search", str(e))
|
||||
|
||||
try:
|
||||
agents = await adapter.discover()
|
||||
result.pass_(f"发现 Agent: {len(agents)} 个")
|
||||
except Exception as e:
|
||||
result.fail("发现 Agent", str(e))
|
||||
|
||||
try:
|
||||
await adapter.close(connection)
|
||||
result.pass_("关闭连接")
|
||||
except Exception as e:
|
||||
result.fail("关闭连接", str(e))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def test_mcp() -> TestResult:
|
||||
"""测试 MCP 适配器"""
|
||||
result = TestResult("MCP")
|
||||
print("\n[MCP] 测试 Model Context Protocol...\n")
|
||||
|
||||
try:
|
||||
adapter = get_adapter("mcp")
|
||||
result.pass_("获取适配器")
|
||||
except Exception as e:
|
||||
result.fail("获取适配器", str(e))
|
||||
return result
|
||||
|
||||
result.skip("MCP 需要本地 npx 环境,跳过实际连接测试")
|
||||
result.skip("如需测试,请配置 config/agents.yaml 中的 MCP Server")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def test_a2a() -> TestResult:
|
||||
"""测试 A2A 适配器"""
|
||||
result = TestResult("A2A")
|
||||
print("\n[A2A] 测试 Agent-to-Agent Protocol...\n")
|
||||
|
||||
try:
|
||||
adapter = get_adapter("a2a")
|
||||
result.pass_("获取适配器")
|
||||
except Exception as e:
|
||||
result.fail("获取适配器", str(e))
|
||||
return result
|
||||
|
||||
result.skip("A2A 需要配置 Agent endpoint,跳过实际连接测试")
|
||||
result.skip("如需测试,请配置 config/agents.yaml 中的 A2A Agent")
|
||||
|
||||
try:
|
||||
agents = await adapter.discover()
|
||||
result.pass_(f"发现 Agent: {len(agents)} 个 (本地配置)")
|
||||
except Exception as e:
|
||||
result.fail("发现 Agent", str(e))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def test_aitp() -> TestResult:
|
||||
"""测试 AITP 适配器"""
|
||||
result = TestResult("AITP")
|
||||
print("\n[AITP] 测试 Agent Interaction & Transaction Protocol...\n")
|
||||
|
||||
try:
|
||||
adapter = get_adapter("aitp")
|
||||
result.pass_("获取适配器")
|
||||
except Exception as e:
|
||||
result.fail("获取适配器", str(e))
|
||||
return result
|
||||
|
||||
result.skip("AITP 需要配置 NEAR 钱包和 endpoint,跳过实际连接测试")
|
||||
result.skip("如需测试,请配置 config/agents.yaml 中的 AITP Agent")
|
||||
|
||||
try:
|
||||
agents = await adapter.discover()
|
||||
result.pass_(f"发现 Agent: {len(agents)} 个 (本地配置)")
|
||||
except Exception as e:
|
||||
result.fail("发现 Agent", str(e))
|
||||
|
||||
try:
|
||||
methods = [
|
||||
{"name": "message", "desc": "发送消息"},
|
||||
{"name": "payment", "desc": "发起支付"},
|
||||
{"name": "decision", "desc": "请求决策"},
|
||||
]
|
||||
result.pass_(f"支持方法: {', '.join([m['name'] for m in methods])}")
|
||||
except Exception as e:
|
||||
result.fail("检查方法", str(e))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def test_agent_protocol() -> TestResult:
|
||||
"""测试 Agent Protocol 适配器"""
|
||||
result = TestResult("Agent Protocol")
|
||||
print("\n[AP] 测试 Agent Protocol...\n")
|
||||
|
||||
try:
|
||||
adapter = get_adapter("agent_protocol")
|
||||
result.pass_("获取适配器 (agent_protocol)")
|
||||
except Exception as e:
|
||||
result.fail("获取适配器", str(e))
|
||||
return result
|
||||
|
||||
try:
|
||||
adapter2 = get_adapter("ap")
|
||||
result.pass_("获取适配器 (别名 ap)")
|
||||
except Exception as e:
|
||||
result.fail("获取适配器别名", str(e))
|
||||
|
||||
result.skip("Agent Protocol 需要运行中的 Agent 服务,跳过实际连接测试")
|
||||
result.skip("如需测试,请启动 AutoGPT 或其他兼容服务")
|
||||
|
||||
try:
|
||||
agents = await adapter.discover()
|
||||
result.pass_(f"发现 Agent: {len(agents)} 个 (本地配置)")
|
||||
except Exception as e:
|
||||
result.fail("发现 Agent", str(e))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def test_lmos() -> TestResult:
|
||||
"""测试 LMOS 适配器"""
|
||||
result = TestResult("LMOS")
|
||||
print("\n[LMOS] 测试 Language Model Operating System...\n")
|
||||
|
||||
try:
|
||||
adapter = get_adapter("lmos")
|
||||
result.pass_("获取适配器")
|
||||
except Exception as e:
|
||||
result.fail("获取适配器", str(e))
|
||||
return result
|
||||
|
||||
result.skip("LMOS 需要配置注册中心或 Agent endpoint,跳过实际连接测试")
|
||||
result.skip("如需测试,请配置 config/agents.yaml 中的 LMOS Agent")
|
||||
|
||||
try:
|
||||
agents = await adapter.discover()
|
||||
result.pass_(f"发现 Agent: {len(agents)} 个 (本地配置)")
|
||||
except Exception as e:
|
||||
result.fail("发现 Agent", str(e))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def main():
|
||||
print("=" * 60)
|
||||
print(" UniAgent 适配器测试")
|
||||
print("=" * 60)
|
||||
|
||||
print(f"\n支持的协议: {list_protocols()}\n")
|
||||
|
||||
results = []
|
||||
|
||||
results.append(await test_anp())
|
||||
results.append(await test_mcp())
|
||||
results.append(await test_a2a())
|
||||
results.append(await test_aitp())
|
||||
results.append(await test_agent_protocol())
|
||||
results.append(await test_lmos())
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(" 测试汇总")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
total_passed = 0
|
||||
total_failed = 0
|
||||
total_skipped = 0
|
||||
|
||||
for r in results:
|
||||
print(r.summary())
|
||||
total_passed += r.passed
|
||||
total_failed += r.failed
|
||||
total_skipped += r.skipped
|
||||
|
||||
print(f"\n总计: {total_passed} passed, {total_failed} failed, {total_skipped} skipped")
|
||||
|
||||
if total_failed > 0:
|
||||
print("\n失败详情:")
|
||||
for r in results:
|
||||
for err in r.errors:
|
||||
print(f" - [{r.protocol}] {err}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\n🎉 所有测试通过!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
368
.opencode/skills/uni-agent/scripts/test_all.py
Normal file
368
.opencode/skills/uni-agent/scripts/test_all.py
Normal file
@@ -0,0 +1,368 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
UniAgent 完整测试脚本
|
||||
启动测试服务器,测试所有协议的真实交互
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import signal
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from adapters import get_adapter
|
||||
|
||||
|
||||
SERVERS = {}
|
||||
|
||||
|
||||
def start_server(name: str, script: str, port: int) -> subprocess.Popen:
|
||||
"""启动测试服务器"""
|
||||
script_path = Path(__file__).parent.parent / "test_servers" / script
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, str(script_path)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
time.sleep(0.5)
|
||||
if proc.poll() is not None:
|
||||
stderr = proc.stderr.read().decode()
|
||||
print(f" ❌ {name} 启动失败: {stderr}")
|
||||
return None
|
||||
print(f" ✅ {name} 启动成功 (port {port})")
|
||||
return proc
|
||||
|
||||
|
||||
def stop_servers():
|
||||
"""停止所有服务器"""
|
||||
for name, proc in SERVERS.items():
|
||||
if proc and proc.poll() is None:
|
||||
proc.terminate()
|
||||
proc.wait(timeout=2)
|
||||
|
||||
|
||||
async def test_anp():
|
||||
"""测试 ANP 适配器"""
|
||||
print("\n" + "=" * 50)
|
||||
print("[ANP] Agent Network Protocol")
|
||||
print("=" * 50)
|
||||
|
||||
adapter = get_adapter("anp")
|
||||
config = {
|
||||
"id": "amap",
|
||||
"protocol": "anp",
|
||||
"ad_url": "https://agent-connect.ai/mcp/agents/amap/ad.json"
|
||||
}
|
||||
|
||||
try:
|
||||
conn = await adapter.connect(config)
|
||||
print(f"✅ 连接成功: {conn.endpoint[:50]}...")
|
||||
|
||||
result = await adapter.call(conn, "maps_weather", {"city": "北京"})
|
||||
city = result.get("result", {}).get("city", "")
|
||||
print(f"✅ maps_weather: {city}")
|
||||
|
||||
result = await adapter.call(conn, "maps_text_search", {"keywords": "咖啡厅", "city": "上海"})
|
||||
pois = result.get("result", {}).get("pois", [])
|
||||
print(f"✅ maps_text_search: 找到 {len(pois)} 个结果")
|
||||
|
||||
await adapter.close(conn)
|
||||
print(f"✅ 关闭连接")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def test_a2a():
|
||||
"""测试 A2A 适配器"""
|
||||
print("\n" + "=" * 50)
|
||||
print("[A2A] Agent-to-Agent Protocol")
|
||||
print("=" * 50)
|
||||
|
||||
adapter = get_adapter("a2a")
|
||||
config = {
|
||||
"id": "test_agent",
|
||||
"protocol": "a2a",
|
||||
"endpoint": "http://localhost:8100"
|
||||
}
|
||||
|
||||
try:
|
||||
conn = await adapter.connect(config)
|
||||
print(f"✅ 连接成功")
|
||||
|
||||
methods = await adapter.get_methods(conn)
|
||||
print(f"✅ 获取方法: {len(methods)} 个 (包含 skills)")
|
||||
|
||||
result = await adapter.call(conn, "tasks/send", {
|
||||
"message": {
|
||||
"role": "user",
|
||||
"parts": [{"type": "text", "text": "Hello A2A!"}]
|
||||
}
|
||||
})
|
||||
if result.get("success"):
|
||||
task = result.get("result", {})
|
||||
history = task.get("history", [])
|
||||
if len(history) >= 2:
|
||||
response = history[-1].get("parts", [{}])[0].get("text", "")
|
||||
print(f"✅ tasks/send: {response}")
|
||||
else:
|
||||
print(f"✅ tasks/send: 任务已创建")
|
||||
else:
|
||||
print(f"❌ tasks/send 失败: {result}")
|
||||
return False
|
||||
|
||||
await adapter.close(conn)
|
||||
print(f"✅ 关闭连接")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def test_aitp():
|
||||
"""测试 AITP 适配器"""
|
||||
print("\n" + "=" * 50)
|
||||
print("[AITP] Agent Interaction & Transaction Protocol")
|
||||
print("=" * 50)
|
||||
|
||||
adapter = get_adapter("aitp")
|
||||
config = {
|
||||
"id": "test_shop",
|
||||
"protocol": "aitp",
|
||||
"endpoint": "http://localhost:8101"
|
||||
}
|
||||
|
||||
try:
|
||||
conn = await adapter.connect(config)
|
||||
print(f"✅ 连接成功 (Thread: {conn.session[:8]}...)")
|
||||
|
||||
result = await adapter.call(conn, "message", {"content": "Hello AITP!"})
|
||||
if result.get("success"):
|
||||
response = result.get("result", {}).get("content", "")
|
||||
print(f"✅ message: {response}")
|
||||
else:
|
||||
print(f"❌ message 失败")
|
||||
return False
|
||||
|
||||
result = await adapter.call(conn, "payment", {
|
||||
"amount": 10,
|
||||
"currency": "NEAR",
|
||||
"recipient": "shop.near"
|
||||
})
|
||||
if result.get("success"):
|
||||
payment = result.get("result", {}).get("payment_response", {})
|
||||
status = payment.get("status", "")
|
||||
tx_id = payment.get("transaction_id", "")[:8]
|
||||
print(f"✅ payment: {status} (tx: {tx_id}...)")
|
||||
else:
|
||||
print(f"❌ payment 失败")
|
||||
return False
|
||||
|
||||
result = await adapter.call(conn, "decision", {
|
||||
"question": "选择颜色",
|
||||
"options": ["红色", "蓝色", "绿色"]
|
||||
})
|
||||
if result.get("success"):
|
||||
decision = result.get("result", {}).get("decision_response", {})
|
||||
selected = decision.get("selected", "")
|
||||
print(f"✅ decision: 选择了 {selected}")
|
||||
else:
|
||||
print(f"❌ decision 失败")
|
||||
return False
|
||||
|
||||
await adapter.close(conn)
|
||||
print(f"✅ 关闭连接")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def test_agent_protocol():
|
||||
"""测试 Agent Protocol 适配器"""
|
||||
print("\n" + "=" * 50)
|
||||
print("[AP] Agent Protocol")
|
||||
print("=" * 50)
|
||||
|
||||
adapter = get_adapter("agent_protocol")
|
||||
config = {
|
||||
"id": "test_agent",
|
||||
"protocol": "agent_protocol",
|
||||
"endpoint": "http://localhost:8102"
|
||||
}
|
||||
|
||||
try:
|
||||
conn = await adapter.connect(config)
|
||||
print(f"✅ 连接成功")
|
||||
|
||||
result = await adapter.call(conn, "create_task", {"input": "Hello Agent Protocol!"})
|
||||
if result.get("success"):
|
||||
task_id = result.get("task_id", "")
|
||||
print(f"✅ create_task: {task_id[:8]}...")
|
||||
else:
|
||||
print(f"❌ create_task 失败")
|
||||
return False
|
||||
|
||||
result = await adapter.call(conn, "execute_step", {
|
||||
"task_id": task_id,
|
||||
"input": "Process this"
|
||||
})
|
||||
if result.get("success"):
|
||||
step = result.get("result", {})
|
||||
output = step.get("output", "")
|
||||
print(f"✅ execute_step: {output}")
|
||||
else:
|
||||
print(f"❌ execute_step 失败")
|
||||
return False
|
||||
|
||||
result = await adapter.call(conn, "get_task", {"task_id": task_id})
|
||||
if result.get("success"):
|
||||
task = result.get("result", {})
|
||||
status = task.get("status", "")
|
||||
print(f"✅ get_task: status={status}")
|
||||
else:
|
||||
print(f"❌ get_task 失败")
|
||||
return False
|
||||
|
||||
result = await adapter.call(conn, "get_artifacts", {"task_id": task_id})
|
||||
if result.get("success"):
|
||||
artifacts = result.get("result", {}).get("artifacts", [])
|
||||
print(f"✅ get_artifacts: {len(artifacts)} 个产物")
|
||||
else:
|
||||
print(f"❌ get_artifacts 失败")
|
||||
return False
|
||||
|
||||
await adapter.close(conn)
|
||||
print(f"✅ 关闭连接")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def test_lmos():
|
||||
"""测试 LMOS 适配器"""
|
||||
print("\n" + "=" * 50)
|
||||
print("[LMOS] Language Model Operating System")
|
||||
print("=" * 50)
|
||||
|
||||
adapter = get_adapter("lmos")
|
||||
config = {
|
||||
"id": "calculator",
|
||||
"protocol": "lmos",
|
||||
"endpoint": "http://localhost:8103/agents/calculator"
|
||||
}
|
||||
|
||||
try:
|
||||
conn = await adapter.connect(config)
|
||||
print(f"✅ 连接成功")
|
||||
|
||||
result = await adapter.call(conn, "invoke", {
|
||||
"capability": "add",
|
||||
"input": {"a": 10, "b": 20}
|
||||
})
|
||||
if result.get("success"):
|
||||
output = result.get("result", {}).get("output", {})
|
||||
calc_result = output.get("result", "")
|
||||
print(f"✅ invoke add(10, 20): {calc_result}")
|
||||
else:
|
||||
print(f"❌ invoke add 失败")
|
||||
return False
|
||||
|
||||
result = await adapter.call(conn, "invoke", {
|
||||
"capability": "multiply",
|
||||
"input": {"a": 6, "b": 7}
|
||||
})
|
||||
if result.get("success"):
|
||||
output = result.get("result", {}).get("output", {})
|
||||
calc_result = output.get("result", "")
|
||||
print(f"✅ invoke multiply(6, 7): {calc_result}")
|
||||
else:
|
||||
print(f"❌ invoke multiply 失败")
|
||||
return False
|
||||
|
||||
greeter_config = {
|
||||
"id": "greeter",
|
||||
"protocol": "lmos",
|
||||
"endpoint": "http://localhost:8103/agents/greeter"
|
||||
}
|
||||
conn2 = await adapter.connect(greeter_config)
|
||||
result = await adapter.call(conn2, "invoke", {
|
||||
"capability": "greet",
|
||||
"input": {"name": "test_user"}
|
||||
})
|
||||
if result.get("success"):
|
||||
output = result.get("result", {}).get("output", {})
|
||||
greeting = output.get("greeting", "")
|
||||
print(f"✅ invoke greet: {greeting}")
|
||||
else:
|
||||
print(f"❌ invoke greet 失败")
|
||||
return False
|
||||
|
||||
await adapter.close(conn)
|
||||
await adapter.close(conn2)
|
||||
print(f"✅ 关闭连接")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def main():
|
||||
print("=" * 60)
|
||||
print(" UniAgent 完整交互测试")
|
||||
print("=" * 60)
|
||||
|
||||
print("\n[1] 启动测试服务器...")
|
||||
SERVERS["A2A"] = start_server("A2A Server", "a2a_server.py", 8100)
|
||||
SERVERS["AITP"] = start_server("AITP Server", "aitp_server.py", 8101)
|
||||
SERVERS["AP"] = start_server("Agent Protocol Server", "agent_protocol_server.py", 8102)
|
||||
SERVERS["LMOS"] = start_server("LMOS Server", "lmos_server.py", 8103)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
print("\n[2] 开始测试...")
|
||||
|
||||
results = {}
|
||||
|
||||
try:
|
||||
results["ANP"] = await test_anp()
|
||||
results["A2A"] = await test_a2a()
|
||||
results["AITP"] = await test_aitp()
|
||||
results["Agent Protocol"] = await test_agent_protocol()
|
||||
results["LMOS"] = await test_lmos()
|
||||
finally:
|
||||
print("\n[3] 停止测试服务器...")
|
||||
stop_servers()
|
||||
print(" ✅ 所有服务器已停止")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(" 测试汇总")
|
||||
print("=" * 60)
|
||||
|
||||
all_passed = True
|
||||
for name, passed in results.items():
|
||||
status = "✅" if passed else "❌"
|
||||
print(f" {status} {name}")
|
||||
if not passed:
|
||||
all_passed = False
|
||||
|
||||
print()
|
||||
if all_passed:
|
||||
print("🎉 所有协议测试通过!")
|
||||
else:
|
||||
print("⚠️ 部分测试失败")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
stop_servers()
|
||||
print("\n测试中断")
|
||||
257
.opencode/skills/uni-agent/scripts/uni_cli.py
Normal file
257
.opencode/skills/uni-agent/scripts/uni_cli.py
Normal file
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
UniAgent CLI - 统一智能体协议调用工具
|
||||
|
||||
用法:
|
||||
# 调用 Agent
|
||||
python uni_cli.py call amap@anp maps_weather '{"city":"北京"}'
|
||||
python uni_cli.py call filesystem@mcp read_file '{"path":"/tmp/a.txt"}'
|
||||
|
||||
# 发现 Agent
|
||||
python uni_cli.py discover weather
|
||||
|
||||
# 列出已注册 Agent
|
||||
python uni_cli.py list
|
||||
|
||||
# 查看 Agent 方法
|
||||
python uni_cli.py methods amap@anp
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
import yaml
|
||||
from adapters import get_adapter, ADAPTERS
|
||||
|
||||
|
||||
CONFIG_DIR = Path(__file__).parent.parent / "config"
|
||||
|
||||
|
||||
def load_agents_config():
|
||||
"""加载 Agent 配置"""
|
||||
agents_file = CONFIG_DIR / "agents.yaml"
|
||||
if not agents_file.exists():
|
||||
return {"agents": []}
|
||||
|
||||
with open(agents_file) as f:
|
||||
return yaml.safe_load(f) or {"agents": []}
|
||||
|
||||
|
||||
def parse_agent_id(agent_id: str) -> tuple:
|
||||
"""解析 Agent ID,返回 (name, protocol)"""
|
||||
if "@" not in agent_id:
|
||||
return agent_id, "anp"
|
||||
|
||||
parts = agent_id.rsplit("@", 1)
|
||||
return parts[0], parts[1]
|
||||
|
||||
|
||||
def get_agent_config(agent_name: str, protocol: str) -> dict:
|
||||
"""获取 Agent 配置"""
|
||||
config = load_agents_config()
|
||||
|
||||
for agent in config.get("agents", []):
|
||||
if agent.get("id") == agent_name and agent.get("protocol") == protocol:
|
||||
return agent
|
||||
|
||||
raise ValueError(f"未找到 Agent: {agent_name}@{protocol}")
|
||||
|
||||
|
||||
async def call_agent(agent_id: str, method: str, params: dict):
|
||||
"""调用 Agent"""
|
||||
agent_name, protocol = parse_agent_id(agent_id)
|
||||
|
||||
print(f"协议: {protocol}")
|
||||
print(f"Agent: {agent_name}")
|
||||
print(f"方法: {method}")
|
||||
print(f"参数: {json.dumps(params, ensure_ascii=False)}")
|
||||
print()
|
||||
|
||||
agent_config = get_agent_config(agent_name, protocol)
|
||||
adapter = get_adapter(protocol)
|
||||
|
||||
connection = await adapter.connect(agent_config)
|
||||
|
||||
try:
|
||||
result = await adapter.call(connection, method, params)
|
||||
print("=== 结果 ===")
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
finally:
|
||||
await adapter.close(connection)
|
||||
|
||||
|
||||
async def discover_agents(capability: str = ""):
|
||||
"""发现 Agent"""
|
||||
print(f"搜索能力: {capability or '全部'}\n")
|
||||
|
||||
all_agents = []
|
||||
|
||||
for protocol, adapter_class in ADAPTERS.items():
|
||||
adapter = adapter_class()
|
||||
agents = await adapter.discover(capability)
|
||||
all_agents.extend(agents)
|
||||
|
||||
if not all_agents:
|
||||
print("未找到匹配的 Agent")
|
||||
return
|
||||
|
||||
print(f"找到 {len(all_agents)} 个 Agent:\n")
|
||||
for agent in all_agents:
|
||||
print(f" {agent.id}")
|
||||
print(f" 名称: {agent.name}")
|
||||
print(f" 协议: {agent.protocol}")
|
||||
if agent.endpoint:
|
||||
print(f" 端点: {agent.endpoint[:60]}...")
|
||||
print()
|
||||
|
||||
|
||||
async def list_agents():
|
||||
"""列出所有已注册 Agent"""
|
||||
config = load_agents_config()
|
||||
agents = config.get("agents", [])
|
||||
|
||||
if not agents:
|
||||
print("暂无已注册的 Agent")
|
||||
print("请编辑 config/agents.yaml 添加 Agent")
|
||||
return
|
||||
|
||||
print(f"\n已注册的 Agent ({len(agents)} 个):\n")
|
||||
|
||||
by_protocol = {}
|
||||
for agent in agents:
|
||||
protocol = agent.get("protocol", "unknown")
|
||||
if protocol not in by_protocol:
|
||||
by_protocol[protocol] = []
|
||||
by_protocol[protocol].append(agent)
|
||||
|
||||
for protocol, protocol_agents in by_protocol.items():
|
||||
print(f"[{protocol.upper()}]")
|
||||
for agent in protocol_agents:
|
||||
agent_id = f"{agent['id']}@{protocol}"
|
||||
name = agent.get("name", agent["id"])
|
||||
print(f" {agent_id}: {name}")
|
||||
print()
|
||||
|
||||
|
||||
async def show_methods(agent_id: str):
|
||||
"""显示 Agent 支持的方法"""
|
||||
agent_name, protocol = parse_agent_id(agent_id)
|
||||
|
||||
print(f"获取 {agent_name}@{protocol} 的方法列表...\n")
|
||||
|
||||
agent_config = get_agent_config(agent_name, protocol)
|
||||
adapter = get_adapter(protocol)
|
||||
|
||||
connection = await adapter.connect(agent_config)
|
||||
|
||||
try:
|
||||
methods = await adapter.get_methods(connection)
|
||||
|
||||
if not methods:
|
||||
print("未获取到方法列表")
|
||||
return
|
||||
|
||||
print(f"可用方法 ({len(methods)} 个):\n")
|
||||
for m in methods[:30]:
|
||||
name = m.get("name", "unknown")
|
||||
desc = m.get("description", "")[:50]
|
||||
print(f" - {name}: {desc}")
|
||||
|
||||
if len(methods) > 30:
|
||||
print(f" ... 还有 {len(methods) - 30} 个方法")
|
||||
finally:
|
||||
await adapter.close(connection)
|
||||
|
||||
|
||||
def show_help():
|
||||
print("""
|
||||
UniAgent - 统一智能体协议调用工具
|
||||
|
||||
用法:
|
||||
python uni_cli.py <命令> [参数...]
|
||||
|
||||
命令:
|
||||
call <agent_id> <method> <params> 调用 Agent 方法
|
||||
discover [capability] 发现 Agent
|
||||
list 列出已注册 Agent
|
||||
methods <agent_id> 查看 Agent 方法
|
||||
|
||||
Agent ID 格式:
|
||||
<name>@<protocol>
|
||||
|
||||
示例:
|
||||
- amap@anp ANP 协议的高德地图
|
||||
- filesystem@mcp MCP 协议的文件系统
|
||||
|
||||
支持的协议:
|
||||
- anp ANP (Agent Network Protocol) - 去中心化 Agent 网络
|
||||
- mcp MCP (Model Context Protocol) - LLM 工具调用
|
||||
- a2a A2A (Agent-to-Agent) - Google Agent 协作
|
||||
- aitp AITP (Agent Interaction & Transaction) - 交互交易
|
||||
- agent_protocol Agent Protocol - REST API 标准 (别名: ap)
|
||||
- lmos LMOS (Language Model OS) - 企业级 Agent 平台
|
||||
|
||||
示例:
|
||||
# ANP - 查天气
|
||||
python uni_cli.py call amap@anp maps_weather '{"city":"北京"}'
|
||||
|
||||
# MCP - 读文件
|
||||
python uni_cli.py call filesystem@mcp read_file '{"path":"/tmp/a.txt"}'
|
||||
|
||||
# A2A - 发送任务
|
||||
python uni_cli.py call assistant@a2a tasks/send '{"message":{"content":"hello"}}'
|
||||
|
||||
# AITP - 对话
|
||||
python uni_cli.py call shop@aitp message '{"content":"我要买咖啡"}'
|
||||
|
||||
# Agent Protocol - 创建任务
|
||||
python uni_cli.py call autogpt@ap create_task '{"input":"写代码"}'
|
||||
|
||||
# 发现 Agent
|
||||
python uni_cli.py discover weather
|
||||
""")
|
||||
|
||||
|
||||
async def main():
|
||||
if len(sys.argv) < 2:
|
||||
show_help()
|
||||
return
|
||||
|
||||
cmd = sys.argv[1]
|
||||
|
||||
if cmd in ["help", "-h", "--help"]:
|
||||
show_help()
|
||||
|
||||
elif cmd == "call":
|
||||
if len(sys.argv) < 5:
|
||||
print("用法: python uni_cli.py call <agent_id> <method> '<params_json>'")
|
||||
return
|
||||
agent_id = sys.argv[2]
|
||||
method = sys.argv[3]
|
||||
params = json.loads(sys.argv[4])
|
||||
await call_agent(agent_id, method, params)
|
||||
|
||||
elif cmd == "discover":
|
||||
capability = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
await discover_agents(capability)
|
||||
|
||||
elif cmd == "list":
|
||||
await list_agents()
|
||||
|
||||
elif cmd == "methods":
|
||||
if len(sys.argv) < 3:
|
||||
print("用法: python uni_cli.py methods <agent_id>")
|
||||
return
|
||||
await show_methods(sys.argv[2])
|
||||
|
||||
else:
|
||||
print(f"未知命令: {cmd}")
|
||||
show_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
107
.opencode/skills/uni-agent/setup.sh
Normal file
107
.opencode/skills/uni-agent/setup.sh
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# UniAgent 一键安装脚本
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CONFIG_DIR="$SCRIPT_DIR/config"
|
||||
|
||||
echo "=========================================="
|
||||
echo " UniAgent - 统一智能体协议适配层"
|
||||
echo " Connect Any Agent, Any Protocol"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 1. 检查 Python
|
||||
echo "[1/4] 检查 Python 环境..."
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo "❌ 未找到 Python3,请先安装 Python 3.8+"
|
||||
exit 1
|
||||
fi
|
||||
PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
|
||||
echo "✅ Python $PYTHON_VERSION"
|
||||
|
||||
# 2. 安装依赖
|
||||
echo ""
|
||||
echo "[2/4] 安装 Python 依赖..."
|
||||
pip3 install -q pyyaml aiohttp anp --break-system-packages 2>/dev/null || pip3 install -q pyyaml aiohttp anp --user 2>/dev/null || pip3 install -q pyyaml aiohttp anp
|
||||
echo "✅ 依赖安装完成"
|
||||
|
||||
# 3. 检查/生成 DID 身份(用于 ANP)
|
||||
echo ""
|
||||
echo "[3/4] 配置 DID 身份 (ANP 协议)..."
|
||||
|
||||
if [ -f "$CONFIG_DIR/did.json" ] && [ -f "$CONFIG_DIR/private-key.pem" ]; then
|
||||
echo "✅ 已存在 DID 身份,跳过生成"
|
||||
DID_ID=$(python3 -c "import json; print(json.load(open('$CONFIG_DIR/did.json'))['id'])" 2>/dev/null || echo "unknown")
|
||||
echo " DID: $DID_ID"
|
||||
else
|
||||
echo "⚙️ 生成本地临时身份..."
|
||||
|
||||
# 生成 secp256k1 私钥
|
||||
openssl ecparam -name secp256k1 -genkey -noout -out "$CONFIG_DIR/private-key.pem" 2>/dev/null
|
||||
|
||||
# 生成随机 ID
|
||||
RANDOM_ID=$(openssl rand -hex 8)
|
||||
|
||||
# 创建 DID 文档
|
||||
cat > "$CONFIG_DIR/did.json" << EOF
|
||||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/did/v1",
|
||||
"https://w3id.org/security/suites/secp256k1-2019/v1"
|
||||
],
|
||||
"id": "did:wba:local:user:$RANDOM_ID",
|
||||
"verificationMethod": [
|
||||
{
|
||||
"id": "did:wba:local:user:$RANDOM_ID#key-1",
|
||||
"type": "EcdsaSecp256k1VerificationKey2019",
|
||||
"controller": "did:wba:local:user:$RANDOM_ID"
|
||||
}
|
||||
],
|
||||
"authentication": [
|
||||
"did:wba:local:user:$RANDOM_ID#key-1"
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "✅ 本地身份生成完成"
|
||||
echo " DID: did:wba:local:user:$RANDOM_ID"
|
||||
fi
|
||||
|
||||
# 4. 验证安装
|
||||
echo ""
|
||||
echo "[4/4] 验证安装..."
|
||||
cd "$SCRIPT_DIR"
|
||||
if python3 scripts/uni_cli.py list &> /dev/null; then
|
||||
echo "✅ 安装成功!"
|
||||
else
|
||||
echo "⚠️ 安装可能有问题,请检查错误信息"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 安装完成!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "支持的协议:"
|
||||
echo " - ANP (Agent Network Protocol) ✅ 已实现"
|
||||
echo " - MCP (Model Context Protocol) ✅ 已实现"
|
||||
echo " - A2A (Agent-to-Agent) ✅ 已实现"
|
||||
echo " - AITP (Agent Interaction & Tx) ✅ 已实现"
|
||||
echo " - AP (Agent Protocol) ✅ 已实现"
|
||||
echo " - LMOS (Eclipse LMOS) ✅ 已实现"
|
||||
echo ""
|
||||
echo "快速开始:"
|
||||
echo ""
|
||||
echo " # 列出已注册 Agent"
|
||||
echo " python scripts/uni_cli.py list"
|
||||
echo ""
|
||||
echo " # 调用 ANP Agent 查天气"
|
||||
echo " python scripts/uni_cli.py call amap@anp maps_weather '{\"city\":\"北京\"}'"
|
||||
echo ""
|
||||
echo " # 查看 Agent 方法"
|
||||
echo " python scripts/uni_cli.py methods amap@anp"
|
||||
echo ""
|
||||
165
.opencode/skills/uni-agent/test_servers/a2a_server.py
Normal file
165
.opencode/skills/uni-agent/test_servers/a2a_server.py
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
A2A 测试服务器 - 简单的 Echo Agent
|
||||
HTTP 服务,提供 Agent Card 和 JSON-RPC 端点
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import urlparse
|
||||
|
||||
PORT = 8100
|
||||
|
||||
|
||||
class A2AHandler(BaseHTTPRequestHandler):
|
||||
|
||||
tasks = {}
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
def send_json(self, data: dict, status: int = 200):
|
||||
body = json.dumps(data, ensure_ascii=False).encode()
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", len(body))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_GET(self):
|
||||
path = urlparse(self.path).path
|
||||
|
||||
if path == "/.well-known/agent.json":
|
||||
self.send_json({
|
||||
"name": "Test A2A Agent",
|
||||
"description": "A simple echo agent for testing",
|
||||
"url": f"http://localhost:{PORT}/rpc",
|
||||
"version": "1.0.0",
|
||||
"capabilities": {
|
||||
"streaming": False,
|
||||
"pushNotifications": False
|
||||
},
|
||||
"skills": [
|
||||
{
|
||||
"id": "echo",
|
||||
"name": "Echo",
|
||||
"description": "Echo back the message",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "greet",
|
||||
"name": "Greet",
|
||||
"description": "Greet the user",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"authentication": {
|
||||
"schemes": ["none"]
|
||||
}
|
||||
})
|
||||
else:
|
||||
self.send_json({"error": "Not found"}, 404)
|
||||
|
||||
def do_POST(self):
|
||||
path = urlparse(self.path).path
|
||||
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length)
|
||||
|
||||
try:
|
||||
request = json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
self.send_json({"error": "Invalid JSON"}, 400)
|
||||
return
|
||||
|
||||
if path == "/rpc":
|
||||
self.handle_rpc(request)
|
||||
else:
|
||||
self.send_json({"error": "Not found"}, 404)
|
||||
|
||||
def handle_rpc(self, request: dict):
|
||||
method = request.get("method", "")
|
||||
params = request.get("params", {})
|
||||
req_id = request.get("id", str(uuid.uuid4()))
|
||||
|
||||
if method == "tasks/send":
|
||||
task_id = params.get("id", str(uuid.uuid4()))
|
||||
message = params.get("message", {})
|
||||
content = message.get("parts", [{}])[0].get("text", "") if "parts" in message else message.get("content", "")
|
||||
|
||||
response_text = f"Echo: {content}"
|
||||
|
||||
A2AHandler.tasks[task_id] = {
|
||||
"id": task_id,
|
||||
"status": {"state": "completed"},
|
||||
"history": [
|
||||
message,
|
||||
{"role": "agent", "parts": [{"type": "text", "text": response_text}]}
|
||||
]
|
||||
}
|
||||
|
||||
self.send_json({
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"result": A2AHandler.tasks[task_id]
|
||||
})
|
||||
|
||||
elif method == "tasks/get":
|
||||
task_id = params.get("id", "")
|
||||
if task_id in A2AHandler.tasks:
|
||||
self.send_json({
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"result": A2AHandler.tasks[task_id]
|
||||
})
|
||||
else:
|
||||
self.send_json({
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"error": {"code": -32000, "message": "Task not found"}
|
||||
})
|
||||
|
||||
elif method == "tasks/cancel":
|
||||
task_id = params.get("id", "")
|
||||
if task_id in A2AHandler.tasks:
|
||||
A2AHandler.tasks[task_id]["status"]["state"] = "canceled"
|
||||
self.send_json({
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"result": {"success": True}
|
||||
})
|
||||
else:
|
||||
self.send_json({
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"error": {"code": -32000, "message": "Task not found"}
|
||||
})
|
||||
|
||||
else:
|
||||
self.send_json({
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"error": {"code": -32601, "message": f"Unknown method: {method}"}
|
||||
})
|
||||
|
||||
|
||||
def main():
|
||||
server = HTTPServer(("localhost", PORT), A2AHandler)
|
||||
print(f"A2A Test Server running on http://localhost:{PORT}")
|
||||
print(f"Agent Card: http://localhost:{PORT}/.well-known/agent.json")
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
146
.opencode/skills/uni-agent/test_servers/agent_protocol_server.py
Normal file
146
.opencode/skills/uni-agent/test_servers/agent_protocol_server.py
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Agent Protocol 测试服务器
|
||||
REST API 标准实现
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import urlparse
|
||||
from datetime import datetime
|
||||
|
||||
PORT = 8102
|
||||
|
||||
|
||||
class APHandler(BaseHTTPRequestHandler):
|
||||
|
||||
tasks = {}
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
def send_json(self, data: dict, status: int = 200):
|
||||
body = json.dumps(data, ensure_ascii=False).encode()
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", len(body))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_GET(self):
|
||||
path = urlparse(self.path).path
|
||||
|
||||
if path == "/" or path == "/ap/v1":
|
||||
self.send_json({
|
||||
"name": "Test Agent Protocol Server",
|
||||
"version": "1.0.0",
|
||||
"protocol_version": "v1"
|
||||
})
|
||||
|
||||
elif path == "/ap/v1/agent/tasks":
|
||||
self.send_json({
|
||||
"tasks": list(APHandler.tasks.values())
|
||||
})
|
||||
|
||||
elif path.startswith("/ap/v1/agent/tasks/"):
|
||||
parts = path.split("/")
|
||||
task_id = parts[5] if len(parts) > 5 else ""
|
||||
|
||||
if "/artifacts" in path:
|
||||
if task_id in APHandler.tasks:
|
||||
self.send_json({
|
||||
"artifacts": APHandler.tasks[task_id].get("artifacts", [])
|
||||
})
|
||||
else:
|
||||
self.send_json({"error": "Task not found"}, 404)
|
||||
|
||||
elif "/steps" in path:
|
||||
if task_id in APHandler.tasks:
|
||||
self.send_json({
|
||||
"steps": APHandler.tasks[task_id].get("steps", [])
|
||||
})
|
||||
else:
|
||||
self.send_json({"error": "Task not found"}, 404)
|
||||
|
||||
else:
|
||||
if task_id in APHandler.tasks:
|
||||
self.send_json(APHandler.tasks[task_id])
|
||||
else:
|
||||
self.send_json({"error": "Task not found"}, 404)
|
||||
|
||||
else:
|
||||
self.send_json({"error": "Not found"}, 404)
|
||||
|
||||
def do_POST(self):
|
||||
path = urlparse(self.path).path
|
||||
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length)
|
||||
|
||||
try:
|
||||
request = json.loads(body) if body else {}
|
||||
except json.JSONDecodeError:
|
||||
self.send_json({"error": "Invalid JSON"}, 400)
|
||||
return
|
||||
|
||||
if path == "/ap/v1/agent/tasks":
|
||||
task_id = str(uuid.uuid4())
|
||||
task_input = request.get("input", "")
|
||||
|
||||
APHandler.tasks[task_id] = {
|
||||
"task_id": task_id,
|
||||
"input": task_input,
|
||||
"status": "running",
|
||||
"steps": [],
|
||||
"artifacts": [],
|
||||
"created_at": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
self.send_json(APHandler.tasks[task_id], 201)
|
||||
|
||||
elif path.startswith("/ap/v1/agent/tasks/") and path.endswith("/steps"):
|
||||
parts = path.split("/")
|
||||
task_id = parts[5]
|
||||
|
||||
if task_id not in APHandler.tasks:
|
||||
self.send_json({"error": "Task not found"}, 404)
|
||||
return
|
||||
|
||||
step_input = request.get("input", "")
|
||||
step_id = str(uuid.uuid4())
|
||||
|
||||
step = {
|
||||
"step_id": step_id,
|
||||
"input": step_input,
|
||||
"output": f"Processed: {step_input}" if step_input else "Step executed",
|
||||
"status": "completed",
|
||||
"is_last": True,
|
||||
"created_at": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
APHandler.tasks[task_id]["steps"].append(step)
|
||||
APHandler.tasks[task_id]["status"] = "completed"
|
||||
|
||||
APHandler.tasks[task_id]["artifacts"].append({
|
||||
"artifact_id": str(uuid.uuid4()),
|
||||
"file_name": "output.txt",
|
||||
"relative_path": "/output.txt",
|
||||
"content": step["output"]
|
||||
})
|
||||
|
||||
self.send_json(step, 201)
|
||||
|
||||
else:
|
||||
self.send_json({"error": "Not found"}, 404)
|
||||
|
||||
|
||||
def main():
|
||||
server = HTTPServer(("localhost", PORT), APHandler)
|
||||
print(f"Agent Protocol Test Server running on http://localhost:{PORT}")
|
||||
print(f"API Base: http://localhost:{PORT}/ap/v1")
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
160
.opencode/skills/uni-agent/test_servers/aitp_server.py
Normal file
160
.opencode/skills/uni-agent/test_servers/aitp_server.py
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AITP 测试服务器 - 模拟交互与交易
|
||||
HTTP 服务,支持 Thread 会话
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import urlparse
|
||||
from datetime import datetime
|
||||
|
||||
PORT = 8101
|
||||
|
||||
|
||||
class AITPHandler(BaseHTTPRequestHandler):
|
||||
|
||||
threads = {}
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
def send_json(self, data: dict, status: int = 200):
|
||||
body = json.dumps(data, ensure_ascii=False).encode()
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", len(body))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_GET(self):
|
||||
path = urlparse(self.path).path
|
||||
|
||||
if path == "/":
|
||||
self.send_json({
|
||||
"name": "Test AITP Agent",
|
||||
"description": "A simple AITP agent for testing",
|
||||
"version": "1.0.0",
|
||||
"capabilities": ["aitp-01", "aitp-02", "aitp-03"]
|
||||
})
|
||||
|
||||
elif path.startswith("/threads/"):
|
||||
parts = path.split("/")
|
||||
if len(parts) >= 3:
|
||||
thread_id = parts[2]
|
||||
if thread_id in AITPHandler.threads:
|
||||
self.send_json(AITPHandler.threads[thread_id])
|
||||
else:
|
||||
self.send_json({"error": "Thread not found"}, 404)
|
||||
else:
|
||||
self.send_json({"error": "Not found"}, 404)
|
||||
|
||||
def do_POST(self):
|
||||
path = urlparse(self.path).path
|
||||
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length)
|
||||
|
||||
try:
|
||||
request = json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
self.send_json({"error": "Invalid JSON"}, 400)
|
||||
return
|
||||
|
||||
if path == "/threads":
|
||||
thread_id = str(uuid.uuid4())
|
||||
AITPHandler.threads[thread_id] = {
|
||||
"id": thread_id,
|
||||
"status": "open",
|
||||
"messages": [],
|
||||
"created_at": datetime.now().isoformat()
|
||||
}
|
||||
self.send_json({"thread_id": thread_id})
|
||||
|
||||
elif path.startswith("/threads/") and path.endswith("/messages"):
|
||||
parts = path.split("/")
|
||||
thread_id = parts[2]
|
||||
|
||||
if thread_id not in AITPHandler.threads:
|
||||
AITPHandler.threads[thread_id] = {
|
||||
"id": thread_id,
|
||||
"status": "open",
|
||||
"messages": [],
|
||||
"created_at": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
thread = AITPHandler.threads[thread_id]
|
||||
|
||||
if "capability" in request:
|
||||
capability = request.get("capability")
|
||||
|
||||
if capability == "aitp-01":
|
||||
payment_req = request.get("payment_request", {})
|
||||
response = {
|
||||
"role": "agent",
|
||||
"capability": "aitp-01",
|
||||
"payment_response": {
|
||||
"status": "approved",
|
||||
"transaction_id": str(uuid.uuid4()),
|
||||
"amount": payment_req.get("amount"),
|
||||
"currency": payment_req.get("currency", "NEAR"),
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
}
|
||||
|
||||
elif capability == "aitp-02":
|
||||
decision_req = request.get("decision_request", {})
|
||||
response = {
|
||||
"role": "agent",
|
||||
"capability": "aitp-02",
|
||||
"decision_response": {
|
||||
"question": decision_req.get("question"),
|
||||
"selected": decision_req.get("options", ["Yes"])[0] if decision_req.get("options") else "Yes"
|
||||
}
|
||||
}
|
||||
|
||||
elif capability == "aitp-03":
|
||||
data_req = request.get("data_request", {})
|
||||
response = {
|
||||
"role": "agent",
|
||||
"capability": "aitp-03",
|
||||
"data_response": {
|
||||
"schema": data_req.get("schema", {}),
|
||||
"data": {"sample": "test_data", "timestamp": datetime.now().isoformat()}
|
||||
}
|
||||
}
|
||||
|
||||
else:
|
||||
response = {
|
||||
"role": "agent",
|
||||
"error": f"Unknown capability: {capability}"
|
||||
}
|
||||
|
||||
else:
|
||||
message = request.get("message", {})
|
||||
content = message.get("content", "")
|
||||
|
||||
response = {
|
||||
"role": "agent",
|
||||
"content": f"AITP Echo: {content}",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
thread["messages"].append(request)
|
||||
thread["messages"].append(response)
|
||||
|
||||
self.send_json(response)
|
||||
|
||||
else:
|
||||
self.send_json({"error": "Not found"}, 404)
|
||||
|
||||
|
||||
def main():
|
||||
server = HTTPServer(("localhost", PORT), AITPHandler)
|
||||
print(f"AITP Test Server running on http://localhost:{PORT}")
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
169
.opencode/skills/uni-agent/test_servers/lmos_server.py
Normal file
169
.opencode/skills/uni-agent/test_servers/lmos_server.py
Normal file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
LMOS 测试服务器 - 模拟企业级 Agent 平台
|
||||
包含注册中心和 Agent 能力调用
|
||||
"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from datetime import datetime
|
||||
|
||||
PORT = 8103
|
||||
|
||||
|
||||
MOCK_AGENTS = [
|
||||
{
|
||||
"id": "calculator",
|
||||
"name": "Calculator Agent",
|
||||
"description": "Performs calculations",
|
||||
"endpoint": f"http://localhost:{PORT}/agents/calculator",
|
||||
"capabilities": [
|
||||
{"id": "add", "description": "Add two numbers"},
|
||||
{"id": "multiply", "description": "Multiply two numbers"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "greeter",
|
||||
"name": "Greeter Agent",
|
||||
"description": "Greets users",
|
||||
"endpoint": f"http://localhost:{PORT}/agents/greeter",
|
||||
"capabilities": [
|
||||
{"id": "greet", "description": "Greet a user by name"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
class LMOSHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
def send_json(self, data: dict, status: int = 200):
|
||||
body = json.dumps(data, ensure_ascii=False).encode()
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", len(body))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_GET(self):
|
||||
parsed = urlparse(self.path)
|
||||
path = parsed.path
|
||||
query = parse_qs(parsed.query)
|
||||
|
||||
if path == "/":
|
||||
self.send_json({
|
||||
"name": "Test LMOS Registry",
|
||||
"version": "1.0.0",
|
||||
"agents": len(MOCK_AGENTS)
|
||||
})
|
||||
|
||||
elif path == "/agents":
|
||||
capability = query.get("capability", [None])[0]
|
||||
|
||||
if capability:
|
||||
filtered = [
|
||||
a for a in MOCK_AGENTS
|
||||
if any(c["id"] == capability for c in a["capabilities"])
|
||||
]
|
||||
self.send_json({"agents": filtered})
|
||||
else:
|
||||
self.send_json({"agents": MOCK_AGENTS})
|
||||
|
||||
elif path.startswith("/agents/") and path.endswith("/capabilities"):
|
||||
agent_id = path.split("/")[2]
|
||||
agent = next((a for a in MOCK_AGENTS if a["id"] == agent_id), None)
|
||||
|
||||
if agent:
|
||||
self.send_json({"capabilities": agent["capabilities"]})
|
||||
else:
|
||||
self.send_json({"error": "Agent not found"}, 404)
|
||||
|
||||
else:
|
||||
self.send_json({"error": "Not found"}, 404)
|
||||
|
||||
def do_POST(self):
|
||||
parsed = urlparse(self.path)
|
||||
path = parsed.path
|
||||
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length)
|
||||
|
||||
try:
|
||||
request = json.loads(body) if body else {}
|
||||
except json.JSONDecodeError:
|
||||
self.send_json({"error": "Invalid JSON"}, 400)
|
||||
return
|
||||
|
||||
if path.startswith("/agents/") and path.endswith("/invoke"):
|
||||
agent_id = path.split("/")[2]
|
||||
agent = next((a for a in MOCK_AGENTS if a["id"] == agent_id), None)
|
||||
|
||||
if not agent:
|
||||
self.send_json({"error": "Agent not found"}, 404)
|
||||
return
|
||||
|
||||
capability = request.get("capability", "")
|
||||
input_data = request.get("input", {})
|
||||
|
||||
if agent_id == "calculator":
|
||||
if capability == "add":
|
||||
a = input_data.get("a", 0)
|
||||
b = input_data.get("b", 0)
|
||||
result = {"result": a + b}
|
||||
elif capability == "multiply":
|
||||
a = input_data.get("a", 0)
|
||||
b = input_data.get("b", 0)
|
||||
result = {"result": a * b}
|
||||
else:
|
||||
result = {"error": f"Unknown capability: {capability}"}
|
||||
|
||||
elif agent_id == "greeter":
|
||||
if capability == "greet":
|
||||
name = input_data.get("name", "World")
|
||||
result = {"greeting": f"Hello, {name}!"}
|
||||
else:
|
||||
result = {"error": f"Unknown capability: {capability}"}
|
||||
|
||||
else:
|
||||
result = {"error": "Unknown agent"}
|
||||
|
||||
self.send_json({
|
||||
"agent_id": agent_id,
|
||||
"capability": capability,
|
||||
"output": result,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
elif path == "/route":
|
||||
query_text = request.get("query", "")
|
||||
|
||||
if "add" in query_text.lower() or "calculate" in query_text.lower():
|
||||
best_agent = MOCK_AGENTS[0]
|
||||
elif "greet" in query_text.lower() or "hello" in query_text.lower():
|
||||
best_agent = MOCK_AGENTS[1]
|
||||
else:
|
||||
best_agent = MOCK_AGENTS[0]
|
||||
|
||||
self.send_json({
|
||||
"recommended_agent": best_agent,
|
||||
"confidence": 0.85,
|
||||
"alternatives": [a for a in MOCK_AGENTS if a["id"] != best_agent["id"]]
|
||||
})
|
||||
|
||||
else:
|
||||
self.send_json({"error": "Not found"}, 404)
|
||||
|
||||
|
||||
def main():
|
||||
server = HTTPServer(("localhost", PORT), LMOSHandler)
|
||||
print(f"LMOS Test Server running on http://localhost:{PORT}")
|
||||
print(f"Registry: http://localhost:{PORT}/agents")
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
161
.opencode/skills/uni-agent/test_servers/mcp_server.py
Normal file
161
.opencode/skills/uni-agent/test_servers/mcp_server.py
Normal file
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MCP 测试服务器 - 简单的 Echo + 计算器
|
||||
通过 stdio 通信
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def send_response(id: str, result: dict):
|
||||
"""发送 JSON-RPC 响应"""
|
||||
response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": id,
|
||||
"result": result
|
||||
}
|
||||
msg = json.dumps(response)
|
||||
sys.stdout.write(f"Content-Length: {len(msg)}\r\n\r\n{msg}")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def send_error(id: str, code: int, message: str):
|
||||
"""发送错误响应"""
|
||||
response = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": id,
|
||||
"error": {"code": code, "message": message}
|
||||
}
|
||||
msg = json.dumps(response)
|
||||
sys.stdout.write(f"Content-Length: {len(msg)}\r\n\r\n{msg}")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def handle_request(request: dict):
|
||||
"""处理请求"""
|
||||
method = request.get("method", "")
|
||||
params = request.get("params", {})
|
||||
req_id = request.get("id", "0")
|
||||
|
||||
if method == "initialize":
|
||||
send_response(req_id, {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {
|
||||
"tools": {"listChanged": True}
|
||||
},
|
||||
"serverInfo": {
|
||||
"name": "test-mcp-server",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
})
|
||||
|
||||
elif method == "notifications/initialized":
|
||||
pass
|
||||
|
||||
elif method == "tools/list":
|
||||
send_response(req_id, {
|
||||
"tools": [
|
||||
{
|
||||
"name": "echo",
|
||||
"description": "返回输入的消息",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {"type": "string", "description": "要返回的消息"}
|
||||
},
|
||||
"required": ["message"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "add",
|
||||
"description": "两数相加",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": {"type": "number"},
|
||||
"b": {"type": "number"}
|
||||
},
|
||||
"required": ["a", "b"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_time",
|
||||
"description": "获取当前时间",
|
||||
"inputSchema": {"type": "object", "properties": {}}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
elif method == "tools/call":
|
||||
tool_name = params.get("name", "")
|
||||
tool_args = params.get("arguments", {})
|
||||
|
||||
if tool_name == "echo":
|
||||
msg = tool_args.get("message", "")
|
||||
send_response(req_id, {
|
||||
"content": [{"type": "text", "text": f"Echo: {msg}"}]
|
||||
})
|
||||
|
||||
elif tool_name == "add":
|
||||
a = tool_args.get("a", 0)
|
||||
b = tool_args.get("b", 0)
|
||||
send_response(req_id, {
|
||||
"content": [{"type": "text", "text": str(a + b)}]
|
||||
})
|
||||
|
||||
elif tool_name == "get_time":
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
send_response(req_id, {
|
||||
"content": [{"type": "text", "text": now}]
|
||||
})
|
||||
|
||||
else:
|
||||
send_error(req_id, -32601, f"Unknown tool: {tool_name}")
|
||||
|
||||
else:
|
||||
send_error(req_id, -32601, f"Unknown method: {method}")
|
||||
|
||||
|
||||
def main():
|
||||
"""主循环 - 读取 stdin,处理请求"""
|
||||
buffer = ""
|
||||
|
||||
while True:
|
||||
try:
|
||||
line = sys.stdin.readline()
|
||||
if not line:
|
||||
break
|
||||
|
||||
buffer += line
|
||||
|
||||
if "Content-Length:" in buffer:
|
||||
parts = buffer.split("\r\n\r\n", 1)
|
||||
if len(parts) == 2:
|
||||
header, body = parts
|
||||
length = int(header.split(":")[1].strip())
|
||||
|
||||
while len(body) < length:
|
||||
body += sys.stdin.read(length - len(body))
|
||||
|
||||
request = json.loads(body[:length])
|
||||
handle_request(request)
|
||||
|
||||
buffer = body[length:]
|
||||
|
||||
elif buffer.strip().startswith("{"):
|
||||
try:
|
||||
request = json.loads(buffer.strip())
|
||||
handle_request(request)
|
||||
buffer = ""
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"Error: {e}\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user