- 将 WebView 调整到编辑器右侧 - 添加 ChatHeader 组件,实现会话下拉菜单 - 替换模态框为紧凑型下拉菜单 - 更新会话切换逻辑,显示当前标题 - 清理旧的会话选择器样式 基于 Claude Code v2.0.43 UI 分析实现。
30 KiB
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 回复 │ └─────────────────────────────────────────────────────────────┘
🎯 关键技术点总结
- VSCode WebView ↔ Extension 通信
// WebView → Extension vscode.postMessage({ type: 'action', data: {...} });
// Extension → WebView
webview.postMessage({ type: 'response', data: {...} });
- 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()); // 处理消息 });
- 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 的部分:
- 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; }
- 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 } }); }
- 处理 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 不是一个中间件或服务,而是一套通信协议标准,让不同的 UI(AionUi、VSCode 插件)可以用统一的方式与不同的 CLI(Qwen、Claude、Codex)对话。
这就像你用 HTTP 协议访问网站,不需要"连接到 HTTP 服务器",HTTP 只是规定了浏览器和服务器怎么说话的格式。ACP 也是一样的道理!