02 Webview 与 Extension Host 通信
学习目标
本章要解决什么问题:Cline 为什么选择 gRPC-like 协议而非 VS Code 原生 postMessage?如何实现流式响应和调试?
项目实践
通信架构
Proto 文件与代码生成
协议定义在 proto/cline/ 目录下,通过 npm run protos 生成 TypeScript 代码:
proto/ └── cline/ ├── webview.proto → Webview → Extension Host 请求 └── ... → 其他服务定义Proto 文件定义服务接口和消息类型,通过 protobuf 编译器生成类型安全的 TypeScript 客户端和服务器代码。
gRPC-like 协议设计
一元请求:Webview 发送单次请求,Extension Host 返回单次响应。
# 伪代码:一元请求const response = await grpcClient.send({ method: "getSettings", params: {}})流式响应:Webview 发送请求后,Extension Host 持续返回流式数据。
# 伪代码:流式响应for await (const chunk of grpcClient.stream({ method: "startTask", params: { prompt: "..." }})) { renderChunk(chunk)}支持流式的关键:VS Code 的 postMessage API 本身支持单向消息传递,Cline 在此基础上构建了请求-响应-流式的模式:
- Webview 发送请求消息,附带唯一 ID
- Extension Host 处理请求,通过多个
postMessage返回流式响应 - Webview 根据请求 ID 将流式响应聚合
grpc-recorder 录制/重放
grpc-recorder 是 Cline 的调试工具:
- 录制模式:记录所有 gRPC 请求和响应到文件
- 重放模式:从文件读取请求和响应,模拟 Extension Host 行为
用途:
- 复现特定的交互序列
- 调试流式解析问题
- 测试 UI 渲染逻辑,无需实际运行 Agent
为什么选 gRPC-like 而非简单 postMessage?
| 对比项 | 原生 postMessage | gRPC-like |
|---|---|---|
| 类型安全 | 无,手动定义消息格式 | Proto 生成,类型安全 |
| 流式支持 | 需要手动实现 | 内置流式语义 |
| 可调试性 | 难以追踪消息流 | grpc-recorder 录制/重放 |
| 跨平台 | 仅 VS Code | Proto 定义可在其他平台复用 |
问题与规避
| 陷阱 | 场景 | 对策 |
|---|---|---|
| 消息丢失 | postMessage 在页面刷新后丢失 | Webview 状态持久化,刷新后恢复 |
| 流式消息乱序 | 并发流式响应交错 | 使用请求 ID 区分不同流 |
| Proto 文件不同步 | 修改 proto 后未重新生成 | CI 检查生成的 TypeScript 文件是否最新 |
设计取舍
gRPC-like vs WebSocket
| 方案 | 优势 | 代价 |
|---|---|---|
| gRPC-like | 类型安全、Proto 可复用、流式语义清晰 | 需要代码生成,理解成本高 |
| WebSocket | 实现简单、实时双向 | 无类型保证、消息格式需要手动约定 |
Cline 选择 gRPC-like,因为类型安全对于工具调用协议至关重要,且 Proto 定义可在 SDK 的其他宿主中复用。
参考来源
- Cline 源码:
apps/vscode/proto/cline/webview.proto - Cline 源码:
apps/vscode/src/core/controller/grpc-handler.ts