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
The JavaScript client is published on npm as @pie-project/client.
npm install @pie-project/client@0.4.0
Quick start
import { PieClient } from '@pie-project/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();
| Method | Description |
|---|---|
connect() | Open the WebSocket connection. Resolves once the socket is open. |
close() | Close the connection. |
Authentication
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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>;
}
| Method | Description |
|---|---|
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):
| Event | Payload | Description |
|---|---|---|
"stdout" | string | Streamed stdout. |
"stderr" | string | Streamed stderr. |
"message" | string | Inferlet-emitted message. |
"file" | Uint8Array | Inferlet-emitted file (reassembled from chunks). |
"return" | string | Final JSON-encoded Output; the process exits cleanly after this. |
"error" | string | The 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
installProgramrequiresfsand is Node.js-only.- Everything else (
authByToken,launchProcess,recv, file transfer with in-memoryUint8Array) works in modern browsers.