pi-mono/packages/client
Armin Ronacher 984846b785 feat(coding-agent): remove legacy session RPC facade
Route discovery, management, and prompting through Chord services. Retain low-level lane watches and reattach Radius clients through SessionManagement.
2026-08-31 00:13:43 +02:00
..
src feat(coding-agent): remove legacy session RPC facade 2026-08-31 00:13:43 +02:00
test feat(coding-agent): remove legacy session RPC facade 2026-08-31 00:13:43 +02:00
CHANGELOG.md Add [Unreleased] section for next cycle 2026-08-28 23:56:06 +02:00
package.json Release v0.84.4 2026-08-28 23:56:03 +02:00
README.md feat(coding-agent): remove legacy session RPC facade 2026-08-31 00:13:43 +02:00
tsconfig.build.json feat(client): add runtime-neutral session client 2026-07-31 12:19:19 +03:00
tsconfig.test.json feat(protocol): add service-addressed session RPC 2026-08-13 18:24:27 +03:00
vitest.config.ts feat(client): add runtime-neutral session client 2026-07-31 12:19:19 +03:00

@earendil-works/pi-client

Transport-neutral client for the experimental Pi service protocol.

import { Client, type ByteTransportFactory } from "@earendil-works/pi-client";

const transportFactory: ByteTransportFactory = async (handlers) => {
  // Connect using WebSocket, Unix socket, or another ordered byte transport.
  return {
    async send(chunk) {
      // Deliver bytes in invocation order and honor backpressure.
    },
    close() {},
  };
};

const client = await Client.connect({
  serverId: "01234567-89ab-4def-8123-456789abcdef",
  transportFactory,
});
const result = await client.request(
  { serverId: client.hello.serverId },
  { serviceId: "example.service", member: "read", args: [] },
);

The client verifies that the physical endpoint reports the expected logical serverId. Server-wide requests carry that ID, and every Session request carries the full live target { serverId, sessionId, attachmentId }. The combined durable address prevents cross-server or cross-session misrouting; the server-generated attachment ID rejects delayed frames after switching or reattaching.

Typed server and Session APIs are provided by Chord service bindings owned by the application. request() and subscribeService() are low-level transport adapters. A service subscription returns a complete provider snapshot; the binding installs it and then calls start() to release updates buffered during hydration. Client applies ordered out-of-band attachment changes but deliberately does not construct typed service proxies or interpret service contracts.

watchSession() is the remaining low-level compatibility API. It creates a main-lane watch and returns its authoritative snapshot without starting event delivery. Install the listener with await watch.start(listener); this then flushes events buffered after the snapshot and continues with live events. watch.dispose() stops server delivery and waits for already-received listener work. A disconnected watch is stale and cannot be reused after reconnection.

On disconnect or disposal, pending requests reject locally, but accepted work may still complete remotely before the attachment is released. The client clears its live attachment route. It never reconnects or replays requests automatically. After disconnection, call reconnect(), attach through the application's management service again, and explicitly repeat only operations known to be safe.

The experimental local coordinator only provides a stable endpoint and relays traffic. Replaceable server processes own Session and worker lifecycle outside the public client protocol.

Call transport handlers as follows:

  • handlers.onData(chunk) for inbound bytes;
  • handlers.onClose() for an orderly terminal close;
  • handlers.onError(error) for transport failures.

A transport factory creates a fresh authenticated connection for each attempt. Requests are correlated by ID, and server failures are exposed as ServerError.

Unix-domain sockets

Node.js and Bun consumers can use the separate Unix transport:

import { Client } from "@earendil-works/pi-client";
import { createUnixTransportFactory } from "@earendil-works/pi-client/unix";

const client = new Client({
  serverId: "01234567-89ab-4def-8123-456789abcdef",
  transportFactory: createUnixTransportFactory({ path: "/tmp/pi.sock" }),
});
await client.connect();

Unix discovery scans an explicit physical-route directory, derives each expected server ID from its filename, and verifies it through the existing handshake:

import { discoverUnixServers } from "@earendil-works/pi-client/unix";

const routes = await discoverUnixServers({ directory: "/run/user/1000/pi" });
// [{ serverId: "...", path: "/run/user/1000/pi/<serverId>.sock" }]

Malformed entries, non-sockets, stale or unresponsive endpoints, and server-ID mismatches are ignored. Discovery is read-only and probes at most 16 sockets concurrently. Unexpected filesystem and socket errors reject discovery. Pass timeoutMs to override the default probe timeout.

ClientOptions.maxFrameLength bounds protocol payloads. maxPendingBytes bounds queued Unix transport output. Configure matching limits on both peers.