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

1027 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

> 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: {...} });
2. 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());
// 处理消息
});
3. 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;
}
2. 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
}
});
}
3. 处理 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 也是一样的道理!