Skip to content

TypeScript SDK

The official TypeScript client for Agent on Demand. Covers every endpoint with typed models, a single async Client, and an AsyncIterable SSE event stream. Works in Node 18+ and modern browsers with zero runtime dependencies.

Install

npm install @ravi-hq/aod-sdk

Quickstart

import { Client } from "@ravi-hq/aod-sdk";

const client = new Client({
  baseUrl: "http://localhost:8777",
  token: "aod_...",
  // or set AOD_API_URL / AOD_API_TOKEN in env (Node only)
});

const agent = await client.agents.create({
  name: "hello",
  provider: "anthropic",
  model: "claude-sonnet",
});

const ack = await client.sessions.create({
  agent_id: agent.id,
  prompt: "Say hello.",
  timeout: 120,
});

const stream = await client.sessions.stream(ack.id);
try {
  for await (const event of stream) {
    if (event.type === "output") {
      process.stdout.write(event.extra.data ?? "");
    } else if (event.type === "exit") {
      console.log(`\n[exit ${event.extra.code}]`);
      break;
    }
  }
} finally {
  await stream.close();
}

Configuration

baseUrl and token can be passed to the constructor or read from AOD_API_URL and AOD_API_TOKEN environment variables (Node only). baseUrl defaults to http://localhost:8777.

Option Default Notes
baseUrl AOD_API_URL or localhost:8777 Trailing slash stripped.
token AOD_API_TOKEN Required. Sent as Authorization: Bearer <token>.
fetch globalThis.fetch Inject a custom fetch for tests or proxies.
timeoutMs 30000 Per-request timeout. Streaming requests use AbortSignal.

Feature summary

Capability Where it lives
Typed resources client.agents, client.environments, client.sessions
Typed models Agent, Environment, Session, SessionAck, SessionTurn, StreamEvent, …
Typed error hierarchy AodHTTPErrorNotFoundError, ConflictError, ValidationError, RateLimitError, AuthError, ServerError
Multi-turn client.sessions.prompt(sessionId, { prompt })SessionAck
Session teardown client.sessions.terminate(sessionId), client.sessions.delete(sessionId)
Turn history client.sessions.turns(sessionId)SessionTurn[]
SSE stream (AsyncIterable, close()) client.sessions.stream(sessionId, { since?, signal? })
Version history client.agents.versions(agentId) / client.environments.versions(environmentId)

Errors

Non-2xx responses throw a typed subclass of AodHTTPError. All share .statusCode, .detail, .method, .url:

import { ConflictError } from "@ravi-hq/aod-sdk";

try {
  await client.agents.update(agentId, { version: 1, name: "renamed" });
} catch (err) {
  if (err instanceof ConflictError) {
    // 409: stale version or archived row
    console.error(err.statusCode, err.detail);
  }
}
Status Class When
401 AuthError Missing or invalid token
404 NotFoundError Resource missing
409 ConflictError Archived row, terminal session, or stale version
422 ValidationError Server-side validation failure
429 RateLimitError Per-user concurrent session limit (.limit, .active)
5xx ServerError

Optimistic concurrency

agents and environments carry a version integer. Pass the current version on every PUT; a stale version throws ConflictError:

const agent = await client.agents.get(agentId);
await client.agents.update(agent.id, {
  version: agent.version,
  name: "renamed",
});

See Core Concepts → Optimistic concurrency for the full semantics.

Streaming

client.sessions.stream(sessionId, opts?) returns a StreamHandle — an AsyncIterable<StreamEvent> with a close() method.

const stream = await client.sessions.stream(sessionId, { since: lastSeenId });
try {
  for await (const event of stream) {
    switch (event.type) {
      case "stage":
        console.log(`[${event.extra.stage} ${event.extra.state}]`);
        break;
      case "output":
        process.stdout.write(event.extra.data ?? "");
        break;
      case "exit":
        console.log(`\n[exit ${event.extra.code}]`);
        return;
      case "error":
      case "terminated":
      case "stale":
        console.error(`\n[${event.type}]`);
        return;
    }
  }
} finally {
  await stream.close();
}

Pass since: lastSeenId to resume after a disconnect. Pass signal: abortController.signal to cancel from outside.

Event types: start, turn_start, output, stage, exit, error, terminated, stale. Everything except type and id lands in event.extra. See Streaming reference for the full event schema.

Multi-turn sessions

After a session reaches completed, call client.sessions.prompt() to send a follow-up. The agent resumes in the same Sprite with the same filesystem and conversation history.

import { Client, ConflictError } from "@ravi-hq/aod-sdk";
import type { SessionAck } from "@ravi-hq/aod-sdk";

const client = new Client({ token: "aod_..." });

// Turn 1
const ack = await client.sessions.create({
  agent_id: agentId,
  prompt: "List the TypeScript files here.",
});
const turn1 = ack.current_turn;
const stream1 = await client.sessions.stream(ack.id);
try {
  for await (const event of stream1) {
    if (event.type === "output" && event.extra.turn === turn1) {
      process.stdout.write((event.extra.data as string) ?? "");
    } else if (
      event.type === "exit" ||
      event.type === "error" ||
      event.type === "terminated" ||
      event.type === "stale"
    ) {
      break;
    }
  }
} finally {
  await stream1.close();
}

// Turn 2 — only valid once session is `completed`
let ack2: SessionAck;
try {
  ack2 = await client.sessions.prompt(ack.id, {
    prompt: "Now summarise what each file does.",
  });
} catch (err) {
  if (err instanceof ConflictError) {
    // session is pending, running, failed, or terminated — inspect err.detail
    // and either retry (pending/running) or start a new session (failed/terminated)
    throw err;
  } else {
    throw err;
  }
}

const turn2 = ack2.current_turn;
const stream2 = await client.sessions.stream(ack.id);
try {
  for await (const event of stream2) {
    if (event.type === "output" && event.extra.turn === turn2) {
      process.stdout.write((event.extra.data as string) ?? "");
    } else if (
      event.type === "exit" ||
      event.type === "error" ||
      event.type === "terminated" ||
      event.type === "stale"
    ) {
      break;
    }
  }
} finally {
  await stream2.close();
}

prompt() returns a SessionAck with the updated current_turn. Only completed sessions accept a prompt — running, pending, failed, and terminated all throw ConflictError (409). See Core Concepts → Session state machine.

Browser use

The SDK has no Node-only dependencies — it uses built-in fetch, ReadableStream, and AbortController. Pass baseUrl and token explicitly in browser code; the AOD_API_URL / AOD_API_TOKEN env fallbacks are Node-only.

If you're calling Agent on Demand from a browser origin other than the API's own origin, the server must send appropriate CORS headers (not enabled by default on self-hosted instances).

See also