Skip to content

4.2 客户端与连接

@pi/client 是远程会话的传输无关客户端。它把"串行化的 Agent 会话"重新变成好用的对象 API。这一节讲 PiClient 如何管理连接、请求响应、以及会话句柄。

PiClient 总览

packages/client/src/client.ts:51PiClient 是客户端入口。它由几个内部组件组成:

PiClient
 ├── Connection     底层连接(握手、收发、状态)
 ├── ClientState    维护服务端快照(会话列表、模型列表)
 ├── pendingRequests   id → 请求映射(匹配响应)
 └── sessionLeases     会话租约管理(attach/detach 的所有权)

核心抽象让客户端不关心传输细节transportFactory 把 Client 和一个传输(TCP/Unix socket)连起来,Connection 负责定帧收发(基于 @pi/protocol)。

连接与握手

PiClient.connect()Connection 建立底层传输 → 发送 ClientHello → 收到 ServerHello

ts
static async connect(options: PiClientOptions): Promise<PiClient> {
	const client = new PiClient(options);
	await client.connect();
	return client;
}

握手成功后,Connection 把初始 ServerSnapshot 交给 ClientState.applyServerSnapshot(),客户端立刻知道有哪些会话、哪些模型。

请求-响应:如何匹配

PiClient#requestSequence 自增 id,把每个请求存进 #pendingRequests

ts
interface PendingRequest { command: Command; resolve; reject; }
ts
async #request(command: Command): Promise<CommandResult> {
	const id = String(++this.#requestSequence);
	this.#pendingRequests.set(id, pending);
	this.#connection.send(encodeClientMessage({ type: "request", id, request: command }));
	// 收到对应 id 的 response 时 resolve / reject
}

服务端回的 ResponseEnvelope 带相同 id,客户端据此把结果交给对应请求。即使请求乱序返回也能正确配对

状态管理:ClientState

packages/client/src/state.tsClientState 维护客户端视角的权威状态:

  • 会话列表:来自 ServerSnapshot.sessions
  • 模型列表:来自 ServerSnapshot.models
  • 事件分发:把 session_snapshotsession_progresssession_removed 等事件分发给订阅者。

客户端遵守"快照重建 + 增量更新"策略:收到快照就重建视图,收到增量就局部打补丁。

会话句柄:PiSessionHandle

客户端不直接操作会话,而是通过 SessionHandlepackages/client/src/session-handle.ts):

ts
const handle = client.acquireSession({...});   // 获取会话租约
await handle.prompt("你好");                    // 发起提问
await handle.steer("插句话");                   // 中途插话
await handle.abort();                           // 中止
handle.onEvent((event) => { ... });            // 订阅会话事件

会话租约(Session Lease)

PiClient租约控制会话所有权:

  • 一个会话同一时刻可能被多个客户端"看到",但活跃写操作需要租约。
  • 服务端配合返回 session_locked 错误(见 4.1)。
  • 客户端内部用 #sessionLeaseCounts#sessionAttachments 等管理 attach/detach 的异步编排,处理重连、恢复等复杂场景。

事件订阅

订阅服务端推来的事件。onEvent 订阅的是 @pi/protocolServerEvent 联合类型(schemas.ts:395),共 4 种

ts
export type ServerEvent =
	| { type: "server_snapshot"; snapshot: ServerSnapshot }        // ① 全局快照
	| { type: "session_snapshot"; snapshot: SessionSnapshot }       // ② 单会话快照
	| { type: "session_progress"; sessionId; progress: TranscriptProgress }  // ③ 增量
	| { type: "session_removed"; sessionId }                        // ④ 会话被移除
事件携带内容触发时机客户端怎么用
server_snapshot全局快照(会话列表 + 模型 + revision)会话列表有增减时广播重建"有哪些会话"的整体视图
session_snapshot单个会话的权威快照(完整转录)会话状态大改(prompt/steer/abort 完成)重建某个会话的完整视图
session_progress增量(转录条目变化)Agent 边跑边推(模型吐字、工具执行)局部更新,实现实时效果
session_removed会话 id会话被删除移除本地视图
ts
// 全局订阅:所有事件
client.onEvent((event) => {
	switch (event.type) {
		case "server_snapshot": ...      // 会话列表变了
		case "session_snapshot": ...     // 某会话全貌变了
		case "session_progress": ...     // 某会话有增量
		case "session_removed": ...      // 某会话被删
	}
});

会话级订阅:也可以用 SessionHandle.onEvent 只关注某个会话:

ts
handle.onEvent((event) => { ... });   // 只收到该 sessionId 的事件

其中 session_progressprogress 又分 4 种(schemas.ts:204):

ts
TranscriptProgress =
	| { type: "item_started"; item }        // 新条目开始(模型开始答/工具开始跑)
	| { type: "assistant_delta"; messageId; contentIndex; kind; delta }  // 助手文本/思考增量
	| { type: "item_updated"; item }        // 条目更新(如工具 running→complete)
	| { type: "item_finished"; item }       // 条目结束

一句话:onEvent 订阅 4 种 ServerEvent(全局快照 / 单会话快照 / 增量 / 删除),靠"快照重建 + 增量局部更新"维护视图。

与"本地 Agent"的关系

PiClient + PiServer 组合,让同一套 Agent 会话既能在本地跑,也能被远程终端连接:

  • 本地:coding-agent 直接持有 Agent 实例。
  • 远程:coding-agent 作为 server 的 backend,PiServer 暴露协议接口,TUI 通过 PiClient 连接。

对上层而言,本地和远程的 API 保持一致(都是 prompt / steer / abort / 事件订阅),这就是"传输无关"的价值。

小结

  • PiClient = Connection + ClientState + 请求匹配 + 会话租约。
  • 请求用自增 id 与响应配对,支持乱序。
  • 快照重建 + 增量更新维护客户端状态。
  • 会话通过租约句柄操作,所有权受服务器约束。
真实源码位置
  • PiClientpackages/client/src/client.ts:51
  • 请求匹配:packages/client/src/client.ts:55
  • ClientStatepackages/client/src/state.ts
  • SessionHandlepackages/client/src/session-handle.ts

下一步看 Demo:最小远程会话