初识 ACP (Agent Client Protocol)

893 阅读15分钟

初识 ACP 协议:AI 编码助手的标准化通信协议

从 MCP 到 ACP,探索 AI Agent 生态的标准化之路

目录


一、引言:AI Agent 生态的标准化挑战

1.1 当前 AI 开发工具面临的问题

在 AI 辅助编程工具快速发展的今天,我们看到了各种强大的 AI 编码助手:

  • GitHub Copilot:微软的 AI 代码补全工具
  • Cursor:AI 驱动的代码编辑器
  • Claude Code:Anthropic 的智能编码助手
  • Codex CLI:OpenAI 的命令行编码工具
  • Gemini Code Assist:Google 的编码助手

然而,这些工具之间存在严重的互操作性问题

graph TB
    subgraph "现状:信息孤岛"
        VSCode[VS Code] -->|锁定| Copilot[Copilot]
        Zed[Zed] -->|锁定| Agent[Agent]

        Claude[Claude]
        Gemini[Gemini]

        VSCode -.X.- Claude
        VSCode -.X.- Gemini
        Zed -.X.- Claude
        Zed -.X.- Copilot

        style VSCode fill:#e1f5ff
        style Zed fill:#e1f5ff
        style Copilot fill:#fff4e6
        style Agent fill:#fff4e6
        style Claude fill:#f3e5f5
        style Gemini fill:#f3e5f5
    end

    Note["每个编辑器只能用特定的 Agent<br/>用户无法自由选择和切换"]

    style Note fill:#ffebee,stroke:#c62828

核心挑战:

  1. 编辑器锁定:用户必须为特定 AI Agent 切换编辑器
  2. 重复开发:每个编辑器都要为每个 Agent 单独开发集成
  3. 用户体验割裂:不同 Agent 的交互方式完全不同
  4. 生态碎片化:难以形成统一的开发者社区

1.2 标准化协议的价值

正如 Language Server Protocol (LSP) 将语言智能从单一 IDE 中解放出来,我们需要一个类似的标准来解决 AI Agent 的互操作性问题。

这就是 Agent Client Protocol (ACP) 诞生的背景。


二、从 MCP 说起:理解 AI 协议的演进

2.1 什么是 MCP?

在介绍 ACP 之前,我们需要先了解 MCP (Model Context Protocol)

MCP 是 Anthropic 推出的开源标准协议,用于连接 AI 模型外部系统(数据源、工具、API 等)。

graph TD
    AIModel["AI Model<br/>(Claude, GPT, etc.)"]

    AIModel -->|MCP Protocol| MCPServers

    subgraph MCPServers["MCP Servers"]
        DB[Database Server]
        FS[File System]
        API[API Services]
        KB[Knowledge Base]
    end

    style AIModel fill:#e3f2fd
    style MCPServers fill:#f3e5f5
    style DB fill:#fff3e0
    style FS fill:#fff3e0
    style API fill:#fff3e0
    style KB fill:#fff3e0

MCP 的三大核心原语:

2.1.1 Resources(资源)

类似文件系统的只读数据源,供 AI 模型读取上下文。

// MCP Resource 示例
{
  "uri": "file:///workspace/README.md",
  "name": "项目文档",
  "mimeType": "text/markdown",
  "description": "项目需求和架构文档"
}
2.1.2 Tools(工具)

AI 模型可调用的可执行函数。

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("code-tools")

@mcp.tool()
async def run_tests(test_file: str) -> str:
    """运行指定的测试文件"""
    result = subprocess.run(['pytest', test_file], capture_output=True)
    return result.stdout.decode()
2.1.3 Prompts(提示模板)

预编写的任务模板,标准化常见操作。

{
  "name": "code_review",
  "description": "代码审查提示模板",
  "arguments": [
    {
      "name": "language",
      "description": "编程语言",
      "required": true
    }
  ]
}

2.2 MCP 的局限性

虽然 MCP 解决了 AI 模型与工具的连接问题,但它并不解决编辑器与 AI Agent 的通信问题

graph LR
    Editor["编辑器<br/>(Zed)"] -.->|"❓ 没有标准协议"| Agent["AI Agent<br/>(Claude)"]
    Agent -->|"✓ MCP 协议"| Tools["工具<br/>(DB/API)"]

    style Editor fill:#ffebee
    style Agent fill:#e8f5e9
    style Tools fill:#e8f5e9

这就是 ACP 要解决的问题。


三、ACP 是什么?

3.1 定义

Agent Client Protocol (ACP) 是一个开放标准协议,用于规范代码编辑器与 **AI 编码助手(Coding Agent)**之间的通信。

graph LR
    Editor["Editor<br/>(Zed)"]
    Agent["Agent<br/>(Claude)"]
    Tools["Tools & Resources"]

    Editor <-->|ACP Protocol| Agent
    Agent -->|MCP Protocol| Tools

    style Editor fill:#e3f2fd
    style Agent fill:#fff3e0
    style Tools fill:#e8f5e9

核心理念:

就像 USB-C 接口可以连接任何设备,ACP 让任何编辑器都能使用任何 AI Agent。

3.2 设计目标

目标说明
通用性任何编辑器都能集成任何符合 ACP 的 Agent
隐私优先本地通信,不经过第三方服务器
开源开放Apache 2.0 许可证,任何人都可以实现
可扩展性支持未来的新功能和新场景

3.3 与 LSP 的类比

如果你熟悉 Language Server Protocol (LSP),可以这样理解 ACP:

LSP 之于语言智能 = ACP 之于 AI 编码助手

graph LR
    subgraph LSP["LSP 模式"]
        E1[VS Code] <-->|LSP| L1[TypeScript]
        E2[Vim] <-->|LSP| L2[Python]
        E3[Emacs] <-->|LSP| L3[Go]
    end

    subgraph ACP["ACP 模式"]
        E4[Zed] <-->|ACP| A1[Claude Code]
        E5[Neovim] <-->|ACP| A2[Gemini]
        E6[JetBrains] <-->|ACP| A3[Codex]
    end

    style LSP fill:#e3f2fd
    style ACP fill:#fff3e0

四、ACP 核心架构设计

4.1 通信模型

ACP 采用 JSON-RPC 2.0 协议,基于 **stdio(标准输入输出)**进行通信。

graph TD
    Editor["Editor<br/>(主进程)"]
    Agent["Agent<br/>(子进程)"]

    Editor -->|"spawn()"| Agent
    Editor -->|"写入 stdin<br/>(JSON-RPC 2.0)"| Agent
    Agent -->|"写入 stdout<br/>(JSON-RPC 2.0)"| Editor

    style Editor fill:#e3f2fd
    style Agent fill:#fff3e0

    Note["通信方式:<br/>• Editor 写入 Agent 的 stdin<br/>• Agent 写入 stdout 返回给 Editor<br/>• 消息格式:JSON-RPC 2.0"]
    style Note fill:#e8f5e9

优势:

  1. 简单高效:无需网络层,直接进程间通信
  2. 隐私安全:所有数据都在本地,不经过外部服务器
  3. 跨平台:stdin/stdout 是所有操作系统的标准

4.2 协议层次

ACP 分为两个核心层:

graph TD
    subgraph Application["应用层 (Application Layer)"]
        SM[Session Management]
        TC[Tool Calls]
        PR[Permission Requests]
        FO[File Operations]
    end

    subgraph Protocol["协议层 (Protocol Layer)"]
        JSON[JSON-RPC 2.0]
        RR[Request/Response]
        NT[Notifications]
        EH[Error Handling]
    end

    subgraph Transport["传输层 (Transport Layer)"]
        STDIO[stdio - stdin/stdout]
    end

    Application --> Protocol
    Protocol --> Transport

    style Application fill:#e3f2fd
    style Protocol fill:#fff3e0
    style Transport fill:#e8f5e9

4.3 消息类型

ACP 支持三种消息类型:

4.3.1. Request(请求)

客户端向服务器发送请求,期待响应。

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "0.1.0",
    "clientInfo": {
      "name": "Zed",
      "version": "0.158.0"
    }
  }
}
4.3.2. Response(响应)

服务器对请求的响应。

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "0.1.0",
    "serverInfo": {
      "name": "Claude Code",
      "version": "1.0.0"
    },
    "capabilities": {
      "tools": true,
      "resources": true
    }
  }
}
4.3.3. Notification(通知)

单向消息,不期待响应。

{
  "jsonrpc": "2.0",
  "method": "session/update",
  "params": {
    "sessionId": "session-123",
    "update": {
      "sessionUpdate": "agent_message_chunk",
      "content": {
        "type": "text",
        "text": "正在分析代码..."
      }
    }
  }
}

五、ACP 协议详解

5.1 核心方法

ACP 定义了一系列标准方法:

方法类型说明
initializeRequest初始化连接,交换能力信息
authenticateRequest身份验证(可选)
session/newRequest创建新的对话会话
session/promptRequest向 Agent 发送用户消息
session/updateNotificationAgent 推送会话更新
session/request_permissionNotificationAgent 请求用户权限
fs/read_text_fileRequest读取文件内容
fs/write_text_fileRequest写入文件内容
end_turnNotificationAgent 完成一轮响应

5.2 初始化流程

sequenceDiagram
    participant Editor
    participant Agent

    Editor->>Agent: spawn(agent-cli)
    Note over Editor,Agent: 启动 Agent 子进程

    Editor->>Agent: initialize request
    Note right of Editor: {clientInfo, version}

    Agent-->>Editor: initialize response
    Note left of Agent: {serverInfo, capabilities}

    Editor->>Agent: authenticate (optional)
    Agent-->>Editor: auth response

    Editor->>Agent: session/new
    Note right of Editor: {cwd, mcpServers}

    Agent-->>Editor: session created
    Note left of Agent: {sessionId}

    Note over Editor,Agent: ● 连接建立完成,可以开始对话

详细说明:

步骤 1:启动 Agent 进程
// AionUi 项目中的实际代码
// src/agent/acp/AcpConnection.ts

async connect(backend: AcpBackend, cliPath?: string, workingDir?: string) {
  const command = cliPath || this.getDefaultCliPath(backend);

  // 使用 spawn 启动 Agent 子进程
  this.agentProcess = spawn(command, [], {
    cwd: workingDir,
    env: process.env,
  });

  // 监听 stdout(Agent 的输出)
  this.agentProcess.stdout.on('data', this.handleStdout.bind(this));

  // 监听 stderr(Agent 的日志)
  this.agentProcess.stderr.on('data', this.handleStderr.bind(this));

  // 初始化协议
  await this.initialize();
}
步骤 2:发送初始化请求
private async initialize(): Promise<AcpResponse> {
  return await this.sendRequest('initialize', {
    protocolVersion: '0.1.0',
    clientInfo: {
      name: 'AionUi',
      version: '1.0.0',
    },
  });
}
步骤 3:接收能力信息
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "0.1.0",
    "serverInfo": {
      "name": "Claude Code",
      "version": "1.0.128"
    },
    "capabilities": {
      "tools": true,
      "resources": true,
      "streaming": false
    }
  }
}

5.3 会话更新类型

ACP 定义了丰富的会话更新类型,让编辑器能实时显示 Agent 的思考和操作过程。

// AionUi 项目中的类型定义
// src/types/acpTypes.ts

export type AcpSessionUpdate =
  | AgentMessageChunkUpdate // Agent 消息块
  | AgentThoughtChunkUpdate // Agent 思考过程
  | ToolCallUpdate // 工具调用
  | ToolCallUpdateStatus // 工具状态更新
  | PlanUpdate // 任务计划
  | AvailableCommandsUpdate // 可用命令列表
  | UserMessageChunkUpdate; // 用户消息块
5.3.1. Agent 消息块(AgentMessageChunkUpdate)

Agent 向用户发送的普通消息。

{
  "method": "session/update",
  "params": {
    "sessionId": "sess-123",
    "update": {
      "sessionUpdate": "agent_message_chunk",
      "content": {
        "type": "text",
        "text": "我已经分析了你的代码,发现了以下问题..."
      }
    }
  }
}
5.3.2. Agent 思考过程(AgentThoughtChunkUpdate)

Agent 的内部思考过程,类似 "思维链"。

{
  "method": "session/update",
  "params": {
    "sessionId": "sess-123",
    "update": {
      "sessionUpdate": "agent_thought_chunk",
      "content": {
        "type": "text",
        "text": "首先,我需要检查 package.json 中的依赖版本..."
      }
    }
  }
}
5.3.3. 工具调用(ToolCallUpdate)

最重要的更新类型,表示 Agent 要执行某个操作。

interface ToolCallUpdate {
  sessionUpdate: 'tool_call';
  toolCallId: string; // 工具调用唯一 ID
  status: 'pending' | 'in_progress' | 'completed' | 'failed';
  title: string; // 操作描述
  kind: 'read' | 'edit' | 'execute'; // 操作类型
  rawInput?: any; // 原始输入参数
  content?: Array<{
    type: 'content' | 'diff';
    // ... 内容详情
  }>;
  locations?: Array<{
    path: string; // 受影响的文件路径
  }>;
}

示例:读取文件

{
  "method": "session/update",
  "params": {
    "sessionId": "sess-123",
    "update": {
      "sessionUpdate": "tool_call",
      "toolCallId": "tool-001",
      "status": "pending",
      "title": "读取 src/index.ts",
      "kind": "read",
      "locations": [{ "path": "/workspace/src/index.ts" }]
    }
  }
}

示例:编辑文件

{
  "method": "session/update",
  "params": {
    "sessionId": "sess-123",
    "update": {
      "sessionUpdate": "tool_call",
      "toolCallId": "tool-002",
      "status": "in_progress",
      "title": "修复 TypeScript 类型错误",
      "kind": "edit",
      "content": [
        {
          "type": "diff",
          "diff": "--- a/src/index.ts\n+++ b/src/index.ts\n@@ -10,7 +10,7 @@\n-function add(a, b) {\n+function add(a: number, b: number): number {\n   return a + b;\n }"
        }
      ],
      "locations": [{ "path": "/workspace/src/index.ts" }]
    }
  }
}
5.3.4. 计划更新(PlanUpdate)

Agent 的任务执行计划。

{
  "method": "session/update",
  "params": {
    "sessionId": "sess-123",
    "update": {
      "sessionUpdate": "plan",
      "entries": [
        {
          "content": "分析现有代码结构",
          "status": "completed"
        },
        {
          "content": "识别类型错误位置",
          "status": "in_progress"
        },
        {
          "content": "修复类型定义",
          "status": "pending",
          "priority": "high"
        },
        {
          "content": "运行 TypeScript 编译检查",
          "status": "pending"
        }
      ]
    }
  }
}

5.4 权限请求机制

ACP 的一个重要安全特性是权限请求机制。Agent 在执行敏感操作前必须获得用户许可。

sequenceDiagram
    participant Agent
    participant Editor
    participant User

    Agent->>Editor: session/request_permission
    Note right of Agent: {toolCall, options}

    Editor->>User: 显示对话框
    Note right of Editor: 用户选择:<br/>• 仅此一次允许<br/>• 始终允许<br/>• 拒绝

    User-->>Editor: 选择权限选项
    Editor-->>Agent: permission response
    Note left of Editor: {optionId: "allow_once"}

权限请求消息格式:

interface AcpPermissionRequest {
  sessionId: string;
  options: Array<{
    optionId: string;
    name: string;
    kind: 'allow_once' | 'allow_always' | 'reject_once' | 'reject_always';
  }>;
  toolCall: {
    toolCallId: string;
    title: string;
    kind: 'read' | 'edit' | 'execute';
    content?: Array<any>;
    locations?: Array<{ path: string }>;
  };
}

示例:请求写入文件权限

{
  "method": "session/request_permission",
  "params": {
    "sessionId": "sess-123",
    "options": [
      {
        "optionId": "allow_once",
        "name": "仅此一次允许",
        "kind": "allow_once"
      },
      {
        "optionId": "allow_always",
        "name": "始终允许对此文件的写入",
        "kind": "allow_always"
      },
      {
        "optionId": "reject",
        "name": "拒绝",
        "kind": "reject_once"
      }
    ],
    "toolCall": {
      "toolCallId": "tool-003",
      "title": "写入文件 src/config.ts",
      "kind": "edit",
      "locations": [{ "path": "/workspace/src/config.ts" }],
      "content": [
        {
          "type": "diff",
          "diff": "... (修改内容) ..."
        }
      ]
    }
  }
}

AionUi 中的权限 UI 实现:

// src/renderer/messages/acp/MessageAcpPermission.tsx

const MessageAcpPermission: React.FC<Props> = ({ message }) => {
  const handleConfirm = async (optionId: string) => {
    // 调用 IPC Bridge 确认权限
    await ipcBridge.acpConversation.confirmMessage.invoke({
      confirmKey: message.confirmKey,
      msg_id: message.msg_id,
      conversation_id: message.conversation_id,
      callId: message.toolCall.toolCallId,
    });
  };

  return (
    <div className="permission-dialog">
      <h3>{message.toolCall.title}</h3>
      <div className="options">
        {message.options.map(option => (
          <button key={option.optionId} onClick={() => handleConfirm(option.optionId)}>
            {option.name}
          </button>
        ))}
      </div>
    </div>
  );
};

六、ACP 实战:从代码看实现

让我们通过 AionUi 项目的实际代码,深入理解 ACP 的实现细节。

6.1 AcpConnection 类:协议通信层

这是 ACP 客户端的核心实现,负责与 Agent 进程的通信。

完整实现流程:

// src/agent/acp/AcpConnection.ts (605 行)

export class AcpConnection {
  private agentProcess: ChildProcess | null = null;
  private pendingRequests: Map<number, PendingRequest> = new Map();
  private requestIdCounter = 0;

  // 事件回调
  public onSessionUpdate?: (data: AcpSessionUpdate) => void;
  public onPermissionRequest?: (data: AcpPermissionRequest) => Promise<{ optionId: string }>;
  public onEndTurn?: () => void;
  public onFileOperation?: (operation: any) => void;

  /**
   * 连接到 Agent
   */
  async connect(backend: AcpBackend, cliPath?: string, workingDir?: string) {
    const command = cliPath || this.getDefaultCliPath(backend);

    // 启动 Agent 子进程
    this.agentProcess = spawn(command, [], {
      cwd: workingDir,
      env: process.env,
      stdio: ['pipe', 'pipe', 'pipe'], // stdin, stdout, stderr
    });

    // 监听输出
    this.agentProcess.stdout.on('data', this.handleStdout.bind(this));
    this.agentProcess.stderr.on('data', this.handleStderr.bind(this));

    // 初始化协议
    await this.initialize();
  }

  /**
   * 发送 JSON-RPC 请求
   */
  private async sendRequest(method: string, params: any, timeout = 60000): Promise<AcpResponse> {
    const id = ++this.requestIdCounter;

    const request: AcpRequest = {
      jsonrpc: JSONRPC_VERSION,
      id,
      method,
      params,
    };

    // 创建 Promise,等待响应
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        this.pendingRequests.delete(id);
        reject(new Error(`Request ${method} timeout after ${timeout}ms`));
      }, timeout);

      this.pendingRequests.set(id, { resolve, reject, timer });

      // 写入 Agent 的 stdin
      const message = JSON.stringify(request) + '\n';
      this.agentProcess.stdin.write(message);
    });
  }

  /**
   * 处理 Agent 的输出
   */
  private handleStdout(data: Buffer) {
    const lines = data
      .toString()
      .split('\n')
      .filter((line) => line.trim());

    for (const line of lines) {
      try {
        const message = JSON.parse(line);

        if ('id' in message && 'result' in message) {
          // Response: 匹配请求并 resolve
          const pending = this.pendingRequests.get(message.id);
          if (pending) {
            clearTimeout(pending.timer);
            pending.resolve(message);
            this.pendingRequests.delete(message.id);
          }
        } else if ('method' in message) {
          // Notification: 触发回调
          this.handleNotification(message);
        }
      } catch (error) {
        console.error('Failed to parse message:', line, error);
      }
    }
  }

  /**
   * 处理通知消息
   */
  private handleNotification(notification: AcpNotification) {
    const { method, params } = notification;

    switch (method) {
      case 'session/update':
        if (this.onSessionUpdate) {
          this.onSessionUpdate(params.update);
        }
        break;

      case 'session/request_permission':
        if (this.onPermissionRequest) {
          this.handlePermissionRequest(params);
        }
        break;

      case 'end_turn':
        if (this.onEndTurn) {
          this.onEndTurn();
        }
        break;

      case 'fs/read_text_file':
        this.handleReadOperation(params);
        break;

      case 'fs/write_text_file':
        this.handleWriteOperation(params);
        break;
    }
  }

  /**
   * 创建新会话
   */
  async newSession(cwd: string): Promise<AcpResponse> {
    return await this.sendRequest(
      'session/new',
      {
        cwd,
        mcpServers: [], // 可配置 MCP 服务器
      },
      120000
    ); // 120 秒超时
  }

  /**
   * 发送用户消息
   */
  async sendPrompt(prompt: string): Promise<AcpResponse> {
    return await this.sendRequest(
      'session/prompt',
      {
        prompt,
      },
      120000
    );
  }

  /**
   * 断开连接
   */
  async disconnect() {
    if (this.agentProcess) {
      this.agentProcess.kill();
      this.agentProcess = null;
    }
    this.pendingRequests.clear();
  }
}

6.2 AcpAgent 类:业务逻辑层

// src/agent/acp/index.ts (607 行)

export class AcpAgent {
  private connection: AcpConnection;
  private sessionId: string | null = null;
  private onStreamEvent: (event: any) => void;

  constructor(options: { id: string; backend: AcpBackend; cliPath?: string; workingDir?: string; onStreamEvent: (event: any) => void }) {
    this.onStreamEvent = options.onStreamEvent;

    // 创建连接
    this.connection = new AcpConnection();

    // 注册回调
    this.connection.onSessionUpdate = this.handleSessionUpdate.bind(this);
    this.connection.onPermissionRequest = this.handlePermissionRequest.bind(this);
    this.connection.onEndTurn = this.handleEndTurn.bind(this);
    this.connection.onFileOperation = this.handleFileOperation.bind(this);
  }

  /**
   * 启动 Agent
   */
  async start() {
    await this.connection.connect(this.backend, this.cliPath, this.workingDir);

    // 创建会话
    const response = await this.connection.newSession(this.workingDir);
    this.sessionId = response.result.sessionId;
  }

  /**
   * 发送消息
   */
  async sendMessage(data: { content: string; files?: string[]; msg_id?: string }): Promise<AcpResult> {
    try {
      await this.connection.sendPrompt(data.content);
      return { success: true };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }

  /**
   * 处理会话更新
   */
  private handleSessionUpdate(update: AcpSessionUpdate) {
    // 使用适配器转换为统一格式
    const event = AcpAdapter.convertUpdate(update);

    // 触发流事件
    this.onStreamEvent(event);
  }

  /**
   * 处理权限请求
   */
  private async handlePermissionRequest(params: AcpPermissionRequest): Promise<{ optionId: string }> {
    // 创建权限消息,显示给用户
    const permissionMessage = {
      role: 'permission_request',
      options: params.options,
      toolCall: params.toolCall,
      confirmKey: generateConfirmKey(),
    };

    this.onStreamEvent(permissionMessage);

    // 等待用户响应(通过 confirmMessage 方法)
    return new Promise((resolve) => {
      this.pendingPermissionResolve = resolve;
    });
  }

  /**
   * 确认权限(用户选择后调用)
   */
  async confirmMessage(data: { confirmKey: string; msg_id: string; callId: string }) {
    // 将用户选择的 optionId 返回给 Agent
    if (this.pendingPermissionResolve) {
      this.pendingPermissionResolve({ optionId: data.confirmKey });
      this.pendingPermissionResolve = null;
    }
  }
}

6.3 前端 UI 集成

聊天界面:

// src/renderer/pages/conversation/acp/AcpChat.tsx

const AcpChat: React.FC<{
  conversation_id: string;
  workspace?: string;
  backend: AcpBackend;
}> = ({ conversation_id, workspace, backend }) => {
  return (
    <ConversationProvider value={{
      conversationId: conversation_id,
      workspace,
      type: 'acp',
    }}>
      {/* 消息列表 */}
      <MessageList />

      {/* 输入框 */}
      <AcpSendBox conversation_id={conversation_id} backend={backend} />
    </ConversationProvider>
  );
};

工具调用 UI:

// src/renderer/messages/acp/MessageAcpToolCall.tsx

const MessageAcpToolCall: React.FC<{ toolCall: ToolCallUpdate }> = ({ toolCall }) => {
  return (
    <div className="tool-call">
      {/* 工具图标 */}
      <div className="tool-icon">
        {toolCall.kind === 'read' && <FileIcon />}
        {toolCall.kind === 'edit' && <EditIcon />}
        {toolCall.kind === 'execute' && <TerminalIcon />}
      </div>

      {/* 工具标题 */}
      <div className="tool-title">{toolCall.title}</div>

      {/* 状态指示器 */}
      <div className={`tool-status tool-status-${toolCall.status}`}>
        {toolCall.status === 'pending' && <ClockIcon />}
        {toolCall.status === 'in_progress' && <SpinnerIcon />}
        {toolCall.status === 'completed' && <CheckIcon />}
        {toolCall.status === 'failed' && <XIcon />}
      </div>

      {/* 文件路径 */}
      {toolCall.locations && (
        <div className="tool-locations">
          {toolCall.locations.map(loc => (
            <span key={loc.path}>{loc.path}</span>
          ))}
        </div>
      )}

      {/* Diff 内容 */}
      {toolCall.content && toolCall.content[0]?.type === 'diff' && (
        <DiffViewer diff={toolCall.content[0].diff} />
      )}
    </div>
  );
};

6.4 IPC Bridge 集成

// src/process/bridge/acpConversationBridge.ts

export function initAcpConversationBridge() {
  // 确认权限
  ipcBridge.acpConversation.confirmMessage.provider(async ({ confirmKey, msg_id, conversation_id, callId }) => {
    const task = WorkerManage.getTaskById(conversation_id) as AcpAgentManager;
    await task.confirmMessage({ confirmKey, msg_id, callId });
    return { success: true };
  });

  // 检测可用的 Agent
  ipcBridge.acpConversation.getAvailableAgents.provider(async () => {
    const agents = acpDetector.getDetectedAgents();
    return { success: true, data: agents };
  });

  // 检测 CLI 路径
  ipcBridge.acpConversation.detectCliPath.provider(async ({ backend }) => {
    const agents = acpDetector.getDetectedAgents();
    const agent = agents.find((a) => a.backend === backend);
    return {
      success: !!agent?.cliPath,
      data: { path: agent?.cliPath },
    };
  });
}

七、ACP vs MCP:两个协议的对比与互补

7.1 核心区别

graph TB
    subgraph ACP["ACP (Agent Client Protocol)"]
        A1["作用:编辑器 ←→ AI Agent"]
        A2["场景:代码编辑、重构、调试"]
        A3["通信:双向(请求/响应 + 通知)"]
        A4["传输:stdio (本地进程通信)"]
        A5["创建者:Zed Industries"]
    end

    subgraph MCP["MCP (Model Context Protocol)"]
        M1["作用:AI 模型 ←→ 工具/资源"]
        M2["场景:数据库查询、API调用、文件系统操作"]
        M3["通信:主要是单向(模型调用工具)"]
        M4["传输:stdio / HTTP with SSE"]
        M5["创建者:Anthropic"]
    end

    style ACP fill:#e3f2fd
    style MCP fill:#fff3e0

7.2 详细对比表

维度ACPMCP
完整名称Agent Client ProtocolModel Context Protocol
主要用途编辑器与 AI 编码助手的通信AI 模型与外部工具/资源的通信
通信方向双向(编辑器 ↔ Agent)主要单向(Model → Tools)
协议基础JSON-RPC 2.0 over stdioJSON-RPC 2.0 over stdio/HTTP
传输方式stdio(标准输入输出)stdio / HTTP with SSE
生命周期编辑器启动 Agent 子进程Host 启动 MCP Server
状态管理有状态(会话持久化)有状态(连接生命周期)
权限控制内置权限请求机制依赖 Host 实现
典型场景代码生成、重构、调试数据库查询、API 调用
作者Zed IndustriesAnthropic
发布时间2025 年2024 年
开源协议Apache 2.0MIT

7.3 协作关系

ACP 和 MCP 不是竞争关系,而是互补关系

graph TD
    Editor["Editor<br/>(Zed)"]
    Agent["AI Agent<br/>(Claude Code)"]

    subgraph MCPServers["MCP Servers"]
        DB[Database Server]
        FS[File System]
        Git[Git Server]
        Web[Web Fetch]
        Mem[Memory/Knowledge]
    end

    Editor <-->|ACP Protocol| Agent
    Agent -->|MCP Protocol| MCPServers

    style Editor fill:#e3f2fd
    style Agent fill:#fff3e0
    style MCPServers fill:#e8f5e9

实际工作流程:

  1. 用户 在 Zed 编辑器中输入:"帮我重构这个函数,并将结果保存到数据库"
  2. Zed 通过 ACP 将消息发送给 Claude Code Agent
  3. Claude 分析代码,生成重构后的代码
  4. Claude 通过 MCP 调用 Database Server 将结果保存
  5. Claude 通过 ACP 将结果返回给 Zed
  6. Zed 在编辑器中显示重构后的代码和执行结果

7.4 在 AionUi 中的集成

AionUi 项目同时支持 ACP 和 MCP:

// src/agent/acp/AcpConnection.ts

async newSession(cwd: string): Promise<AcpResponse> {
  return await this.sendRequest('session/new', {
    cwd,
    // 可以在创建 ACP 会话时配置 MCP 服务器
    mcpServers: [
      {
        name: 'database',
        command: 'node',
        args: ['./mcp-servers/database-server.js'],
      },
      {
        name: 'git',
        command: 'node',
        args: ['./mcp-servers/git-server.js'],
      },
    ],
  });
}

这样,Claude Code Agent 就可以同时:

  • 通过 ACP 与编辑器通信
  • 通过 MCP 访问数据库、Git 等工具

八、生态系统与实际应用

8.1 支持 ACP 的编辑器

mindmap
  root((ACP 编辑器生态))
    已支持
      Zed
        官方支持
        原生集成
      Neovim
        社区插件
      JetBrains IDEs
        官方合作
      Marimo
        数据科学笔记本
    计划中
      VS Code
      Emacs
        社区开发中

Zed 的 ACP 配置示例:

// ~/.config/zed/settings.json
{
  "agents": {
    "claude": {
      "command": "claude-code",
      "args": [],
      "env": {
        "ANTHROPIC_API_KEY": "sk-ant-..."
      }
    },
    "gemini": {
      "command": "gemini-cli",
      "args": ["--model", "gemini-1.5-pro"]
    },
    "codex": {
      "command": "codex",
      "args": ["--api-key", "sk-..."]
    }
  }
}

8.2 支持 ACP 的 AI Agent

Agent厂商模型特性
Claude CodeAnthropicClaude 3.5 Sonnet长上下文、思维链、代码理解强
Gemini CLIGoogleGemini 1.5 Pro多模态、快速响应
Codex CLIOpenAIGPT-4广泛的编程语言支持
Qwen Code阿里云Qwen Coder中文编程、本地化
gooseBlock多模型支持开源、可定制

8.3 实际应用场景

场景 1:代码重构

用户输入:

"将这个组件从 Class 组件重构为 Function 组件,使用 Hooks"

ACP 工作流程:

1. [Agent Message] "我会帮你重构这个组件..."

2. [Tool Call - Read]
   读取 src/components/UserList.tsx

3. [Agent Thought]
   "这是一个 Class 组件,有三个生命周期方法和一个状态..."

4. [Tool Call - Edit]
   生成重构后的代码(使用 useState、useEffect)

5. [Permission Request]
   "是否允许修改 UserList.tsx?"

6. [User] 点击"允许"

7. [Tool Call - Execute]
   运行 `npm run lint` 检查语法

8. [Agent Message]
   "重构完成!代码已通过 lint 检查。"
场景 2:Bug 修复

用户输入:

"修复这个 TypeScript 类型错误"

ACP 工作流程:

1. [Tool Call - Read]
   读取当前文件

2. [Agent Thought]
   "类型错误是因为函数返回值类型不匹配..."

3. [Plan Update]
   - [✓] 分析类型错误
   - [→] 修改函数签名
   - [ ] 添加类型注解
   - [ ] 运行 tsc 检查

4. [Tool Call - Edit]
   修改函数类型定义

5. [Tool Call - Execute]
   运行 `tsc --noEmit`

6. [Agent Message]
   "类型错误已修复!TypeScript 编译通过。"
场景 3:集成 MCP 的复杂场景

用户输入:

"从数据库中查询用户列表,生成一个 React 表格组件"

ACP + MCP 协作流程:

1. [ACP] Agent 收到请求

2. [MCP] Agent 调用 Database Server
   Tool: query_users()

3. [MCP] Database Server 返回数据
   Result: [{id: 1, name: "Alice"}, ...]

4. [ACP] Agent 思考如何生成组件

5. [ACP] Agent 创建新文件
   Tool Call: write_file("UserTable.tsx")

6. [ACP] Agent 生成组件代码
   基于数据库结构生成 TypeScript 类型

7. [ACP] Agent 运行测试
   Tool Call: execute("npm test UserTable")

8. [ACP] Agent 返回结果
   "组件已创建,所有测试通过!"

九、最佳实践与安全考量

9.1 实现 ACP Agent 的最佳实践

9.1.1. 日志处理

❌ 错误做法:

// 不要写入 stdout!
console.log('Agent is processing...');

✅ 正确做法:

// 使用 stderr 或文件日志
import fs from 'fs';

const logFile = fs.createWriteStream('/tmp/agent.log');

function log(message: string) {
  logFile.write(`[${new Date().toISOString()}] ${message}\n`);
}

log('Agent is processing...');

原因: ACP 使用 stdout 传输 JSON-RPC 消息,任何非 JSON 输出都会破坏协议。

9.1.2. 错误处理
try {
  await executeToolCall(toolCall);
} catch (error) {
  // 返回标准错误格式
  return {
    jsonrpc: '2.0',
    id: requestId,
    error: {
      code: -32603,
      message: error.message,
      data: {
        stack: error.stack,
      },
    },
  };
}
9.1.3. 超时管理
const TOOL_CALL_TIMEOUT = 30000; // 30 秒

async function executeToolCallWithTimeout(toolCall: ToolCall) {
  return Promise.race([executeToolCall(toolCall), new Promise((_, reject) => setTimeout(() => reject(new Error('Tool call timeout')), TOOL_CALL_TIMEOUT))]);
}
9.1.4. 流式响应

对于长时间的操作,使用流式更新:

async function generateCode(prompt: string) {
  // 发送进度更新
  sendNotification('session/update', {
    update: {
      sessionUpdate: 'agent_thought_chunk',
      content: { type: 'text', text: '正在分析需求...' },
    },
  });

  // 生成代码
  const code = await llm.generate(prompt);

  // 发送代码块
  sendNotification('session/update', {
    update: {
      sessionUpdate: 'agent_message_chunk',
      content: { type: 'text', text: code },
    },
  });
}

9.2 安全考量

9.2.1. 文件系统访问控制
// 定义允许的工作目录
const ALLOWED_WORKSPACE = process.env.WORKSPACE_DIR;

function validateFilePath(path: string): boolean {
  const resolvedPath = path.resolve(path);

  // 检查路径是否在允许的工作目录内
  if (!resolvedPath.startsWith(ALLOWED_WORKSPACE)) {
    throw new Error('Access denied: path outside workspace');
  }

  // 检查是否访问敏感文件
  const sensitivePatterns = ['.env', '.git/config', 'id_rsa'];
  if (sensitivePatterns.some((pattern) => resolvedPath.includes(pattern))) {
    throw new Error('Access denied: sensitive file');
  }

  return true;
}
9.2.2. 命令执行安全
// 白名单机制
const ALLOWED_COMMANDS = ['npm test', 'npm run lint', 'tsc --noEmit', 'git status'];

function validateCommand(command: string): boolean {
  return ALLOWED_COMMANDS.some((allowed) => command.startsWith(allowed));
}

async function executeCommand(command: string) {
  if (!validateCommand(command)) {
    throw new Error('Command not allowed');
  }

  // 执行命令
  return execAsync(command, {
    timeout: 30000,
    cwd: WORKSPACE_DIR,
  });
}
9.2.3. 权限请求实现
async function requestPermission(toolCall: ToolCall): Promise<boolean> {
  // 发送权限请求
  sendNotification('session/request_permission', {
    sessionId: currentSessionId,
    options: [
      { optionId: 'allow_once', name: '仅此一次允许', kind: 'allow_once' },
      { optionId: 'allow_always', name: '始终允许', kind: 'allow_always' },
      { optionId: 'reject', name: '拒绝', kind: 'reject_once' },
    ],
    toolCall,
  });

  // 等待用户响应
  const response = await waitForPermissionResponse();

  // 缓存权限决策
  if (response.kind === 'allow_always') {
    permissionCache.set(toolCall.kind, true);
  } else if (response.kind === 'reject_always') {
    permissionCache.set(toolCall.kind, false);
  }

  return response.kind.startsWith('allow');
}
9.2.4. 敏感信息处理
// 过滤敏感信息
function sanitizeContent(content: string): string {
  // 移除 API 密钥
  content = content.replace(/sk-[a-zA-Z0-9]{48}/g, '***API_KEY***');

  // 移除密码
  content = content.replace(/password\s*=\s*['"][^'"]+['"]/gi, 'password=***');

  // 移除 Token
  content = content.replace(/Bearer\s+[a-zA-Z0-9._-]+/g, 'Bearer ***');

  return content;
}

9.3 性能优化

9.3.1. 批量操作
// 批量读取文件
async function readMultipleFiles(paths: string[]): Promise<Map<string, string>> {
  const results = new Map();

  await Promise.all(
    paths.map(async (path) => {
      const content = await fs.readFile(path, 'utf-8');
      results.set(path, content);
    })
  );

  return results;
}
9.3.2. 增量更新
// 只发送变化的内容
let lastContent = '';

function sendDiffUpdate(newContent: string) {
  const diff = computeDiff(lastContent, newContent);

  if (diff) {
    sendNotification('session/update', {
      update: {
        sessionUpdate: 'agent_message_chunk',
        content: { type: 'diff', diff },
      },
    });
  }

  lastContent = newContent;
}
9.3.3. 缓存机制
// 缓存文件内容
const fileCache = new LRU<string, string>({
  max: 100,
  maxAge: 5 * 60 * 1000, // 5 分钟
});

async function readFileWithCache(path: string): Promise<string> {
  const cached = fileCache.get(path);
  if (cached) return cached;

  const content = await fs.readFile(path, 'utf-8');
  fileCache.set(path, content);

  return content;
}

十、未来展望

10.1 协议演进方向

10.1.1. 更丰富的内容类型
// 未来可能支持的内容类型
interface FutureContent {
  type: 'text' | 'image' | 'video' | 'audio' | 'diagram' | '3d-model';
  // ...
}

// 示例:Agent 生成架构图
{
  sessionUpdate: 'agent_message_chunk',
  content: {
    type: 'diagram',
    format: 'mermaid',
    data: `
      graph TD
        A[Client] -->|HTTP| B[Server]
        B --> C[Database]
    `
  }
}
10.1.2. 多 Agent 协作
graph TD
    Editor[Editor]

    CodeAgent[Code Agent<br/>负责代码生成]
    TestAgent[Test Agent<br/>负责测试]
    ReviewAgent[Review Agent<br/>负责代码审查]
    DeployAgent[Deploy Agent<br/>负责部署]

    Editor --> CodeAgent
    Editor --> TestAgent
    Editor --> ReviewAgent
    Editor --> DeployAgent

    CodeAgent <-.协作.-> TestAgent
    TestAgent <-.协作.-> ReviewAgent
    ReviewAgent <-.协作.-> DeployAgent

    style Editor fill:#e3f2fd
    style CodeAgent fill:#fff3e0
    style TestAgent fill:#e8f5e9
    style ReviewAgent fill:#f3e5f5
    style DeployAgent fill:#fce4ec

    Note["Agent 之间可以互相通信和协作"]
    style Note fill:#fffde7
10.1.3. 增强的上下文管理
// 未来的会话上下文
interface EnhancedSessionContext {
  // 项目元数据
  project: {
    name: string;
    language: string[];
    framework: string[];
    dependencies: Record<string, string>;
  };

  // 代码图谱
  codeGraph: {
    files: FileNode[];
    imports: ImportEdge[];
    exports: ExportEdge[];
  };

  // 历史操作
  history: Operation[];

  // 用户偏好
  preferences: {
    codingStyle: string;
    testFramework: string;
    // ...
  };
}

10.2 生态系统建设

10.2.1. ACP Agent 市场
mindmap
  root((ACP Agent 市场))
    Agent类型
      官方Agent
        Claude
        Gemini
        Codex
      社区Agent
        开源
        定制化
      企业Agent
        私有部署
        定制模型
    用户功能
      浏览和搜索Agent
      查看评分和评论
      一键安装和配置
      分享自己的Agent
10.2.2. 标准化的 Agent 能力声明
// agent-manifest.json
{
  "name": "my-custom-agent",
  "version": "1.0.0",
  "description": "A custom coding agent for Rust projects",
  "author": "Your Name",
  "capabilities": {
    "languages": ["rust", "toml"],
    "frameworks": ["tokio", "actix"],
    "tools": {
      "code_generation": true,
      "refactoring": true,
      "testing": true,
      "debugging": false
    }
  },
  "requirements": {
    "model": "gpt-4",
    "apiKey": "required",
    "minEditorVersion": "0.158.0"
  }
}
10.2.3. 跨编辑器同步
// 未来的跨编辑器配置同步
interface AgentConfig {
  agents: {
    [name: string]: {
      command: string;
      args: string[];
      env: Record<string, string>;
      settings: any;
    };
  };
  preferences: {
    defaultAgent: string;
    autoStart: boolean;
    // ...
  };
}

// 同步到云端
await syncConfigToCloud(config);

// 在另一台设备上
const config = await loadConfigFromCloud();

10.3 与其他标准的集成

10.3.1. 与 LSP 的深度集成
graph TD
    Editor[Editor]

    subgraph LSP["LSP (提供语言智能)"]
        L1[自动补全]
        L2[类型检查]
        L3[重构建议]
    end

    subgraph ACP["ACP (提供 AI 能力)"]
        A1[理解 LSP 的诊断信息]
        A2[生成符合项目规范的代码]
        A3[自动修复 LSP 报告的错误]
    end

    Editor --> LSP
    Editor --> ACP
    ACP -.读取诊断.-> LSP

    style Editor fill:#e3f2fd
    style LSP fill:#fff3e0
    style ACP fill:#e8f5e9
10.3.2. 与 Debug Adapter Protocol (DAP) 集成
// Agent 可以理解调试信息
{
  sessionUpdate: 'debug_analysis',
  breakpoint: {
    file: 'src/index.ts',
    line: 42,
    variables: {
      user: { id: 123, name: 'Alice' },
      error: new Error('Invalid token')
    }
  },
  suggestion: "错误是因为 token 已过期,建议添加 token 刷新逻辑"
}

10.4 AI 原生编程范式

ACP 正在推动一种新的编程范式:AI 原生编程(AI-Native Programming)

graph LR
    subgraph Traditional["传统编程"]
        T1[人类] --> T2[手写代码] --> T3[编译器] --> T4[可执行程序]
    end

    subgraph Current["AI 辅助编程(现在)"]
        C1[人类] --> C2[AI 生成代码片段] --> C3[人类修改] --> C4[编译器] --> C5[可执行程序]
    end

    subgraph Future["AI 原生编程(未来)"]
        F1[人类描述需求] --> F2[AI Agent 理解] --> F3[AI 设计架构]
        F3 --> F4[AI 实现] --> F5[AI 测试] --> F6[AI 部署]
        F6 -.人类审查和指导.-> F1
    end

    style Traditional fill:#ffebee
    style Current fill:#fff3e0
    style Future fill:#e8f5e9

关键特征:

  1. 声明式编程:人类只需描述"要什么",而不是"怎么做"
  2. 持续对话:编程变成与 AI 的持续对话过程
  3. 多层抽象:从需求 → 架构 → 实现 → 优化,每层都有 AI 辅助
  4. 自动化流程:测试、部署、监控都由 AI 自动化

十一、总结

11.1 ACP 的核心价值

标准化:统一的协议规范,消除编辑器与 Agent 的互操作壁垒

开放性:开源协议,任何人都可以实现和扩展

隐私优先:本地通信,不经过第三方服务器

可组合性:与 MCP 等协议协同工作,构建完整的 AI 生态

易用性:基于成熟的 JSON-RPC 2.0,简单高效

11.2 适用场景

  • 代码编辑器开发者:希望集成多个 AI 编码助手
  • AI Agent 开发者:希望让自己的 Agent 被更多编辑器支持
  • 企业开发团队:需要在统一的编辑器环境中使用不同的 AI 工具
  • 开源社区:构建开放、协作的 AI 辅助编程生态

11.3 开始使用 ACP

11.3.1. 作为编辑器开发者
# 安装 ACP SDK
npm install @agentclientprotocol/sdk

# 参考 Zed 的实现
git clone https://github.com/zed-industries/zed
11.3.2. 作为 Agent 开发者
# 选择你喜欢的语言 SDK
npm install @agentclientprotocol/sdk       # TypeScript
pip install agent-client-protocol          # Python
cargo add agent-client-protocol            # Rust
11.3.3. 作为用户
# 下载支持 ACP 的编辑器
# Zed
curl https://zed.dev/install.sh | sh

# 配置你喜欢的 Agent
zed --config agents.claude.command="claude-code"

11.4 学习资源


附录:ACP 与 MCP 协议对比速查表

特性ACPMCP
完整名称Agent Client ProtocolModel Context Protocol
主要作用编辑器 ↔ AI AgentAI Model ↔ Tools
协议基础JSON-RPC 2.0JSON-RPC 2.0
传输方式stdiostdio / HTTP+SSE
通信方向双向主要单向
生命周期管理编辑器控制Host 控制
权限控制内置机制依赖 Host
流式响应支持支持
作者Zed IndustriesAnthropic
开源协议Apache 2.0MIT
发布时间20252024
典型场景代码生成、重构数据库、API 调用

关于本文

本文结合了:

  • MCP 官方文档和社区实践
  • ACP 官方规范和 Zed 实现
  • AionUi 项目的实际代码
  • 行业最佳实践和未来趋势

希望能帮助你深入理解 ACP 协议,并在实际项目中应用。

作者:基于掘金文章《MCP 深度解析》、ACP 官方文档、以及 AionUi 开源项目分析,感兴趣可以关注我!

扫码_搜索联合传播样式-白色版.png

相关阅读