Skip to main content

JavaScript Client

The JavaScript client (Node.js or modern browsers) talks to the Pie server over WebSocket. It mirrors the Python client surface, with camelCase method names.

Installation

npm install pie-client

Quick start

import { PieClient } from 'pie-client';

const client = new PieClient("ws://127.0.0.1:8080");
await client.connect();

// Internal-token auth (used between backend processes).
// For public-key auth, use the Python or Rust client today.
await client.authByToken(process.env.PIE_TOKEN);

const proc = await client.launchProcess(
"text-completion@0.1.0",
{ prompt: "Hello", max_tokens: 64 },
);

while (true) {
const { event, value } = await proc.recv();
if (event === "stdout") process.stdout.write(value);
else if (event === "return") { console.log("\n[done]", value); break; }
else if (event === "error") { console.error("\n[error]", value); break; }
}

await client.close();

PieClient

const client = new PieClient(serverUri);
await client.connect();
await client.ping();
await client.close();
MethodDescription
connect()Open the WebSocket connection. Resolves once the socket is open.
close()Close the connection.

Authentication

MethodDescription
authByToken(token)Authenticate with an internal token. The only auth mechanism currently exposed by the JS client.

If the server has [auth].enabled = false, you can skip authentication entirely.

Programs

MethodDescription
resolveVersion(name, registryUrl)Returns name@version; passes through if name already includes @.
checkProgram(inferlet)Returns true if the server already has this name@version. (programExists is an alias.)
installProgram(wasmPath, manifestPath, forceOverwrite=false)Node.js only. Reads the WASM and Pie.toml from disk and uploads them in chunks.

Processes

MethodDescription
launchProcess(inferlet, input={}, captureOutputs=true, { tokenBudget=null }={})Launch a name@version. input is JSON-stringified into the inferlet's typed input. (launchInstance is an alias.)
attachProcess(processId)Reattach to an existing process by id.
signalProcess(processId, message)Fire-and-forget string signal.
terminateProcess(processId)Stop a process.
listProcesses()Return active process ids.
launchDaemon(inferlet, port, input={})Start a long-running daemon inferlet bound to port.
ping()Round-trip a ping.
query(subject, record)Generic key-value query passthrough.

launchProcess and attachProcess resolve to a Process.

MCP

MethodDescription
registerMcpServer(name, { transport, command, args, url })Spawn a local MCP server and announce it to the engine under name. Inferlets launched on this client see the server via mcp.availableServers(). Today only transport: 'stdio' is supported; pass command and args.
await client.registerMcpServer('fs', {
transport: 'stdio',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp/sandbox'],
});

Process

interface Process {
processId: string;

signal(message: string): Promise<void>;
transferFile(fileBytes: Uint8Array | Buffer): Promise<void>;
recv(): Promise<{ event: string; value: string | Uint8Array }>;
terminate(): Promise<void>;
}
MethodDescription
recv()Await the next event. Returns { event, value }.
signal(message)Send a string signal to the running inferlet.
transferFile(bytes)Send a binary file (chunked).
terminate()Request termination.

Instance is exported as a backwards-compatible alias for Process.

Receive loop pattern

const proc = await client.launchProcess("my-inferlet@0.1.0", { prompt });

while (true) {
const { event, value } = await proc.recv();
switch (event) {
case "stdout": process.stdout.write(value); break;
case "stderr": process.stderr.write(value); break;
case "message": handleMessage(value); break;
case "file": saveBlob(value); break; // Uint8Array
case "return": return JSON.parse(value); // typed Output
case "error": throw new Error(value);
}
}

Events

Events are passed as plain strings (no enum):

EventPayloadDescription
"stdout"stringStreamed stdout.
"stderr"stringStreamed stderr.
"message"stringInferlet-emitted message.
"file"Uint8ArrayInferlet-emitted file (reassembled from chunks).
"return"stringFinal JSON-encoded Output; the process exits cleanly after this.
"error"stringThe process aborted.

The event queue for a process is cleaned up automatically once return or error is delivered.

Detached / reattach

launchProcess returns a handle you can drop. Reconnect later, call attachProcess(processId), and resume reading. While disconnected, the server buffers events for you.

const c1 = new PieClient(uri); await c1.connect();
const proc = await c1.launchProcess("long-task@0.1.0", { topic: "KV cache reuse" });
const pid = proc.processId;
await c1.close();

// … later …
const c2 = new PieClient(uri); await c2.connect();
const resumed = await c2.attachProcess(pid);
const { event, value } = await resumed.recv();

Browser vs. Node.js

  • installProgram requires fs and is Node.js-only.
  • Everything else (authByToken, launchProcess, recv, file transfer with in-memory Uint8Array) works in modern browsers.