@crow-agent/client
v0.2.0
Published
Client library for connecting to a Crow server over the wire protocol.
Downloads
429
Readme
@crow-agent/client
Client for the Crow service protocol. It is transport-neutral: you supply a
ByteTransportFactory that moves ordered bytes, and the client handles the
handshake, request correlation, attachments, and subscriptions on top.
import { Client, type ByteTransport, type ByteTransportFactory } from "@crow-agent/client";
async function openTransport(handlers): Promise<ByteTransport> {
// Wire up a WebSocket, Unix socket, or any ordered byte channel here.
return {
send: async (chunk) => {
// write bytes in call order, honoring backpressure
},
close: () => {},
};
}
const transportFactory: ByteTransportFactory = (handlers) => openTransport(handlers);
const client = await Client.connect({
serverId: "01234567-89ab-4def-8123-456789abcdef",
transportFactory,
});
const hello = client.hello;
if (!hello) throw new Error("handshake did not complete");
const result = await client.request(
{ serverId: hello.serverId },
{ serviceId: "example.service", member: "read", args: [] },
);During connect() the client confirms the physical endpoint reports the
expected logical serverId. Server-wide requests carry that id; every session
request carries the full live target { serverId, sessionId, attachmentId }.
That combined durable address rules out cross-server and cross-session
misrouting, and the server-issued attachment id discards stale frames after a
switch or reattach.
A factory opens one fresh authenticated connection per attempt. Requests are
matched by id, and server-side failures surface as ServerError.
Services and subscriptions
Typed server and session APIs come from Chord service bindings owned by the
application — never from this package. createClientServiceTransport() adapts
a lazily resolved server or session route into a Chord transport, while
request() and subscribeService() stay the low-level primitives. Chord
provides the service-control parsers and the per-subscription state decoder;
@crow-agent/protocol only validates the routed envelope and the strict-JSON
boundary.
Subscribing to a service first yields a complete provider snapshot. The binding
installs that snapshot, then calls start() to release updates buffered
during hydration. Client applies ordered out-of-band attachment changes but
does not build typed service proxies or interpret application contracts, so
observation APIs such as the coding agent's transcript stay ordinary Chord
services whose snapshots and updates pass through untouched.
Disconnects
On disconnect or disposal, in-flight requests fail locally, although accepted
work may still finish remotely before the attachment is released. The client
drops its live attachment route and never reconnects or replays requests on
its own. To resume: call reconnect(), attach again through the application's
management service, and explicitly repeat only the operations known to be
safe.
The local coordinator only offers a stable endpoint and relays traffic. Replaceable server processes own session and worker lifecycle outside the public client protocol.
Transport handler contract
Drive the handlers your factory receives like this:
handlers.onData(chunk)— inbound bytes arrived;handlers.onClose()— the transport closed in an orderly, terminal way;handlers.onError(error)— the transport failed.
Unix-domain sockets
Node.js and Bun consumers can use the dedicated Unix transport instead of writing a factory:
import { Client } from "@crow-agent/client";
import { createUnixTransportFactory } from "@crow-agent/client/unix-transport";
const client = new Client({
serverId: "01234567-89ab-4def-8123-456789abcdef",
transportFactory: createUnixTransportFactory({ path: "/tmp/crow.sock" }),
});
await client.connect();discoverUnixServers() scans an explicit physical-route directory, derives
each expected server id from its filename, and confirms it with the usual
handshake:
import { discoverUnixServers } from "@crow-agent/client/unix-transport";
const routes = await discoverUnixServers({ directory: "/run/user/1000/crow" });
// [{ serverId: "...", path: "/run/user/1000/crow/<serverId>.sock" }]Malformed entries, non-sockets, stale or silent endpoints, and server-id
mismatches are skipped. Discovery is read-only and probes at most 16 sockets
at once; unexpected filesystem and socket errors fail the whole discovery run.
Pass timeoutMs to change the default probe timeout.
ClientOptions.maxFrameLength caps protocol payloads and maxPendingBytes
caps queued Unix transport output. Set matching limits on both peers.
