chore: commit remaining workspace changes

This commit is contained in:
Haitao Pan 2026-03-25 14:25:24 +08:00
parent bc30a02151
commit 70db3fae34
8 changed files with 1491 additions and 4 deletions

View File

@ -0,0 +1,105 @@
# External ACP Server
一个独立的 Agent Communication Protocol (ACP) 服务实现,支持:
- **Single-agent 模式**: 单代理执行
- **Multi-agent 模式**: 多代理协作
- **自定义工具**: 扩展 MCP 工具能力
## 架构
```
┌─────────────────────────────────────────────────────┐
│ ACP Server │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ WebSocket │ │ HTTP POST │ │ Tool Bridge │ │
│ │ /acp │ │ /acp/rpc │ │ (MCP) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┴────────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ Router │ │
│ └──────┬──────┘ │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ │ │ │ │
│ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ │
│ │ Session │ │ Agent │ │ Tool │ │
│ │ Manager │ │ Executor │ │ Handler │ │
│ └───────────┘ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────────┘
```
## 快速开始
```bash
# 安装依赖
pip install -r requirements.txt
# 配置环境变量
export ACP_LISTEN_ADDR="127.0.0.1:8787"
export ACP_MULTI_AGENT_ENABLED="true"
# 启动服务
python server.py serve
# 或使用自定义配置
python server.py serve --listen 0.0.0.0:9000
```
## 环境变量
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `ACP_LISTEN_ADDR` | `127.0.0.1:8787` | 服务监听地址 |
| `ACP_MULTI_AGENT_ENABLED` | `true` | 是否启用多代理模式 |
| `ACP_MULTI_AGENT_MODEL` | `gpt-4o` | 多代理使用的模型 |
## 协议
### JSON-RPC 方法
| 方法 | 说明 |
|------|------|
| `acp.capabilities` | 查询服务器能力 |
| `session.start` | 启动新会话 |
| `session.message` | 发送消息(延续会话)|
| `session.cancel` | 取消会话 |
| `session.close` | 关闭会话 |
### 通知类型
| type | 说明 |
|------|------|
| `status` | 会话状态变更 |
| `delta` | 增量文本输出 |
| `step` | 多代理步骤进度 |
## 扩展自定义工具
`tools/` 目录下添加新的工具实现:
```python
# tools/my_tool.py
class MyTool:
name = "my_tool"
description = "工具描述"
input_schema = {
"type": "object",
"properties": {
"input": {"type": "string", "description": "输入参数"}
},
"required": ["input"]
}
def execute(self, arguments: dict) -> str:
return f"处理结果: {arguments.get('input')}"
```
`server.py` 中注册:
```python
from tools.my_tool import MyTool
tool_registry.register(MyTool())
```

View File

@ -0,0 +1,3 @@
# ACP Server dependencies
aiohttp>=3.9.0
requests>=2.31.0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
# Tools package for external ACP server
# Add your custom tools here
from .base import Tool, ToolRegistry

View File

@ -0,0 +1,94 @@
"""
Tool base classes for external ACP server
"""
from abc import ABC, abstractmethod
from typing import Any, Optional
class Tool(ABC):
"""
工具基类
所有自定义工具必须继承此类并实现以下方法
- name: 工具名称
- description: 工具描述
- input_schema: 输入参数 JSON Schema
- execute: 执行逻辑
"""
@property
@abstractmethod
def name(self) -> str:
"""
工具名称
必须唯一使用小写字母和下划线例如: code_review
"""
pass
@property
@abstractmethod
def description(self) -> str:
"""
工具描述
简短描述工具的功能用于 AI 选择合适的工具
"""
pass
@property
def input_schema(self) -> dict:
"""
输入参数 JSON Schema
定义工具接受的参数结构
"""
return {
"type": "object",
"properties": {},
"required": []
}
@abstractmethod
def execute(self, arguments: dict) -> str:
"""
执行工具
Args:
arguments: 工具参数根据 input_schema 验证
Returns:
工具执行结果作为文本返回
"""
pass
class ToolRegistry:
"""工具注册表"""
def __init__(self):
self._tools: dict[str, Tool] = {}
def register(self, tool: Tool):
"""注册工具"""
self._tools[tool.name] = tool
def get(self, name: str) -> Optional[Tool]:
"""获取工具"""
return self._tools.get(name)
def list_all(self) -> list[Tool]:
"""列出所有工具"""
return list(self._tools.values())
def to_mcp_tools_list(self) -> list[dict]:
"""转换为 MCP 工具列表格式"""
return [
{
"name": tool.name,
"description": tool.description,
"inputSchema": tool.input_schema
}
for tool in self._tools.values()
]

View File

@ -0,0 +1,117 @@
"""
示例自定义工具代码审查工具
这个工具展示了如何扩展 ACP 服务器添加自定义工具
"""
class CodeReviewTool:
"""代码审查工具 - 使用 LLM 审查代码变更"""
@property
def name(self) -> str:
return "code_review"
@property
def description(self) -> str:
return "Review code changes and provide feedback"
@property
def input_schema(self) -> dict:
return {
"type": "object",
"properties": {
"diff": {
"type": "string",
"description": "The git diff or code changes to review"
},
"context": {
"type": "string",
"description": "Optional context about the changes"
},
"focus": {
"type": "string",
"description": "Areas to focus on (security, performance, style, etc.)"
}
},
"required": ["diff"]
}
def execute(self, arguments: dict) -> str:
"""
执行代码审查
实际实现中你可以
1. 调用外部 LLM API
2. 使用本地模型
3. 执行静态分析工具
4. 查询代码库知识库
"""
import os
diff = arguments.get("diff", "")
context = arguments.get("context", "")
focus = arguments.get("focus", "general")
# 获取 LLM 配置
api_key = os.environ.get("LLM_API_KEY", "")
base_url = os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1")
model = os.environ.get("LLM_MODEL", "gpt-4o")
if not api_key:
return "Error: LLM_API_KEY environment variable not set"
# 构建审查提示
system_prompt = """You are an expert code reviewer. Analyze the provided code changes and provide:
1. Summary of changes
2. Potential issues (bugs, security, performance)
3. Code style suggestions
4. Overall assessment
Be concise and actionable."""
if focus != "general":
system_prompt += f"\n\nFocus particularly on: {focus}"
user_prompt = f"Context: {context}\n\nCode changes:\n```\n{diff}\n```" if context else f"Code changes:\n```\n{diff}\n```"
# 调用 LLM API
try:
import requests
response = requests.post(
f"{base_url.rstrip('/')}/chat/completions",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
},
json={
"model": model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
"max_tokens": 4096
},
timeout=120
)
if response.status_code != 200:
return f"Error: API returned {response.status_code}: {response.text[:200]}"
return response.json()["choices"][0]["message"]["content"]
except Exception as e:
return f"Error: {e}"
# 注册工具的示例
def register_tools(registry):
"""注册所有自定义工具"""
from .base import Tool
# 注册代码审查工具
registry.register(CodeReviewTool())
# 在这里添加更多工具...
# registry.register(AnotherTool())

View File

@ -26,8 +26,8 @@
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
8E6F4A7D31A1A10100A1B2C3 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 8E6F4A7C31A1A10100A1B2C3 /* PrivacyInfo.xcprivacy */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
8E6F4A7D31A1A10100A1B2C3 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 8E6F4A7C31A1A10100A1B2C3 /* PrivacyInfo.xcprivacy */; };
A96EF8FFA0E80B16252FE834 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5F4830709C3A67EC8096888 /* Pods_RunnerTests.framework */; };
F02922E20E15948F8CE5469F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2099D82E31DC5912EA477802 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
@ -74,7 +74,6 @@
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
8E6F4A7C31A1A10100A1B2C3 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Runner/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
@ -85,6 +84,7 @@
73098D7450C105A02267F617 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
890E8B245EC31E1C0F085895 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
8E6F4A7C31A1A10100A1B2C3 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Runner/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
B5F4830709C3A67EC8096888 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D8467093D02A39550CF15067 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
@ -422,7 +422,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n/bin/sh \"${PROJECT_DIR}/../scripts/ensure-framework-dsyms.sh\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */

View File

@ -4,7 +4,13 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
BRIDGE_DIR="$ROOT_DIR/go/go_core"
OUTPUT_DIR="${OUTPUT_DIR:-$ROOT_DIR/build/bin}"
OUTPUT_PATH="${OUTPUT_PATH:-$OUTPUT_DIR/xworkmate-go-core}"
OUTPUT_PATH_BASE="${OUTPUT_DIR}/xworkmate-go-core"
if [[ "$(uname -s)" == *MINGW* || "$(uname -s)" == *MSYS* || "$(uname -s)" == *CYGWIN* ]]; then
OUTPUT_PATH="${OUTPUT_PATH:-${OUTPUT_PATH_BASE}.exe}"
else
OUTPUT_PATH="${OUTPUT_PATH:-${OUTPUT_PATH_BASE}}"
fi
if [[ ! -f "$BRIDGE_DIR/go.mod" ]]; then
echo "Missing go.mod in $BRIDGE_DIR" >&2