v1.1.0Pi v1.1.0 源码分析Analysis of Pi v1.1.0 · 2026-10-07

protocolprotocol

远程 pi 会话的传输无关 CBOR 协议Transport-neutral CBOR protocol for remote pi sessions

Run modesInfra

← → 翻页 · F 全屏 · O 目录 · T 明暗 · L 语言← → navigate · F fullscreen · O overview · T theme · L language

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.001
01 · 三层:信封、编解码、分帧01 · Three layers: envelopes, codec, framingRun modes

三层:信封、编解码、分帧Three layers: envelopes, codec, framing

pi-protocol: 869 lines in three layers — envelopes, codec, framingpackages/protocol/srccbor/依赖与使用者Dependencies and users22index.ts唯一导入路径 ".":cbor、codec、framing 全部导出,protocol.ts 导出类型与 PROTOCOL_VERSIONthe only import path ".": all of cbor, codec, framing; types and PROTOCOL_VERSION from protocol.ts141codec.tsparse* · encodeClientMessage / encodeServerMessage · ClientMessageDecoder / ServerMessageDecoder · ProtocolValidationErrorparse* · encodeClient/ServerMessage · Client/ServerMessageDecoder · ProtocolValidationError110protocol.tsTypeBox 信封 schemaTypeBox envelope schemasPROTOCOL_VERSION = 8151framing.tsu32 大端长度前缀u32 big-endian prefixFrameDecoder · 16 MiB216encoder.tsencodeCbor:定长子集encodeCbor: definite subset168decoder.tsdecodeCbor:严格解码decodeCbor: strict decode52options.ts上限默认值 · CborErrordefault limits · CborErrorhellorequestcancelhellohello_errorresponseservice_updateattachment@earendil-works/chord ^1.1.0JsonValue · isJsonValue:严格 JSON 边界JsonValue · isJsonValue: the strict-JSON boundarytypebox 1.3.27Type.Object(additionalProperties: false) + Check5clientpi-client:Connection 握手、请求信封pi-client: Connection handshake, request envelopes5serverpi-server:ClientMessageDecoder、回应与更新pi-server: ClientMessageDecoder, responses, updates8coding-agentexperimental/:ServerId、DEFAULT_MAX_FRAME_LENGTHexperimental/: ServerId, DEFAULT_MAX_FRAME_LENGTH校验validate分帧frame客户端消息(3)Client messages (3)服务端消息(5)Server messages (5)依赖Depends on被导入(src 中导入它的文件数)Imported by (files in src)coding-agent 只把三个包列为 devDependencies,coding-agent lists all three packages only as供实验性的 server / client 运行时使用。devDependencies, for its experimental server / client runtime.README:默认上限 16 MiB / 帧、1,000,000 个数组元素或映射项、64 层嵌套;协议是实验性的,不保证兼容。README: default limits 16 MiB per frame, 1,000,000 array elements or map entries, 64 nesting levels; experimental, no compatibility guarantees.标签 = 行数 / 文件数tag = lines / files信封与编解码envelopes & codecCBOR依赖dependencies入口entry使用uses

没有任何 Node 专用 API:同一份代码可以用在浏览器、Bun 或 worker 里;传输(Unix socket、WebSocket…)由 client / server 提供。No Node-only APIs: the same code works in a browser, Bun or a worker; transports (Unix sockets, WebSockets…) come from client / server.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.002
02 · 一条消息的往返02 · One message, there and backRun modes

一条消息的往返One message, there and back

One message: validate → CBOR → frame → bytes → split → decode → validate任何一步失败都是 ProtocolValidationErrorAny failure along the way is a ProtocolValidationErrorcodec.tsClientMessage普通 JS 对象a plain JS objectparseClientMessageTypeBox Check + isJsonValueencodeCbor上限 = maxFrameLengthlimit = maxFrameLengthencodeFrame前缀 u32 大端长度prefix a u32 big-endian lengthUint8Array一整帧,交给传输one whole frame, to the transportByteTransport / ByteConnection:可以任意切分、合并字节,但必须保持顺序ByteTransport / ByteConnection: may split and coalesce bytes arbitrarily, but must preserve their orderchunk, chunk, …任意大小的片段fragments of any sizeFrameDecoder.push凑齐 4 字节头 → 负载4-byte header → payloaddecodeCbor恰好一个定长 CBOR 项exactly one definite-length itemparseServerMessage同一套 schema 再校验一次the same schemas, checked againServerMessage[]push() 返回 0..n 条消息push() returns 0..n messages发送方(encodeClientMessage / encodeServerMessage)Sender (encodeClientMessage / encodeServerMessage)接收方(ClientMessageDecoder / ServerMessageDecoder)Receiver (ClientMessageDecoder / ServerMessageDecoder)schema 不符或含非 JSON 值Schema mismatch or a non-JSON value未知字段、Uint8Array、NaN、undefined、原型、环unknown fields, Uint8Array, NaN, undefined, prototypes, cyclesCBOR 或分帧出错Malformed CBOR or framingCborError / FrameError 被包装,消息截断到 500 字符CborError / FrameError are wrapped, messages cut to 500 chars解码器一旦失败Once a decoder fails之后的 push() / end() 都直接抛错every later push() / end() throwsend() 时仍有半帧end() with a partial frame"Truncated frame at end of stream"协议代码protocol code传输transport数据流data flow失败failure

任何失败都变成 ProtocolValidationError:CborError 与 FrameError 被包装,错误消息截断到 500 个字符。Every failure becomes a ProtocolValidationError: CborError and FrameError are wrapped and the message is cut to 500 characters.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.003
03 · 8 种信封03 · The 8 envelopesRun modes

8 种信封The 8 envelopes

Protocol version 8: 8 envelopes — 3 client messages, 5 server messagesClientMessage客户端 → 服务端client → serverServerMessage服务端 → 客户端server → client«first»ClientHellotype: "hello"version: integer ≥ 0RequestEnvelopetype: "request"id: string (non-empty)target: RpcTargetcall: JSON (opaque)CancelEnvelopetype: "cancel"id: stringtarget: RpcTarget«first»ServerHellotype: "hello"version: 8 (literal)serverId: ServerId«terminal»ServerHelloErrortype: "hello_error"error: ProtocolErrorResponseEnvelopetype: "response"id · ok: trueresult?: JSON (opaque)type: "response"id · ok: falseerror: ProtocolErrorServiceEventEnvelopetype: "service_update"subscriptionId: stringupdate: JSON (opaque)«out of band»AttachmentEnvelopetype: "attachment"attachment: SessionTarget | nullProtocolErrorcode: string (non-empty)message: string错误码对协议是不透明的:Codes are opaque to the protocol:version、invalid_request、cancelled、wrong_server …version, invalid_request, cancelled, wrong_server …由 server 与 chord 定义。are defined by the server and chord.所有信封都是 additionalProperties: false 的 StrictObject;id 是非空字符串;call、result、update 只校验为严格 JSON(Type.Unknown + isJsonValue)。Every envelope is a StrictObject with additionalProperties: false; ids are non-empty strings; call, result and update are only checked to be strict JSON (Type.Unknown + isJsonValue).response 的 ok: true 可以没有 result 字段(void 回应)。A response with ok: true may omit result (a void response).客户端发送sent by the client服务端发送sent by the server错误error

客户端 hello 的 version 是任意非负整数,服务端的则是字面量 8:版本不符由服务端用 hello_error { code: "version" } 回答。The client's hello.version is any non-negative integer, the server's the literal 8: a mismatch is answered by the server with hello_error { code: "version" }.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.004
04 · 路由:serverId、sessionId、attachmentId04 · Routes: serverId, sessionId, attachmentIdRun modes

路由:serverId、sessionId、attachmentIdRoutes: serverId, sessionId, attachmentId

RpcTarget: fencing a call to one logical server, one Session, one live attachment逻辑服务器Logical serverserverId持久 SessionDurable SessionsessionId实时附着Live attachmentattachmentId路由怎样到达客户端How the route reaches the client服务器拒绝的路由Routes the server rejects{ serverId }服务器级调用:目录、管理server-wide calls: directory, management{ serverId, sessionId, attachmentId }Session 调用:每个请求都带完整路由Session calls: every request carries the full routewrong_serversession_not_attachedserver_draininginvalid_request规范的小写 UUIDv4;是逻辑身份,不是 socket 地址a canonical lowercase UUIDv4; a logical identity, not a socket addressSession 与它的 Harness 留在 Session worker 进程里the Session and its Harness stay in the Session worker process每次成功 attach 由服务器生成(randomUUID)generated by the server on every successful attach (randomUUID)一个 Session 可以有多个 presentation 附着;A Session may have several presentation attachments;同一连接重复 attach 同一 Session 是幂等的。repeating attach for the same Session on one connection is idempotent.^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$// 管理调用的结果里没有任何路由 id// management results carry no route idsawait sessions.attach(sessionId)// 服务器装好路由后,带外发送:// once installed, the server sends out of band:{ type: "attachment",attachment: { serverId, sessionId, attachmentId } }// detach / 断开 / 失效 → attachment: null// detach / disconnect / invalidation → attachment: nulltarget.serverId 不是这台服务器(在访问仓库之前)target.serverId is another server (before any repository access)本连接没有附着该 Session,或 attachmentId 已过期this connection holds no such attachment, or the id is stale服务器正在关闭the server is shutting down信封本身不合法the envelope itself is invalid切换或重新附着之后,带旧 attachmentId 的延迟帧会被拒绝:路由同时防止跨服务器、跨 Session 和过期的调用。After switching or reattaching, delayed frames with an old attachmentId are rejected: the route prevents cross-server, cross-Session and stale calls alike.服务器级server-wideSession附着attachment拒绝rejected

管理服务的 attach() / detach() 不返回任何路由 id:服务器装好路由后才发 attachment,解除时发 attachment: null。Management attach() / detach() return no route ids: the server sends attachment once the route is installed, and attachment: null when it is cleared.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.005
05 · 一条连接上的消息顺序05 · Message order on one connectionRun modes

一条连接上的消息顺序Message order on one connection

Message order on one connection: handshake, requests, out-of-band attachment, subscriptions, cancellationClientpi-clientServerpi-serverhello { version: 8 }hello { version: 8, serverId }request { id: "request-1", target, call: attach }attachment { attachment: { serverId, sessionId, attachmentId } }response { id: "request-1", ok: true }request { id: "request-2", call: subscribe }response { id: "request-2", ok: true, result: snapshot }service_update { subscriptionId, update }cancel { id: "request-3", target }response { id: "request-3", ok: false, error: { code: "cancelled" } }① 握手① Handshake第一帧必须是 hellothe first frame must be hello② 管理调用② A management callattach 的结果里没有路由 idattach returns no route ids③ 订阅③ A subscription先快照,再更新snapshot first, then updates④ 取消④ Cancellation同一 id、同一 targetsame id, same target服务端 5 s 内没完成握手No handshake within 5 s→ hello_error,关闭连接→ hello_error, close版本不符 → hello_error { version }Wrong version → hello_error { version }serverId 不符 → 客户端断开serverId mismatch → client drops带外:在 attach 的回应之前Out of band: before the attach送达,路由已经装好response, route already installed快照之前产生的更新Updates produced before the在服务端排队,回应之后才发snapshot queue until after it请求在服务端被 abortThe request is aborted on the客户端早已在本地 rejectserver; the client rejected locally握手之后再发 hello、或在客户端发出 hello 之前收到服务端数据,都是协议错误:连接被关闭。A second hello after the handshake, or server data before the client's hello was sent, is a protocol error: the connection closes.客户端 → 服务端client → server服务端 → 客户端server → client错误回应error response

服务端握手超时默认 5 秒;第一条消息不是 hello、或之后再发 hello,都会以 hello_error { code: "invalid_request" } 结束连接。The server's handshake timeout is 5 seconds by default; a first message that is not hello, or a later hello, ends the connection with hello_error { code: "invalid_request" }.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.006
06 · 分帧与 FrameDecoder06 · Framing and FrameDecoderRun modes

分帧与 FrameDecoderFraming and FrameDecoder

Framing: a u32 big-endian length + one CBOR item; 16 MiB by default一段字节流(chunk 边界可以落在任何地方)A byte stream (chunk boundaries may fall anywhere)FrameDecoder 的状态FrameDecoder statesframing.tspush(chunk) 做的事What push(chunk) doesopenendedfailed00 00 00 15CBOR · 21 字节CBOR · 21 bytes00 00 00 0000 02 00 00负载 · 131,072 字节 …payload · 131,072 bytes …chunk 1chunk 2chunk 3chunk 4 …长度 = 21length = 21{ type: "hello", version: 8 }空帧 → Uint8Array(0)empty frame → Uint8Array(0)长度 = 131,072length = 131,072= 2 个 64 KiB 块,收满后拼接= two 64 KiB blocks, joined when complete编码:encodeFrame(payload) 只加 4 字节头;负载超过 0xffffffff 字节 → RangeError。Encoding: encodeFrame(payload) only adds the 4-byte header; a payload above 0xffffffff bytes → RangeError.end():没有半帧end(): no partial frame超限 · 截断too long · truncatedpush(chunk) → Uint8Array[]留在 openstays open之后的 push / end → FrameErrorlater push / end → FrameError清空缓冲;之后一律 FrameErrorbuffers cleared; FrameError from then onClient/ServerMessageDecoder 把它包装成 ProtocolValidationError。Client/ServerMessageDecoder wrap it as ProtocolValidationError.凑齐 4 字节头,可以跨多个 chunkFill the 4-byte header, across chunks if needed长度 > maxFrameLength → 立即失败Length > maxFrameLength → fail at once不等负载到达without waiting for the payload长度 0 → 立刻产出一个空帧Length 0 → emit an empty frame now负载按 64 KiB 的块收集Collect the payload in 64 KiB blocksPAYLOAD_BLOCK_SIZE收满:单块直接返回,否则拼成一个数组Complete: one block as is, else joined总是复制字节,不引用输入 chunkBytes are always copied, never aliased123456帧frames解码器decoder正常结束clean end失败failure

负载总是被复制:返回的帧不引用调用方传入的 chunk,调用方可以复用自己的缓冲区。Payloads are always copied: returned frames never alias the caller's chunks, so callers may reuse their buffers.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.007
07 · 严格的 CBOR 子集07 · A strict CBOR subsetRun modes

严格的 CBOR 子集A strict CBOR subset

A strict RFC 8949 subset: definite-length items only, no tags8 种主类型(major type)The 8 major typescbor/decoder.ts · encoder.tsCborOptions 与规则CborOptions and rulesoptions.ts例子:encodeClientMessage({ type: "hello", version: 8 })Example: encodeClientMessage({ type: "hello", version: 8 })0✓1✓2✓3✓4✓5✓6✕7✓31✕16 MiBmaxByteLength输入 / 输出与单个串的长度input / output and any one string1,000,000maxContainerLength数组元素或映射项array elements or map entries64 (≤ 512)maxDepth嵌套层数nesting levels无符号整数unsigned intnumber(安全整数)number (safe integer)负整数negative intnumber,-1 − n 必须安全number; -1 − n must be safe字节串byte stringUint8Array;信封层再拒绝(非 JSON)Uint8Array; refused later by the envelope check文本串text stringfatal UTF-8;保留开头的 BOMfatal UTF-8; a leading BOM is kept数组array不能有空洞或 undefinedno holes or undefined映射map键必须是字符串且不重复;__proto__ 是普通数据string keys, no duplicates; __proto__ is datatag一律拒绝always rejected简单值 / 浮点simple / float只有 false、true、null 与有限的 float64only false, true, null and finite float64不定长(附加信息 31)indefinite length (info 31)一律拒绝,break 也是always rejected, break too编码器Encoder整数用最短的参数;-0 与小数写成 float64shortest argument for integers; -0 and fractions as float64值为 undefined 的属性被省略,假值保留undefined properties are omitted, falsy values kept环、符号键、非普通对象 → CborErrorcycles, symbol keys, non-plain objects → CborError字符串必须是合法的 Unicode 标量序列strings must be valid Unicode scalar values解码器Decoder先检查声明的长度与深度,再遍历checks declared lengths and depth before traversing恰好一个项:尾随字节 → 错误exactly one item: trailing bytes → error整数超出安全范围 → 错误integers outside the safe range → error00 00 00 15 a2 64 74 79 70 65 65 68 65 6c 6c 6f 67 76 65 72 73 69 6f 6e 0825 字节:4 字节长度(21)· a2 = 2 项映射 · 64 "type" · 65 "hello" · 67 "version" · 08 = 825 bytes: 4-byte length (21) · a2 = map of 2 · 64 "type" · 65 "hello" · 67 "version" · 08 = 8支持supported拒绝rejected上限limits

编码器为整数选最短的参数;非整数与 -0 写成 float64(0xfb);非有限数、非安全整数直接报错。The encoder picks the shortest argument for integers; non-integers and -0 are written as float64 (0xfb); non-finite numbers and unsafe integers are errors.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.008
08 · protocol、chord 与应用的分界08 · Where protocol, chord and the application meetRun modes

protocol、chord 与应用的分界Where protocol, chord and the application meet

Who owns which layer: the envelope is protocol's, the call is chord's, the data is the application'spi-protocol 校验的信封The envelope pi-protocol validatesRequestEnvelopechord 解释的 callThe call chord interpretsServiceCall应用自己的数据Application dataargs · result · update谁在哪里解析 chord 语法Who parses the chord grammar, where不在公共协议里Not in the public protocoltype: "request"idtarget: RpcTargetcall: …serviceIdinstance?memberargscataloguesubscribeunsubscribeSession 目录状态Session directory state管理结果management resultstranscripttranscripts模型models插件plugins其他服务值other service valuespi-client · client.tsparseServiceCall · parseServiceCataloguecreateServiceStateDecoder · parseWire…Snapshotpi-server · server.tsdecodeServiceControlCall · parseServiceCallcreateServiceStateEncoder · RemoteServiceErrorcoding-agent · services/connection.tscreateClientServiceTransport(client, route)→ 应用自己的类型化 chord 服务绑定→ the app's own typed chord service bindings或者一个 $chord.service 控制调用:or a $chord.service control call:回应里的服务目录、订阅快照与更新、服务错误码、复制状态的 Delta 路径编解码都属于 chord。Catalogues, subscription snapshots and updates, service error codes and Delta path codecs belong to chord.对 protocol 与 server 都是不透明的 JSON;Opaque JSON to both the protocol and the server;由 chord 绑定与应用在服务边界上校验。validated by chord bindings and the app at the service boundary.服务器与 worker 的生命周期:协调器只转发不透明消息Server and worker lifecycle: the coordinator only relays opaque messages对端认证与已认证的服务上下文:实验性传输没有实现Peer authentication and authenticated contexts: not implemented兼容性保证:协议是实验性的Compatibility guarantees: the protocol is experimentalprotocol 只检查 call、result、update 是严格 JSON;它不导出、也不校验 chord 的语法——这让信封层可以在不了解任何服务契约的情况下路由。The protocol only checks that call, result and update are strict JSON; it neither exports nor validates chord's grammar — so the envelope layer routes without knowing any service contract.protocolchord应用application范围之外out of scope

因此服务端可以把 Session 调用原样转发给 Session worker,而不需要加载那个服务的契约。So the server can forward a Session call to the Session worker as is, without loading that service's contract.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.009
扩展点Extension pointspi-coding-agent

protocol 的扩展点Where to plug into protocol

1
maxFrameLengthmaxFrameLength

每一端的帧上限(默认 16 MiB);两端必须一致。Each peer's frame limit (16 MiB by default); both peers must match.

2
encodeCbor / decodeCbor(…, CborOptions)encodeCbor / decodeCbor(…, CborOptions)

单独使用 CBOR 编解码,配置字节、容器与深度上限。Use the CBOR codec on its own, with byte, container and depth limits.

3
FrameDecoder / encodeFrameFrameDecoder / encodeFrame

只用分帧,承载你自己的负载。Use only the framing, carrying your own payloads.

4
parseClientMessage / parseServerMessageparseClientMessage / parseServerMessage

校验从别的通道得到的消息对象。Validate message objects obtained through another channel.

5
ByteTransport (client) · ServerListener (server)ByteTransport (client) · ServerListener (server)

任何保持字节顺序的流都能承载协议:Unix socket、WebSocket 中继、管道。Any order-preserving byte stream can carry the protocol: Unix sockets, a WebSocket relay, pipes.

图解 Pi Agent · Pi v1.1.0Pi Agent, Illustrated · Pi v1.1.010
←
1 / 10