> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rkat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Getting started with the Meerkat TypeScript SDK: sessions, turns, streaming events, and capabilities.

The TypeScript SDK (`@rkat/sdk`) is a thin wrapper over Meerkat's settled runtime-backed contracts. It spawns a local `rkat-rpc` subprocess and exposes the same session lifecycle used by the CLI, REST, JSON-RPC, and MCP surfaces as TypeScript-native `Session` and `DeferredSession` handles.

## Getting started

<Steps>
  <Step title="Install the SDK">
    ```bash theme={null}
    npm install @rkat/sdk
    ```
  </Step>

  <Step title="Install the RPC binary">
    ```bash theme={null}
    cargo build -p meerkat-rpc --release
    # Ensure target/release/rkat-rpc is on your $PATH
    ```

    By default the SDK can also auto-resolve and download `rkat-rpc` for the current platform, so a manual binary install is optional unless you want to control the binary path yourself.

    You also need an API key for at least one LLM provider (e.g. `ANTHROPIC_API_KEY`).
  </Step>

  <Step title="Configure tsconfig">
    The SDK is ESM. Your `tsconfig.json` must use Node16 module resolution:

    ```json theme={null}
    {
      "compilerOptions": {
        "target": "ES2022",
        "module": "Node16",
        "moduleResolution": "Node16",
        "strict": true
      }
    }
    ```
  </Step>

  <Step title="Connect and run">
    ```typescript theme={null}
    import { MeerkatClient } from "@rkat/sdk";

    const client = new MeerkatClient();
    await client.connect();

    const session = await client.createSession("What is the capital of Sweden?");
    console.log(session.text);
    console.log(session.id);

    const result = await session.turn("And what is its population?");
    console.log(result.text);

    await session.archive();
    await client.close();
    ```
  </Step>
</Steps>

***

## Method overview

### MeerkatClient methods

| Method                                                                                                                                                           | Description                                                                  |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `connect(options?)`                                                                                                                                              | Spawn `rkat-rpc`, handshake, fetch capabilities                              |
| `close()`                                                                                                                                                        | Kill the subprocess                                                          |
| `createSession(prompt, options?)`                                                                                                                                | Create a session, run the first turn, return a `Session`                     |
| `createSessionStreaming(prompt, options?)`                                                                                                                       | Create a session and stream typed events from the first turn                 |
| `listSessions()`                                                                                                                                                 | List active sessions                                                         |
| `readSession(sessionId)`                                                                                                                                         | Read typed session metadata (`SessionInfo`)                                  |
| `readSessionHistory(sessionId, options?)`                                                                                                                        | Read committed session transcript history                                    |
| `sendExternalEvent(sessionId, eventType, payload, options?)`                                                                                                     | Inject a canonical external event into a session                             |
| `injectContext(sessionId, text, options?)`                                                                                                                       | Append runtime system context                                                |
| `getModelsCatalog()`                                                                                                                                             | Read config-backed model catalog                                             |
| `createSchedule/getSchedule/listSchedules/updateSchedule/pauseSchedule/resumeSchedule/deleteSchedule/listScheduleOccurrences/listScheduleTools/callScheduleTool` | Schedule API wrappers                                                        |
| `createMob(options?)`                                                                                                                                            | Create a mob, return a `Mob`                                                 |
| `createDeferredSession(prompt, options?)`                                                                                                                        | Create a session without running the first turn; returns a `DeferredSession` |
| `listMobs()`                                                                                                                                                     | List mobs                                                                    |
| `getConfig()`                                                                                                                                                    | Read runtime configuration                                                   |
| `setConfig(config)`                                                                                                                                              | Replace runtime configuration                                                |
| `patchConfig(patch)`                                                                                                                                             | Merge-patch runtime configuration                                            |
| `liveOpen/liveStatus/liveClose/liveSendInput/liveCommitInput/liveInterrupt/liveTruncate/liveRefresh`                                                             | Direct wrappers for the `live/*` RPC surface                                 |

### Auth wrappers

The SDK also exposes the auth-profile wrappers from the RPC surface:

* `authProfileList(...)`, `authProfileGet(...)`, `authProfileCreate(...)`, `authProfileDelete(...)`
* `authLoginStart(...)`, `authLoginComplete(...)`, `authLoginDeviceStart(...)`, `authLoginDeviceComplete(...)`, `authLoginProvisionApiKey(...)`
* `authStatusGet(...)`, `authLogout(...)`
* `realmList(...)`, `realmGet(...)`
* `sendPeerResponseTerminal(...)`
* `mcpAdd(...)`, `mcpRemove(...)`, `mcpReload(...)`
* `getBlob(...)`, `listSkills()`

### Generated assistant images

When a session model calls the built-in `generate_image` tool, generated images appear in committed history as `SessionAssistantBlock` entries with `blockType === "image"`. The SDK preserves `imageId`, `blobId`, `mediaType`, `width`, `height`, `revisedPrompt`, and provider metadata.

```typescript theme={null}
const history = await session.history();
const image = history.messages
  .flatMap((message) => [...message.blocks])
  .find((block) => block.blockType === "image");
const payload = image?.blobId ? await client.getBlob(image.blobId) : undefined;
```

Image-generation wire contracts are exported for integrations that inspect tool results directly: `WireGenerateImageRequest`, `WireGenerateImageExecutionPlan`, `WireImageGenerationToolResult`, `WireImageOperationPhase`, and `WireAssistantImageRef`.

### Capability methods

| Method                         | Description                                        |
| ------------------------------ | -------------------------------------------------- |
| `client.capabilities`          | Read-only array of all `Capability` objects        |
| `client.hasCapability(id)`     | Returns `true` if capability is `"Available"`      |
| `client.requireCapability(id)` | Throws `CapabilityUnavailableError` if unavailable |

### Session methods

| Method                                  | Description                                                                           |
| --------------------------------------- | ------------------------------------------------------------------------------------- |
| `session.turn(prompt, options?)`        | Run another turn (non-streaming), returns `RunResult`                                 |
| `session.stream(prompt, options?)`      | Run another turn with streaming, returns `EventStream`                                |
| `session.history(options?)`             | Read committed transcript history for this session                                    |
| `session.injectContext(text, options?)` | Append runtime system context                                                         |
| `session.interrupt()`                   | Cancel the currently running turn                                                     |
| `session.archive()`                     | Remove the session from the server                                                    |
| `session.invokeSkill(skillRef, prompt)` | Invoke a skill in this session                                                        |
| `session.send(command)`                 | Send a typed comms command scoped to this session                                     |
| `session.peers()`                       | List peers visible to this session's comms runtime                                    |
| `session.subscribeEvents()`             | Open a standalone event subscription; returns `EventSubscription<AgentEventEnvelope>` |

### Live channel helper

`RealtimeChannel` and the old realtime convenience helpers are no longer part of
the public SDK. Use `LiveChannel.session(client, session.id, options?)` for a
session-bound wrapper around the `live/*` methods. The SDK returns the transport
bootstrap from `live/open`; callers still own the WebSocket connection to
`openResult.transport.url`.

```typescript theme={null}
import { LiveChannel, MeerkatClient } from "@rkat/sdk";

const client = new MeerkatClient();
await client.connect({ liveWs: true });

const session = await client.createSession("Open a live channel");
const channel = LiveChannel.session(client, session.id, {
  turningMode: "explicit_commit",
});

const openResult = await channel.open();
// Connect to openResult.transport.url externally.
await channel.sendInputText("hello");
await channel.commitInput("text");
await channel.close();
```

### Mob methods

| Method                                                   | Description                                                                   |
| -------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `mob.mobId`                                              | The mob's identifier                                                          |
| `mob.status()`                                           | Get mob status                                                                |
| `mob.lifecycle(action)`                                  | Stop, resume, complete, destroy, or reset the mob                             |
| `mob.spawn(spec)`                                        | Spawn a member                                                                |
| `mob.spawnMany(specs)`                                   | Batch-spawn members                                                           |
| `mob.retire(agentIdentity)`                              | Retire a member                                                               |
| `mob.respawn(agentIdentity)`                             | Respawn a member                                                              |
| `mob.wire(a, b)`                                         | Wire two members                                                              |
| `mob.unwire(a, b)`                                       | Remove wiring                                                                 |
| `mob.listMembers()`                                      | List mob members                                                              |
| `mob.member(agentIdentity).send(content, handlingMode?)` | Send work to a member                                                         |
| `mob.readEvents(options?)`                               | Read mob event history (`mob/events`)                                         |
| `mob.listFlows()`                                        | List available flows                                                          |
| `mob.runFlow(flowId, params)`                            | Start a flow run                                                              |
| `mob.flowStatus(runId)`                                  | Get flow run status                                                           |
| `mob.cancelFlow(runId)`                                  | Cancel a flow run                                                             |
| `mob.subscribeMemberEvents(agentIdentity)`               | Subscribe to member events; returns `EventSubscription<AgentEventEnvelope>`   |
| `mob.subscribeEvents()`                                  | Subscribe to mob-wide events; returns `EventSubscription<AttributedMobEvent>` |

***

## MeerkatClient

### Constructor

```typescript theme={null}
new MeerkatClient(rkatPath?: string)
```

Defaults to searching `$PATH` for `rkat-rpc`. If not found, automatically downloads the correct binary for the current platform and caches it in `~/.cache/meerkat/bin/rkat-rpc`. Pass an explicit path or set `MEERKAT_BIN_PATH` to override.

### connect()

```typescript theme={null}
async connect(options?: ConnectOptions): Promise<this>
```

Spawns `rkat-rpc`, performs `initialize` handshake, checks contract version compatibility, and fetches capabilities. Returns `this` for chaining.

```typescript theme={null}
export interface ConnectOptions {
  realmId?: string;
  instanceId?: string;
  realmBackend?: "jsonl" | "sqlite";
  isolated?: boolean;
  stateRoot?: string;
  contextRoot?: string;
  userConfigRoot?: string;
  liveWs?: boolean;
}
```

`realmId` and `isolated` are mutually exclusive. If `realmId` is omitted and `isolated` is not set, the spawned `rkat-rpc` process uses its normal isolated-mode default rather than a shared default realm. Reuse `realmId` explicitly to share sessions and config across processes or surfaces.
Set `liveWs: true` when you need the `live/*` adapter surface; the SDK starts
`rkat-rpc` with an ephemeral live WebSocket listener and `live/open` returns the
transport URL/token for your client to connect.

### createSession()

```typescript theme={null}
async createSession(
  prompt: string | ContentBlock[],
  options?: SessionOptions,
): Promise<Session>
```

Creates a new session, runs the first turn with `prompt`, and returns a `Session` object. The `Session` holds the last `RunResult` and exposes convenience accessors for the most recent text, usage, and tool call counts.

```typescript theme={null}
const session = await client.createSession("Summarise this project", {
  model: "claude-sonnet-4-6",
  systemPrompt: "You are a senior engineer.",
  maxTokens: 4096,
});

console.log(session.text);
console.log(session.usage.inputTokens);
```

### createSessionStreaming()

```typescript theme={null}
createSessionStreaming(
  prompt: string | ContentBlock[],
  options?: SessionOptions,
): EventStream
```

Creates a new session and returns an `EventStream` for the first turn. Iterate the stream to receive typed events as they arrive. The final `RunResult` is available on `stream.result` after iteration completes.

```typescript theme={null}
const stream = client.createSessionStreaming("Explain async/await in TypeScript");

for await (const event of stream) {
  if (event.type === "text_delta") {
    process.stdout.write(event.delta);
  }
}

console.log("\nTokens used:", stream.result.usage.outputTokens);
```

### SessionOptions

All fields are camelCase:

```typescript theme={null}
interface SessionOptions {
  model?: string;
  provider?: string;
  systemPrompt?: string;
  maxTokens?: number;
  outputSchema?: Record<string, unknown>;
  structuredOutputRetries?: number;
  hooksOverride?: Record<string, unknown>;
  enableBuiltins?: boolean;
  enableShell?: boolean;
  enableMemory?: boolean;
  enableMob?: boolean;
  keepAlive?: boolean;
  commsName?: string;
  peerMeta?: Record<string, unknown>;
  budgetLimits?: Record<string, unknown>;
  providerParams?: Record<string, unknown>;
  preloadSkills?: SkillRef[];
  skillRefs?: SkillRef[];
  labels?: Record<string, string>;
  additionalInstructions?: string[];
  appContext?: unknown;
  shellEnv?: Record<string, string>;
  externalTools?: Record<string, unknown>[];
}
```

***

## Session

`createSession()` and `createSessionStreaming()` both produce a `Session` object that acts as the handle for all subsequent turns on the same conversation. The SDK does not own a second execution path; it only wraps the canonical runtime session identity.

### Identity

```typescript theme={null}
session.id    // stable UUID string
session.ref   // optional human-readable reference (string | undefined)
```

### Last-result shortcuts

These accessors always reflect the most recent completed turn:

```typescript theme={null}
session.text            // assistant text from the last turn
session.usage           // Usage from the last turn
session.turns           // number of LLM turns in the last run
session.toolCalls       // number of tool calls in the last run
session.structuredOutput // structured output from the last run (unknown | undefined)
session.lastResult      // the full RunResult object
```

### turn()

```typescript theme={null}
async turn(
  prompt: string | ContentBlock[],
  options?: {
    skillRefs?: SkillRef[];
    flowToolOverlay?: TurnToolOverlay;
    additionalInstructions?: string[];
    keepAlive?: boolean;
    model?: string;
    provider?: string;
    maxTokens?: number;
    systemPrompt?: string;
    outputSchema?: Record<string, unknown>;
    structuredOutputRetries?: number;
    providerParams?: Record<string, unknown>;
  },
): Promise<RunResult>
```

Sends another turn to the session and returns the `RunResult`. Also updates the session's last-result shortcuts.

```typescript theme={null}
const result = await session.turn("Now explain the second point in more detail");
console.log(result.text);
```

### stream()

```typescript theme={null}
stream(
  prompt: string | ContentBlock[],
  options?: {
    skillRefs?: SkillRef[];
    flowToolOverlay?: TurnToolOverlay;
    additionalInstructions?: string[];
    keepAlive?: boolean;
    model?: string;
    provider?: string;
    maxTokens?: number;
    systemPrompt?: string;
    outputSchema?: Record<string, unknown>;
    structuredOutputRetries?: number;
    providerParams?: Record<string, unknown>;
  },
): EventStream
```

Sends another turn and returns an `EventStream`. Iterating it yields typed `AgentEvent` objects. The session's last-result shortcuts are updated when iteration completes.

```typescript theme={null}
for await (const event of session.stream("Elaborate on point three")) {
  if (event.type === "text_delta") {
    process.stdout.write(event.delta);
  }
}
// session.text is now updated
```

### interrupt()

```typescript theme={null}
async interrupt(): Promise<void>
```

Sends `turn/interrupt` for this session. Has no effect if no turn is running.

### archive()

```typescript theme={null}
async archive(): Promise<void>
```

Removes this session from the server. The `Session` object should not be used after calling `archive()`.

### invokeSkill()

```typescript theme={null}
async invokeSkill(skillRef: SkillRef, prompt: string): Promise<RunResult>
```

Calls `requireCapability("skills")`, then runs a turn with the provided structured `SkillKey` injected:

```typescript theme={null}
const result = await session.invokeSkill(
  { sourceUuid: "abc123", skillName: "code-review" },
  "Review this function for safety issues",
);
```

### send() and peers()

```typescript theme={null}
async send(command: CommsCommand): Promise<CommsSendReceipt>
async peers(): Promise<Array<Record<string, unknown>>>
```

Scope comms operations to this session. Requires the `comms` capability.

```typescript theme={null}
await session.send({
  kind: "peer_message",
  to: "agent-b",
  body: "Hello!",
  handling_mode: "queue",
});
const peers = await session.peers();
```

### subscribeEvents()

```typescript theme={null}
async subscribeEvents(): Promise<EventSubscription<AgentEventEnvelope>>
```

Open a standalone event subscription for this session. Returns an async-iterable `EventSubscription` that yields typed `AgentEventEnvelope` objects.

```typescript theme={null}
const sub = await session.subscribeEvents();
for await (const envelope of sub) {
  console.log(envelope.payload.type, envelope.seq);
}
await sub.close();
```

***

## Capabilities

Capabilities are fetched automatically during `connect()`. Use them to guard code paths that depend on optional features.

```typescript theme={null}
// Read all capabilities
console.log(client.capabilities);
// [{ id: "sessions", description: "...", status: "Available" }, ...]

// Check a single capability
if (client.hasCapability("comms")) {
  await session.send({
    kind: "peer_message",
    to: "agent-b",
    body: "Hi",
    handling_mode: "queue",
  });
}

// Assert and throw if unavailable
client.requireCapability("skills");  // throws CapabilityUnavailableError if not available
```

<Accordion title="Known capability IDs">
  | ID                   | Description                   |
  | -------------------- | ----------------------------- |
  | `sessions`           | Session lifecycle             |
  | `streaming`          | Real-time event streaming     |
  | `structured_output`  | JSON schema structured output |
  | `hooks`              | Lifecycle hooks               |
  | `builtins`           | Built-in tools                |
  | `shell`              | Shell tool                    |
  | `comms`              | Inter-agent communication     |
  | `memory_store`       | Semantic memory               |
  | `session_store`      | Session persistence           |
  | `session_compaction` | Context compaction            |
  | `skills`             | Skill loading                 |
</Accordion>

***

## Config management

```typescript theme={null}
const config = await client.getConfig();
await client.setConfig({ ...config.config, model: "claude-opus-4-6" });
const updated = await client.patchConfig({ max_tokens: 8192 });
console.log(updated.config.max_tokens);
```

***

## Examples

<Accordion title="Structured output">
  ```typescript theme={null}
  import { MeerkatClient } from "@rkat/sdk";

  const client = new MeerkatClient();
  await client.connect();

  const session = await client.createSession("List three European capitals", {
    outputSchema: {
      type: "object",
      properties: {
        capitals: { type: "array", items: { type: "string" } },
      },
      required: ["capitals"],
    },
    structuredOutputRetries: 3,
  });

  console.log(session.structuredOutput);
  // { capitals: ["Paris", "Berlin", "Madrid"] }

  await client.close();
  ```
</Accordion>

<Accordion title="Multi-turn conversation">
  ```typescript theme={null}
  import { MeerkatClient } from "@rkat/sdk";

  const client = new MeerkatClient();
  await client.connect();

  const session = await client.createSession("My name is Alice.", {
    model: "claude-sonnet-4-6",
  });

  const result = await session.turn("What is my name?");
  console.log(result.text);  // Mentions "Alice"

  await session.archive();
  await client.close();
  ```
</Accordion>

<Accordion title="Streaming a turn">
  ```typescript theme={null}
  import { MeerkatClient } from "@rkat/sdk";

  const client = new MeerkatClient();
  await client.connect();

  const session = await client.createSession("You are a helpful assistant.", {
    systemPrompt: "Be concise.",
  });

  for await (const event of session.stream("Explain Rust lifetimes in two sentences")) {
    switch (event.type) {
      case "text_delta":
        process.stdout.write(event.delta);
        break;
      case "turn_completed":
        console.log(`\nStop reason: ${event.stopReason}`);
        console.log(`Tokens: ${event.usage.inputTokens} in / ${event.usage.outputTokens} out`);
        break;
    }
  }

  await client.close();
  ```
</Accordion>

<Accordion title="Invoking a skill">
  ```typescript theme={null}
  import { MeerkatClient } from "@rkat/sdk";

  const client = new MeerkatClient();
  await client.connect();

  client.requireCapability("skills");

  const session = await client.createSession("Let's review some code.");

  const result = await session.invokeSkill(
    { sourceUuid: "abc123", skillName: "code-review" },
    "Review this function for correctness and safety",
  );

  console.log(result.text);

  await client.close();
  ```
</Accordion>

<Accordion title="Multimodal prompt (image input)">
  ```typescript theme={null}
  import { MeerkatClient, ContentBlock } from "@rkat/sdk";
  import { readFileSync } from "fs";

  const client = new MeerkatClient();
  await client.connect();

  const imageData = readFileSync("screenshot.png").toString("base64");

  const session = await client.createSession([
    { type: "text", text: "What do you see in this image?" },
    { type: "image", media_type: "image/png", data: imageData },
  ]);

  console.log(session.text);
  await client.close();
  ```
</Accordion>

<Accordion title="Using collectText() for simple streaming">
  ```typescript theme={null}
  import { MeerkatClient } from "@rkat/sdk";

  const client = new MeerkatClient();
  await client.connect();

  const stream = client.createSessionStreaming("Write a haiku about Rust");
  const [fullText, result] = await stream.collectText();

  console.log(fullText);
  console.log("Input tokens:", result.usage.inputTokens);

  await client.close();
  ```
</Accordion>

***

## See also

* [TypeScript SDK reference](/sdks/typescript/reference) - types, events, errors, and version compatibility
* [Rust SDK overview](/rust/overview) - Rust library API
* [RPC reference](/api/rpc) - JSON-RPC protocol specification
