@av-pi-studio/client
与 Pi- Studio 守护进程交谈的客户端端库: 低级 WebSocket 驱动程序
(单位:千美元)DaemonClient)外加一个高水平,打入的SDK外观 (PiStudioClient) (中文(简体) ). 这个包裹是什么
@av-pi-studio/cli 以及未来任何网络/移动/桌面客户端应基于什么
我也一样.
安装
npm install @av-pi-studio/client
两层
DaemonClient——低级WebSocket驱动程序:运输生命周期hello握手,JSON + 二进制框架解析,RPC请求/响应关联,乒乓活性, 和一个可插件Transport抽象化,因此测试(或未来的非网络Socket传输)可以 (c) 启动它们自己的实施.PiStudioClient- 一个高层的外表结束DaemonClient使用已键入、已命名的方法 (单位:千美元)createAgent, (中文(简体) ),agent(id).send(...), (中文(简体) ),providers.listModels(...),而不是原始的request(type, payload)打电话.
还包括:a ReconnectionManager (责任后退自动连接)和a
TerminalStreamRouter (demuxs 二进制终端帧按槽).
快开始
import { DaemonClient, PiStudioClient, createWebSocketTransport } from "@av-pi-studio/client";
const daemon = new DaemonClient({
url: "ws://127.0.0.1:6767",
clientId: "my-stable-client-id",
clientType: "cli", // "mobile" | "browser" | "cli" | "mcp"
});
await daemon.connect(); // resolves once the hello → status handshake completes
const client = new PiStudioClient(daemon);
const { agentId } = await client.createAgent({
provider: "pi",
model: "claude-3-5-sonnet",
cwd: "~/my-project",
});
const agent = client.agent(agentId);
agent.timeline.subscribe((event) => console.log(event.kind, event));
await agent.send("Add a health-check endpoint");
DaemonClient
连接生命周期
idle → connecting → open → closing → closed,通过 onStateChange(handler)。 。 。 .
关键方法
| 方法 | 说明 |
|---|---|
connect() | 打开运输工具,等待 hello → status 手握手来完成 ServerInfoPayload |
close(code?, reason?) | 关闭运输。 没有分开的 disconnect()。 。 。 . |
request<T>(type, params?, timeoutMs?) | 发送关联的 RPC, 解决响应或拒绝 RpcError编号 :RpcTimeoutError |
sendSession(message) | 火灾和遗忘会话消息(以 session ——没有等待答复 |
sendBinary(data) | 通过运输发送原始的二进制框架(终端/文件传输) |
ping(timeoutMs?) | 发送 JSON ping,等待关联 pong (不是RFC 6455平调) |
hasFeature(flag) | true 如果最后一个 server_info.features.<flag> 是事实 |
onSessionMessage(handler) | 订阅每个入境者 session 消息; 返回未订阅的 fn |
onTerminalFrame(handler) | 订阅每个已解码的二进制终端框架 |
onFileTransferFrame(handler) | 订阅每个已解码的二进制文件传输框架 |
onStateChange(handler) | 订阅连接状态过渡 |
state 编号 : serverId 编号 : features 编号 : serverCapabilities | 当前连接状态和上次握手后的身份/能力 |
错误
| 类 | 扔的时候 |
|---|---|
RpcError | 守护进程回复了 rpc_error 此项请求 |
RpcTimeoutError | 未收到答复 rpcTimeoutMs —— 说 仅操作级别失败; 套接字未打开 |
构造选项
interface DaemonClientOptions {
url: string;
clientId: string;
clientType: "mobile" | "browser" | "cli" | "mcp";
protocolVersion?: number; // defaults to the current protocol version
appVersion?: string;
capabilities?: Record<string, boolean>; // CLIENT_CAPS flags to advertise in `hello`
transport?: Transport; // inject a stub for tests; defaults to native WebSocket
rpcTimeoutMs?: number; // per-request timeout; never tears down the socket
now?: () => number; // inject a clock for deterministic tests
}
PiStudioClient
const client = new PiStudioClient(daemonClient);
| 成员 | 说明 |
|---|---|
createAgent(req) | create_agent_request 难民保护委员会 |
agent(agentId) | 为现有代理人采取的行动的范围send, (中文(简体) ), interrupt, (中文(简体) ), update, (中文(简体) ), resume, (中文(简体) ), archive, (中文(简体) ), delete, (中文(简体) ), onUpdate, (中文(简体) ), timeline.fetch编号 :.subscribe(中文(简体) ) |
workspace(workspaceId) | 工作空间的范围行动 |
providers | listProviders(), (中文(简体) ), listModels(provider), (中文(简体) ), listModes(provider), (中文(简体) ), refreshSnapshot() |
onAgentUpdate(handler) 编号 : onWorkspaceUpdate(handler) | 订阅所有代理/工作空间的广播 |
connection | 逃回深处 DaemonClient |
importAgentSession(daemon, args) | 命名导出 - 按处理恢复提供者- 本地会话 |
自动重接
import { ReconnectionManager } from "@av-pi-studio/client";
const mgr = new ReconnectionManager(daemon, { initialDelayMs: 500, maxDelayMs: 30_000 });
mgr.onReconnected(({ attempt, serverId }) => console.log(`reconnected after ${attempt} tries`));
mgr.start(); // arms; automatically retries with exponential backoff on socket drop
mgr.stop(); // disarm
每次接通尝试电话 daemon.connect(),它重新发送完整 hello 握个手
能力和身份总是透明地得到补充.
终端帧路由
import { TerminalStreamRouter } from "@av-pi-studio/client";
const router = new TerminalStreamRouter(daemon);
router.start(); // begin routing (idempotent)
const unsubscribe = router.subscribeSlot(slot, {
onOutput: (data) => { /* opcode Output */ },
onSnapshot: (data) => { /* opcode Snapshot, sent on (re)subscribe */ },
onRestore: (data) => { /* opcode Restore, reflowable/mode-gated */ },
});
router.sendInput(slot, bytes); // opcode Input = 0x02
router.sendResize(slot, rows, cols); // opcode Resize = 0x03
router.stop(); // stop routing; subscribers retained
自定义运输
interface Transport {
connect(url: string): Promise<void>; // resolves once the raw connection is open (pre-handshake)
sendText(data: string): void;
sendBinary(data: Uint8Array): void;
close(code?: number, reason?: string): void;
readonly isOpen: boolean;
onMessage: ((data: string | ArrayBuffer | Blob) => void) | null;
onClose: ((code: number, reason: string) => void) | null;
onError: ((error: unknown) => void) | null;
}
createWebSocketTransport(factory?: WsFactory) 是默认的节点/浏览器 WebSocket 备份
执行. factory 是可注入的 (url, protocols?) => AnyWebSocket ——为
或装有无记名字副规范的定制工厂(没有内置) password
参数;见 web-client是因为 connection-store.ts 以这种方式).
中继运输(E2EE,通过 @av-pi-studio/relay(中文(简体) )
import { createRelayTransport, parsePairingUrl, relayDialUrl } from "@av-pi-studio/client";
// offer: { publicKey, publicKeyB64, host? } OR { publicKey, publicKeyB64, relay: { endpoint, useTls } }
const offer = parsePairingUrl(pairingUrlOrFragment);
// sessionId defaults to deriveRelaySessionId(offer.publicKey) — the SAME id the daemon's own
// outbound relay dial always registers under — so it never needs to be transmitted separately.
const transport = createRelayTransport({ daemonPublicKey: offer!.publicKey });
const daemon = new DaemonClient({
url: relayDialUrl(offer!.relay!), // the relay's own address, not the daemon's
clientId,
clientType: "cli",
transport,
});
await daemon.connect(); // completes the E2EE handshake before the `hello` RPC ever crosses the wire
createRelayTransport 执行完全相同的 Transport 合同为
createWebSocketTransport - 交换它通过 DaemonClientOptions.transport 和所有别的东西
DaemonClient编号 :PiStudioClient API 工作不变, 不管守护进程是直接的还是到达的
通过接力。 见 @av-pi-studio/relay运行中继服务器的 README
@av-pi-studio/server读取一个守护进程.
设计规则
RpcTimeoutError从不关闭套接字. 缓慢的RPC是操作级别失败,而不是 连接失败 .clientId整个连接保持稳定 ——它识别逻辑客户端会话,而不是 物理联系.- 无 DOM/ Node 特定全球 内
daemon-client.ts或基数Transport类型——仅createWebSocketTransport本身触摸了平台 WebSocket, 而基础驱动程序从未 直接进口.
发展
npm run build # tsc -b
npx vitest run packages/client
测试注入根 Transport 执行和模拟时钟——没有打开真正的插座.