@av-pi-studio/client

中文文档
0.0.115发布于 2026/11/04TypeScriptES Module构建来源已验证近 30 天下载 1922

The client-side library for talking to a Pi-Studio daemon: a low-level WebSocket driver (DaemonClient) plus a high-level, typed SDK facade (PiStudioClient). This package is what @av-pi-studio/cli is built on, and what any future web/mobile/desktop c

原始使用文档
文档目录15 节
  1. Install
  2. Two layers
  3. Quick start
  4. DaemonClient
  5. Connection lifecycle
  6. Key methods
  7. Errors
  8. Constructor options
  9. PiStudioClient
  10. Auto-reconnect
  11. Terminal frame routing
  12. Custom transports
  13. Relay transport (E2EE, via @av-pi-studio/relay )
  14. Design rules
  15. Development

@av-pi-studio/client

The client-side library for talking to a Pi-Studio daemon: a low-level WebSocket driver (DaemonClient) plus a high-level, typed SDK facade (PiStudioClient). This package is what @av-pi-studio/cli is built on, and what any future web/mobile/desktop client should build on too.


Install

npm install @av-pi-studio/client

Two layers

  1. DaemonClient — the low-level WebSocket driver: transport lifecycle, the hello handshake, JSON + binary frame parsing, RPC request/response correlation, ping/pong liveness, and a pluggable Transport abstraction so tests (or a future non-WebSocket transport) can inject their own implementation.
  2. PiStudioClient — a high-level facade over DaemonClient with typed, named methods (createAgent, agent(id).send(...), providers.listModels(...), …) instead of raw request(type, payload) calls.

Also included: a ReconnectionManager (exponential-backoff auto-reconnect) and a TerminalStreamRouter (demuxes binary terminal frames by slot).

Quick start

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

Connection lifecycle

idle → connecting → open → closing → closed, observable via onStateChange(handler).

Key methods

MethodDescription
connect()Opens the transport, waits for the hello → status handshake to complete; resolves with the ServerInfoPayload
close(code?, reason?)Close the transport. There is no separate disconnect().
request<T>(type, params?, timeoutMs?)Sends a correlated RPC, resolves with the response or rejects with RpcError/RpcTimeoutError
sendSession(message)Fire-and-forget session message (wrapped in a session envelope) — no response awaited
sendBinary(data)Send a raw binary frame (terminal/file-transfer) via the transport
ping(timeoutMs?)Send a JSON ping, await the correlated pong (not RFC 6455 ping)
hasFeature(flag)true iff the last server_info.features.<flag> was truthy
onSessionMessage(handler)Subscribe to every inbound session message; returns an unsubscribe fn
onTerminalFrame(handler)Subscribe to every decoded inbound binary terminal frame
onFileTransferFrame(handler)Subscribe to every decoded inbound binary file-transfer frame
onStateChange(handler)Subscribe to connection state transitions
state / serverId / features / serverCapabilitiesCurrent connection state and the identity/capabilities from the last handshake

Errors

ClassWhen it's thrown
RpcErrorThe daemon replied with rpc_error for this request
RpcTimeoutErrorNo response arrived within rpcTimeoutMs — an operation-level failure only; the socket is left open

Constructor options

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);
MemberDescription
createAgent(req)create_agent_request RPC
agent(agentId)Scoped actions for an existing agent (send, interrupt, update, resume, archive, delete, onUpdate, timeline.fetch/.subscribe)
workspace(workspaceId)Scoped actions for a workspace
providerslistProviders(), listModels(provider), listModes(provider), refreshSnapshot()
onAgentUpdate(handler) / onWorkspaceUpdate(handler)Subscribe to broadcasts across all agents/workspaces
connectionEscape hatch back to the underlying DaemonClient
importAgentSession(daemon, args)Named export — resume a provider-native session by handle

Auto-reconnect

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

Every reconnect attempt calls daemon.connect(), which re-sends the full hello handshake, so capabilities and identity are always rehydrated transparently.

Terminal frame routing

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

Custom transports

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) is the default Node/browser WebSocket-backed implementation. factory is an injectable (url, protocols?) => AnyWebSocket — inject a stub for tests, or a custom factory to carry a bearer-password subprotocol (there is no built-in password parameter; see web-client's connection-store.ts for that pattern).

Relay transport (E2EE, via @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 implements the exact same Transport contract as createWebSocketTransport — swap it in via DaemonClientOptions.transport and every other DaemonClient/PiStudioClient API works unchanged, whether the daemon is direct or reached through a relay. See @av-pi-studio/relay's README for running a relay server and @av-pi-studio/server's README for pointing a daemon at one.

Design rules

  • RpcTimeoutError never closes the socket. A slow RPC is an operation-level failure, not a connection failure.
  • clientId stays stable across reconnects — it identifies the logical client session, not the physical connection.
  • No DOM/Node-specific globals in daemon-client.ts or the base Transport type — only createWebSocketTransport itself touches the platform WebSocket, and the base driver never imports it directly.

Development

npm run build       # tsc -b
npx vitest run packages/client

Tests inject stub Transport implementations and mock clocks — no real sockets are opened.

版本与发布记录

@av-pi-studio/client 共发布 114 个版本,首次发布于 2026/07/18;过去 12 个月发布了 114 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.0.115latest2026/11/044 天前
0.0.1142026/10/2910 天前
0.0.1132026/10/2613 天前
0.0.1122026/10/2613 天前
0.0.1112026/10/2513 天前
0.0.1102026/10/1029 天前

关于 @av-pi-studio/client 的常见问题

@av-pi-studio/client 是什么?

The client-side library for talking to a Pi-Studio daemon: a low-level WebSocket driver (DaemonClient) plus a high-level, typed SDK facade (PiStudioClient). This package is what @av-pi-studio/cli is built on, and what any future web/mobile/desktop c

如何安装 @av-pi-studio/client?

在项目目录运行 npm install @av-pi-studio/client,或使用 pnpm add / yarn add / bun add。

@av-pi-studio/client 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@av-pi-studio/client 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@av-pi-studio/client 可以商用吗?

@av-pi-studio/client 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@av-pi-studio/client 的下载量有多少?

根据 npm 官方下载统计,@av-pi-studio/client 在 2026/10/07 – 2026/11/05 这 30 天内被下载 1,922 次,日均约 64 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@av-pi-studio/client 还在维护吗?

最新版本 0.0.115 发布于 2026/11/04(4 天前);至今共发布 114 个版本,其中过去 12 个月发布了 114 个;首次发布于 2026/07/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。