Files
qwen-code/packages/vscode-ide-companion/docs-tmp/tmp.md
yiliang114 732220e651 wip(vscode-ide-companion): 实现 quick win 功能
- 将 WebView 调整到编辑器右侧
- 添加 ChatHeader 组件,实现会话下拉菜单
- 替换模态框为紧凑型下拉菜单
- 更新会话切换逻辑,显示当前标题
- 清理旧的会话选择器样式
基于 Claude Code v2.0.43 UI 分析实现。
2025-11-19 00:16:45 +08:00

30 KiB
Raw Blame History

VSCode WebView postMessage 如何与 qwen codex gemini 之类的 cli 进行交互呢

⏺ 非常好的问题!这是整个方案的核心技术细节。让我详细解释 VSCode WebView 如何通过 postMessage 与 CLI 进程交互。

🔄 完整的通信链路

┌─────────────────────────────────────────────────────────────────┐ │ VSCode 窗口 │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ WebView (React UI) │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ 用户输入: "帮我分析这个文件" │ │ │ │ │ │ [Send Button] │ │ │ │ │ └──────────────┬──────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ (1) webview.postMessage() │ │ │ └─────────────────┼──────────────────────────────────────────┘ │ │ │ │ │ ┌─────────────────▼──────────────────────────────────────────┐ │ │ │ Extension Host (Node.js) │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ │ │ (2) onDidReceiveMessage() 接收 │ │ │ │ │ │ ↓ │ │ │ │ │ │ (3) AcpAgentManager.sendMessage() │ │ │ │ │ │ ↓ │ │ │ │ │ │ (4) spawn('qwen', ['--experimental-acp']) │ │ │ │ │ └──────────────┬───────────────────────────────────────┘ │ │ │ └─────────────────┼──────────────────────────────────────────┘ │ └────────────────────┼─────────────────────────────────────────────┘ │ ▼ (5) JSON-RPC via stdio/WebSocket ┌─────────────────────────────┐ │ Qwen CLI 独立进程 │ │ (--experimental-acp) │ │ │ │ 接收: {"method": "send"} │ │ 执行: AI 推理 + 工具调用 │ │ 返回: {"result": "..."} │ └─────────────┬───────────────┘ │ ▼ (6) 响应返回 ┌─────────────────────────────┐ │ Extension Host │ │ 解析响应 → 格式化 │ └─────────────┬───────────────┘ │ ▼ (7) webview.postMessage() ┌─────────────────────────────┐ │ WebView │ │ 显示 AI 的回复 │ └─────────────────────────────┘


💻 详细代码实现

第 1 步WebView 发送消息到 Extension

// webview/components/ChatInput.tsx import { useState } from 'react';

const ChatInput = () => { const [input, setInput] = useState('');

// 获取 VSCode API
const vscode = acquireVsCodeApi();

const handleSend = () => {
  // (1) 通过 postMessage 发送到 Extension Host
  vscode.postMessage({
    type: 'send_message',      // 消息类型
    agentType: 'qwen',         // 使用哪个 Agent
    content: input,            // 用户输入
    files: [],                 // 附带文件
    timestamp: Date.now()
  });

  setInput('');
};

return (
  <div>
    <input value={input} onChange={e => setInput(e.target.value)} />
    <button onClick={handleSend}>Send</button>
  </div>
);

};


第 2 步Extension Host 接收消息

// extension.ts import * as vscode from 'vscode'; import { spawn, ChildProcess } from 'child_process';

class AionUiViewProvider implements vscode.WebviewViewProvider { private webviewView?: vscode.WebviewView; private qwenProcess?: ChildProcess; // Qwen CLI 进程 private codexProcess?: ChildProcess; // Codex CLI 进程 private activeAgent: string = 'qwen';

resolveWebviewView(webviewView: vscode.WebviewView) {
  this.webviewView = webviewView;

  webviewView.webview.options = {
    enableScripts: true,
    localResourceRoots: [this.extensionUri]
  };

  // (2) 监听来自 WebView 的消息
  webviewView.webview.onDidReceiveMessage(async (message) => {
    console.log('[Extension] Received from WebView:', message);

    switch (message.type) {
      case 'send_message':
        await this.handleSendMessage(message);
        break;

      case 'switch_agent':
        await this.handleSwitchAgent(message.agentType);
        break;

      case 'approve_permission':
        await this.handleApprovePermission(message);
        break;
    }
  });

  // 加载 HTML
  webviewView.webview.html = this.getHtmlForWebview(webviewView.webview);
}

// (3) 处理发送消息逻辑
private async handleSendMessage(message: any) {
  const { agentType, content, files } = message;

  // 确保 CLI 进程已启动
  if (!this.qwenProcess && agentType === 'qwen') {
    await this.startQwenCLI();
  }

  // 发送到对应的 CLI
  await this.sendToCLI(agentType, {
    method: 'session/send_prompt',
    params: {
      sessionId: this.currentSessionId,
      content: content
    }
  });
}

}


第 3 步:启动 CLI 进程并建立 ACP 连接

// extension.ts (continued)

class AionUiViewProvider { private acpConnection?: AcpConnection; private currentSessionId?: string;

// (4) 启动 Qwen CLI 进程
private async startQwenCLI(): Promise<void> {
  const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
  const config = vscode.workspace.getConfiguration('aionui');
  const qwenPath = config.get<string>('qwen.cliPath', 'npx @qwen-code/qwen-code');

  // 解析命令和参数
  let command: string;
  let args: string[];

  if (qwenPath.startsWith('npx ')) {
    command = process.platform === 'win32' ? 'npx.cmd' : 'npx';
    args = [...qwenPath.split(' ').slice(1), '--experimental-acp'];
  } else {
    command = qwenPath;
    args = ['--experimental-acp'];
  }

  console.log(`[Extension] Starting Qwen CLI: ${command} ${args.join(' ')}`);

  // 启动进程
  this.qwenProcess = spawn(command, args, {
    cwd: workspaceRoot,
    stdio: ['pipe', 'pipe', 'pipe'],  // stdin, stdout, stderr
    env: {
      ...process.env,
      // 传递必要的环境变量
    }
  });

  // 监听进程输出
  this.qwenProcess.stdout?.on('data', (data) => {
    console.log('[Qwen CLI] stdout:', data.toString());
    this.handleCLIOutput(data);
  });

  this.qwenProcess.stderr?.on('data', (data) => {
    console.error('[Qwen CLI] stderr:', data.toString());
  });

  this.qwenProcess.on('error', (error) => {
    console.error('[Qwen CLI] Process error:', error);
    this.notifyWebView({
      type: 'error',
      message: `Failed to start Qwen CLI: ${error.message}`
    });
  });

  this.qwenProcess.on('exit', (code) => {
    console.log('[Qwen CLI] Process exited with code:', code);
  });

  // 创建 ACP 连接
  this.acpConnection = new AcpConnection(this.qwenProcess);

  // 初始化 ACP 协议
  await this.initializeACP();

  // 创建会话
  await this.createSession(workspaceRoot);
}

// (5) 初始化 ACP 协议JSON-RPC 2.0
private async initializeACP(): Promise<void> {
  console.log('[Extension] Initializing ACP protocol...');

  // 发送 initialize 请求
  const response = await this.acpConnection!.sendRequest({
    jsonrpc: '2.0',
    id: this.nextRequestId++,
    method: 'initialize',
    params: {
      protocolVersion: '0.1.0',
      capabilities: {
        /* 客户端能力 */
      }
    }
  });

  console.log('[Extension] ACP initialized:', response);
}

// (6) 创建会话
private async createSession(workingDir?: string): Promise<void> {
  console.log('[Extension] Creating new session...');

  const response = await this.acpConnection!.sendRequest({
    jsonrpc: '2.0',
    id: this.nextRequestId++,
    method: 'session/new_session',
    params: {
      workingDirectory: workingDir || process.cwd()
    }
  });

  this.currentSessionId = response.result.sessionId;
  console.log('[Extension] Session created:', this.currentSessionId);

  // 通知 WebView 会话已就绪
  this.notifyWebView({
    type: 'session_ready',
    sessionId: this.currentSessionId
  });
}

}


第 4 步ACP 连接实现JSON-RPC 2.0

// backend/acp/AcpConnection.ts import { ChildProcess } from 'child_process'; import { EventEmitter } from 'events';

interface AcpRequest { jsonrpc: '2.0'; id: number; method: string; params?: any; }

interface AcpResponse { jsonrpc: '2.0'; id: number; result?: any; error?: { code: number; message: string; }; }

export class AcpConnection extends EventEmitter { private process: ChildProcess; private pendingRequests = new Map<number, { resolve: (value: any) => void; reject: (error: any) => void; }>(); private buffer = '';

constructor(process: ChildProcess) {
  super();
  this.process = process;

  // 监听 CLI 的输出
  this.process.stdout?.on('data', (data: Buffer) => {
    this.handleData(data);
  });
}

// 处理来自 CLI 的数据
private handleData(data: Buffer) {
  this.buffer += data.toString();

  // 按行分割JSON-RPC 消息通常是一行一个)
  const lines = this.buffer.split('\n');
  this.buffer = lines.pop() || ''; // 保留最后一个不完整的行

  for (const line of lines) {
    if (!line.trim()) continue;

    try {
      const message = JSON.parse(line);
      this.handleMessage(message);
    } catch (error) {
      console.error('[ACP] Failed to parse message:', line, error);
    }
  }
}

// 处理 JSON-RPC 消息
private handleMessage(message: any) {
  console.log('[ACP] Received:', message);

  if (message.id !== undefined) {
    // 这是一个响应
    const pending = this.pendingRequests.get(message.id);
    if (pending) {
      this.pendingRequests.delete(message.id);

      if (message.error) {
        pending.reject(new Error(message.error.message));
      } else {
        pending.resolve(message);
      }
    }
  } else if (message.method) {
    // 这是一个通知Notification
    this.handleNotification(message);
  }
}

// 处理 CLI 发来的通知
private handleNotification(message: any) {
  switch (message.method) {
    case 'session/update':
      // AI 正在输出内容
      this.emit('session_update', message.params);
      break;

    case 'permission/request':
      // AI 请求执行操作的权限
      this.emit('permission_request', message.params);
      break;

    case 'session/end_turn':
      // AI 完成了一轮对话
      this.emit('end_turn', message.params);
      break;
  }
}

// 发送 JSON-RPC 请求
sendRequest(request: AcpRequest): Promise<AcpResponse> {
  return new Promise((resolve, reject) => {
    // 保存待处理的请求
    this.pendingRequests.set(request.id, { resolve, reject });

    // 发送到 CLI 的 stdin
    const json = JSON.stringify(request) + '\n';
    this.process.stdin?.write(json);

    console.log('[ACP] Sent request:', request);

    // 设置超时
    setTimeout(() => {
      if (this.pendingRequests.has(request.id)) {
        this.pendingRequests.delete(request.id);
        reject(new Error('Request timeout'));
      }
    }, 30000); // 30 秒超时
  });
}

// 发送提示词
async sendPrompt(content: string): Promise<void> {
  await this.sendRequest({
    jsonrpc: '2.0',
    id: Date.now(),
    method: 'session/send_prompt',
    params: {
      sessionId: this.sessionId,
      content: content
    }
  });
}

}


第 5 步Extension 监听 CLI 的响应并转发给 WebView

// extension.ts (continued)

class AionUiViewProvider {

private async sendToCLI(agentType: string, request: any) {
  // 监听 CLI 的各种事件
  this.acpConnection!.on('session_update', (data) => {
    // (7) 将 AI 的响应发送回 WebView
    this.notifyWebView({
      type: 'agent_message',
      data: data
    });
  });

  this.acpConnection!.on('permission_request', (data) => {
    // 将权限请求发送到 WebView
    this.notifyWebView({
      type: 'permission_request',
      data: data
    });
  });

  this.acpConnection!.on('end_turn', () => {
    // AI 完成回复
    this.notifyWebView({
      type: 'finish'
    });
  });

  // 发送请求
  await this.acpConnection!.sendRequest(request);
}

// 发送消息到 WebView
private notifyWebView(message: any) {
  if (!this.webviewView) {
    console.warn('[Extension] WebView not ready, dropping message:', message);
    return;
  }

  console.log('[Extension] Sending to WebView:', message);

  // (7) 通过 postMessage 发送到 WebView
  this.webviewView.webview.postMessage(message);
}

// 处理权限批准
private async handleApprovePermission(message: any) {
  const { permissionId, approved } = message;

  // 发送权限响应到 CLI
  await this.acpConnection!.sendRequest({
    jsonrpc: '2.0',
    id: Date.now(),
    method: 'permission/respond',
    params: {
      permissionId: permissionId,
      approved: approved
    }
  });
}

}


第 6 步WebView 接收并显示 AI 响应

// webview/index.tsx import { useEffect, useState } from 'react';

const ChatView = () => { const [messages, setMessages] = useState<any[]>([]); const vscode = acquireVsCodeApi();

useEffect(() => {
  // (8) 监听来自 Extension 的消息
  window.addEventListener('message', (event) => {
    const message = event.data;
    console.log('[WebView] Received from Extension:', message);

    switch (message.type) {
      case 'session_ready':
        console.log('Session ready:', message.sessionId);
        break;

      case 'agent_message':
        // 显示 AI 的回复
        setMessages(prev => [...prev, {
          type: 'assistant',
          content: message.data.content
        }]);
        break;

      case 'permission_request':
        // 显示权限请求对话框
        showPermissionDialog(message.data);
        break;

      case 'finish':
        // AI 完成回复
        console.log('AI finished');
        break;

      case 'error':
        // 显示错误
        alert(message.message);
        break;
    }
  });
}, []);

const showPermissionDialog = (data: any) => {
  const approved = confirm(
    `Qwen 想要执行: ${data.toolCall.title}\n是否允许`
  );

  // 发送批准/拒绝到 Extension
  vscode.postMessage({
    type: 'approve_permission',
    permissionId: data.permissionId,
    approved: approved
  });
};

return (
  <div>
    <MessageList messages={messages} />
    <ChatInput />
  </div>
);

};


📊 完整的消息流示意图

// 用户输入 "帮我分析代码"

┌─────────────────────────────────────────────────────────────┐ │ [WebView] 用户点击发送 │ └─────────────┬───────────────────────────────────────────────┘ │ ▼ vscode.postMessage({type: 'send_message', ...}) ┌─────────────────────────────────────────────────────────────┐ │ [Extension] onDidReceiveMessage() 接收 │ │ → handleSendMessage() │ │ → acpConnection.sendRequest() │ └─────────────┬───────────────────────────────────────────────┘ │ ▼ process.stdin.write(JSON-RPC 请求) ┌─────────────────────────────────────────────────────────────┐ │ [Qwen CLI] --experimental-acp │ │ stdin 接收: {"method": "session/send_prompt", ...} │ │ ↓ │ │ AI 处理: 分析代码、调用工具 │ │ ↓ │ │ stdout 输出: {"method": "session/update", ...} │ └─────────────┬───────────────────────────────────────────────┘ │ ▼ process.stdout.on('data', ...) ┌─────────────────────────────────────────────────────────────┐ │ [Extension] AcpConnection.handleData() │ │ → handleNotification('session_update') │ │ → emit('session_update', data) │ │ → notifyWebView({type: 'agent_message', ...}) │ └─────────────┬───────────────────────────────────────────────┘ │ ▼ webview.postMessage({type: 'agent_message', ...}) ┌─────────────────────────────────────────────────────────────┐ │ [WebView] window.addEventListener('message', ...) │ │ → setMessages([...messages, newMessage]) │ │ → React 重新渲染,显示 AI 回复 │ └─────────────────────────────────────────────────────────────┘


🎯 关键技术点总结

  1. VSCode WebView ↔ Extension 通信

// WebView → Extension vscode.postMessage({ type: 'action', data: {...} });

// Extension → WebView
webview.postMessage({ type: 'response', data: {...} });

  1. Extension ↔ CLI 通信

// Extension → CLI (stdin) process.stdin.write(JSON.stringify(request) + '\n');

// CLI → Extension (stdout) process.stdout.on('data', (data) => { const message = JSON.parse(data.toString()); // 处理消息 });

  1. JSON-RPC 2.0 协议

// 请求 {"jsonrpc": "2.0", "id": 1, "method": "send_prompt", "params": {...}}

// 响应 {"jsonrpc": "2.0", "id": 1, "result": {...}}

// 通知 {"jsonrpc": "2.0", "method": "session/update", "params": {...}}


最终效果

用户在 VSCode 中输入消息 ↓ WebView UI 收集输入 ↓ postMessage 发送到 Extension ↓ Extension 启动 Qwen CLI 进程 ↓ 通过 JSON-RPC 发送到 CLI ↓ Qwen 处理并返回结果 ↓ Extension 解析响应 ↓ postMessage 返回到 WebView ↓ UI 显示 AI 的回复

完全不需要中间服务器,直接在 VSCode 中完成所有通信!

需要我提供完整的可运行示例代码吗?

这样的话 是不是就跟 acp 没有什么关系了?

🤔 非常好的观察!但其实恰恰相反 - 这个方案的核心就是 ACP 协议!

让我澄清一个重要的概念误区:

🔑 关键理解ACP 是什么?

ACP (Agent Communication Protocol) 不是一个独立的服务或中间件,而是一个通信协议标准(基于 JSON-RPC 2.0)。

就像:

  • HTTP 是一个协议,不是软件
  • WebSocket 是一个协议,不是服务
  • ACP 也是一个协议,不是程序

📋 对比说明

误解的架构(你可能以为的):

VSCode Extension ↓ 需要连接到一个 "ACP 服务器" ↓ ACP 服务器再去调用 Qwen CLI

实际的架构(真实情况):

VSCode Extension ↓ 直接使用 ACP 协议 ↓ 与 Qwen CLI 通信Qwen CLI 支持 ACP 协议)


🎯 ACP 在整个流程中的作用

让我用更清晰的方式展示 ACP 的角色:

完整的技术栈:

┌─────────────────────────────────────────────────────────┐ │ 应用层VSCode WebView (React UI) │ │ - 用户界面 │ │ - 消息展示 │ └────────────────┬────────────────────────────────────────┘ │ postMessage (VSCode API) ┌────────────────▼────────────────────────────────────────┐ │ 桥接层VSCode Extension Host │ │ - 接收 UI 事件 │ │ - 管理 CLI 进程生命周期 │ │ - 转换 UI 消息 → ACP 协议消息 │ └────────────────┬────────────────────────────────────────┘ │ │ 🔴 这里使用 ACP 协议! │ (JSON-RPC 2.0 via stdio) │ ┌────────────────▼────────────────────────────────────────┐ │ 协议层ACP 协议 (JSON-RPC 2.0) │ │ - initialize │ │ - session/new_session │ │ - session/send_prompt │ │ - session/update (notification) │ │ - permission/request (notification) │ └────────────────┬────────────────────────────────────────┘ │ stdin/stdout (pipe) ┌────────────────▼────────────────────────────────────────┐ │ CLI 层Qwen CLI (--experimental-acp) │ │ - 实现了 ACP 协议规范 │ │ - 接收 ACP 消息,执行对应操作 │ │ - 发送 ACP 通知回传结果 │ └────────────────┬────────────────────────────────────────┘ │ HTTPS ┌────────────────▼────────────────────────────────────────┐ │ AI 服务Qwen API │ │ - 实际的 AI 模型 │ └─────────────────────────────────────────────────────────┘


💡 ACP 协议的实际作用

没有 ACP 的情况(假设):

// 如果没有 ACP 协议,你需要自己定义所有通信格式

// 启动 CLI const qwen = spawn('qwen');

// 自己发明一个消息格式? qwen.stdin.write('SEND: 帮我分析代码\n'); // ??? 什么格式?

// 怎么接收响应? qwen.stdout.on('data', (data) => { // ??? 如何解析? // ??? 如何区分是 AI 回复还是错误信息? // ??? 如何处理权限请求? });

有 ACP 协议的情况(实际):

// 使用 ACP 协议,所有格式都是标准化的

// 启动支持 ACP 的 CLI const qwen = spawn('qwen', ['--experimental-acp']);

// 使用标准的 JSON-RPC 2.0 格式 qwen.stdin.write(JSON.stringify({ jsonrpc: '2.0', // 标准协议版本 id: 1, // 请求 ID method: 'session/send_prompt', // 标准方法名 params: { sessionId: 'xxx', content: '帮我分析代码' } }) + '\n');

// 接收标准格式的响应 qwen.stdout.on('data', (data) => { const message = JSON.parse(data);

// ✅ 标准的响应格式
if (message.id === 1) {
  // 这是对请求 1 的响应
  console.log(message.result);
}

// ✅ 标准的通知格式
if (message.method === 'session/update') {
  // AI 正在输出内容
  console.log(message.params.content);
}

// ✅ 标准的权限请求
if (message.method === 'permission/request') {
  // AI 需要权限
  console.log(message.params.toolCall);
}

});


📖 ACP 协议的具体内容

ACP 定义的标准方法:

// 1. 初始化 { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "0.1.0", "capabilities": {} } }

// 2. 创建会话 { "jsonrpc": "2.0", "id": 2, "method": "session/new_session", "params": { "workingDirectory": "/path/to/project" } }

// 3. 发送提示词 { "jsonrpc": "2.0", "id": 3, "method": "session/send_prompt", "params": { "sessionId": "session-123", "content": "帮我分析代码" } }

// 4. 会话更新CLI → Extension 的通知) { "jsonrpc": "2.0", "method": "session/update", "params": { "sessionId": "session-123", "update": { "sessionUpdate": "agent_message_chunk", "content": { "type": "text", "text": "好的,我来分析..." } } } }

// 5. 权限请求CLI → Extension 的通知) { "jsonrpc": "2.0", "method": "permission/request", "params": { "sessionId": "session-123", "permissionId": "perm-456", "toolCall": { "toolCallId": "tool-789", "title": "Read file: config.json", "kind": "read" }, "options": [ {"optionId": "allow", "name": "Allow", "kind": "allow_once"}, {"optionId": "reject", "name": "Reject", "kind": "reject_once"} ] } }

这些都是 ACP 协议规范的一部分!


🔍 AionUi 中 ACP 的使用证据

让我展示 AionUi 代码中实际使用 ACP 的部分:

  1. ACP 协议类型定义 (src/types/acpTypes.ts)

// ACP JSON-RPC Protocol Types export const JSONRPC_VERSION = '2.0' as const;

export interface AcpRequest { jsonrpc: typeof JSONRPC_VERSION; // ← ACP 协议标准 id: number; method: string; // ← ACP 定义的方法 params?: any; }

export interface AcpNotification { jsonrpc: typeof JSONRPC_VERSION; // ← ACP 协议标准 method: string; // ← ACP 定义的通知类型 params?: any; }

  1. ACP 连接实现 (src/agent/acp/AcpConnection.ts)

// 启动 ACP CLI async connect(backend: AcpBackend, cliPath?: string, workspace?: string) { // 启动支持 ACP 的 CLI this.process = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'] });

// 发送 ACP 初始化请求
const initResponse = await this.sendRequest({
  jsonrpc: '2.0',           // ← ACP 协议
  id: this.nextId++,
  method: 'initialize',     // ← ACP 方法
  params: { /* ... */ }
});

}

// 创建 ACP 会话 async newSession(workingDirectory?: string) { const response = await this.sendRequest({ jsonrpc: '2.0', // ← ACP 协议 id: this.nextId++, method: 'session/new_session', // ← ACP 方法 params: { workingDirectory } }); }

// 发送 ACP 提示词 async sendPrompt(content: string) { await this.sendRequest({ jsonrpc: '2.0', // ← ACP 协议 id: this.nextId++, method: 'session/send_prompt', // ← ACP 方法 params: { sessionId: this.sessionId, content: content } }); }

  1. 处理 ACP 通知 (src/agent/acp/AcpConnection.ts)

private handleMessage(message: string) { const data = JSON.parse(message);

// 根据 ACP 协议处理不同类型的消息
if (data.method === 'session/update') {
  // ← ACP 定义的通知类型
  this.onSessionUpdate?.(data.params);
}

if (data.method === 'permission/request') {
  // ← ACP 定义的通知类型
  this.onPermissionRequest?.(data.params);
}

if (data.method === 'session/end_turn') {
  // ← ACP 定义的通知类型
  this.onEndTurn?.();
}

}


🎯 总结ACP 的核心价值

ACP 协议 = 标准化的通信规范

就像:

  • HTTP 定义了浏览器和服务器如何通信
  • WebSocket 定义了双向实时通信的格式
  • ACP 定义了 UI 和 AI CLI 如何通信

没有 ACP 会怎样?

每个 CLI 都会有自己的格式:

Qwen 自己的格式(假设)

qwen send "帮我分析代码"

RESPONSE: 好的...

Claude 自己的格式(假设)

claude --prompt="帮我分析代码"

OUTPUT: 好的...

Codex 自己的格式(假设)

codex --input "帮我分析代码"

RESULT: 好的...

你需要为每个 CLI 写不同的对接代码!

有了 ACP 之后?

所有支持 ACP 的 CLI 都使用相同的格式: // 统一的发送格式 {"jsonrpc": "2.0", "method": "session/send_prompt", "params": {...}}

// 统一的接收格式 {"jsonrpc": "2.0", "method": "session/update", "params": {...}}

你只需要写一套代码就能对接所有 CLI


最终答案

问:这样的话是不是就跟 ACP 没有什么关系了?

答:完全相反!整个通信的核心就是 ACP 协议!

  • VSCode Extension 使用 ACP 协议与 Qwen CLI 通信
  • 所有的消息格式都遵循 ACP 规范JSON-RPC 2.0
  • initialize、new_session、send_prompt 都是 ACP 定义的方法
  • session/update、permission/request 都是 ACP 定义的通知

ACP 不是一个中间件或服务,而是一套通信协议标准,让不同的 UIAionUi、VSCode 插件)可以用统一的方式与不同的 CLIQwen、Claude、Codex对话。

这就像你用 HTTP 协议访问网站,不需要"连接到 HTTP 服务器"HTTP 只是规定了浏览器和服务器怎么说话的格式。ACP 也是一样的道理!