@rivetplane/sdk
v0.3.0
Published
First-party TypeScript SDK for the Rivetplane control-plane API
Maintainers
Readme
@rivetplane/sdk
The first-party TypeScript SDK for the Rivetplane control-plane API. It uses standard fetch, streams SSE without EventSource, and uses the standard WebSocket API. The same package works in Node.js 24 or later, Bun, and modern browsers.
Install
npm install @rivetplane/sdkUse the REST API
import { Rivetplane } from "@rivetplane/sdk";
const rivetplane = new Rivetplane({
authentication: process.env.RIVETPLANE_TOKEN!,
});
for (const session of await rivetplane.listSessions({ status: "waiting_approval" })) {
console.log(session.title, session.model?.provider_id, session.model?.model_id);
const pending = await rivetplane.sessions.pending(session.id);
if (pending?.type === "approval") {
await rivetplane.sessions.respondToPending(session.id, {
pending_id: pending.id,
response: "approve",
scope: "once",
});
}
}Authentication can be a token, a function, or an object with getToken(). Use a provider when tokens can rotate:
const rivetplane = new Rivetplane({
baseUrl: "http://127.0.0.1:8080",
authentication: async () => tokenStore.current(),
});The default server is https://rivetplane.com. Set baseUrl only for a self-hosted server or the local runner API.
Session lists support stable time-based pagination:
const sessions = await rivetplane.listSessions({ before: new Date().toISOString(), limit: 100 });Session list and detail responses can include harness-reported identity fields. All are optional so clients remain compatible with adapters that do not report them.
const session = await rivetplane.getSession(sessionId);
console.log(session.title);
console.log(session.model?.provider_id, session.model?.model_id);
console.log(session.agent, session.read_only, session.metadata);Use attention for the fleet-wide approval and question inbox. Pending items include the same optional session identity fields. By default, the server returns actionable items only. listPending() remains as a compatibility alias for attention.list().
const inbox = await rivetplane.attention.list();
const diagnostics = await rivetplane.attention.list({ includeNonActionable: true });
for (const item of inbox) {
console.log(item.pending.id, item.title, item.model, item.agent, item.read_only);
}
const approval = inbox.find((item) => item.pending.type === "approval");
if (approval?.actionable) {
await rivetplane.attention.respond(approval.pending.id, {
response: "approve",
scope: "once",
});
}Harness adapters can report normalized command, description, source, response_mode, and expires_at fields. Consumers should prefer those structured fields and retain tool_input_summary only as a compatibility fallback.
AI usage
Use usage.get() to get token totals, cost semantics, breakdowns, and the latest context and quota data. The getUsage() method is a short alias. All filters are optional.
const usage = await rivetplane.usage.get({
from: "2026-08-25T00:00:00Z",
to: "2026-08-26T00:00:00Z",
machine: "laptop",
harness: "codex",
provider: "openai",
model: "gpt-5.4",
});
console.log(usage.totals.tokens.total);
console.log(usage.totals.cost.coverage); // "complete", "partial", or "none"
if (usage.totals.cost.status === "reported") {
console.log("Reported cost", usage.totals.cost.amount, usage.totals.cost.currency);
} else if (usage.totals.cost.status === "estimated") {
console.log("Estimated cost (not a bill)", usage.totals.cost.amount, usage.totals.cost.currency);
} else {
console.log("Cost unavailable");
}Token fields use null when a source does not report a counter. Cost status is always explicit: reported, estimated, or unavailable. Do not treat an estimate as authoritative billing. Cost summaries also contain coverage, priced_samples, and unavailable_samples, so a priced subset cannot look like complete spend. by_currency can contain separate totals when a single aggregate amount cannot represent multiple currencies. Older harness clients can produce an empty report with unavailable values.
Pagination and streaming
transcriptPages() gets all transcript pages lazily. transcriptEvents() flattens those pages. streamTranscript() reads live SSE events with an authenticated fetch call. Thus, it works in browsers where EventSource cannot set an authorization header.
for await (const event of rivetplane.sessions.transcriptEvents(sessionId, { limit: 100 })) {
console.log(event.type, event.payload);
}
const controller = new AbortController();
for await (const event of rivetplane.sessions.streamTranscript(sessionId, { signal: controller.signal })) {
console.log(event);
}The account-wide WebSocket reconnects with exponential backoff by default. Browser authentication uses Rivetplane's bearer.<base64url-token> subprotocol.
for await (const event of rivetplane.events({
reconnect: { initialDelayMs: 500, maxDelayMs: 10_000 },
})) {
console.log(event.type, event.session_id);
}Node.js 24, Bun, and modern browsers provide the required WebSocket implementation. You can also pass a WHATWG-compatible WebSocket constructor in options.webSocket.
Errors
Non-success HTTP responses throw RivetplaneApiError. It contains status, method, url, body, requestId, and retryable. Transport failures throw RivetplaneNetworkError. Invalid JSON or event data throws RivetplaneProtocolError.
See examples/basic.ts, examples/streaming.ts, and docs/release.md.
