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

# TypeScript SDK

> Control Firedrill worlds and run drills while your TypeScript runner continues to own the agent.

<Note>
  Requires Node.js 20.19 or newer and authorized Firedrill access. The package name is retained for compatibility.
</Note>

The generated `FiredrillClient` maps the Control API. Higher-level helpers create
and control sessions, run drills with a caller-owned agent callback, follow evidence,
and retain recovery checkpoints.

Install the SDK from npm:

```sh theme={null}
npm install @firedrill-run/cloud
```

## Configure the client

```ts theme={null}
import { FiredrillClient } from "@firedrill-run/cloud";

const client = new FiredrillClient({
  token: process.env.FIREDRILL_CREDENTIAL,
  // Defaults to https://api.firedrill.run.
  baseUrl: process.env.FIREDRILL_API_URL,
});
```

Use an opaque control credential issued for your user or service. Do not pass this
client or credential to the agent under test.

## Start an owned world

Examples below are integration skeletons. Implement `runExistingAgent` around your actual agent and `checkpointStore.save` as durable storage. Replace resource IDs with values from your project. The SDK does not supply those functions or an agent.

```ts theme={null}
import { connect, FiredrillClient } from "@firedrill-run/cloud";

const client = new FiredrillClient({
  token: process.env.FIREDRILL_CREDENTIAL,
});

const world = await connect({
  client,
  projectId: "prj_...",
  environmentId: "env_...",
  actorId: "operator",
  seed: "42",
  // scenarioId: "busy-inbox", // optional; do not combine with drillId
});

try {
  await runExistingAgent(world.binding);
  await world.reset();
} finally {
  await world.destroy();
}
```

`connect()` creates one session, waits for readiness, issues actor-scoped world
access, and returns a handle. It does not invoke an agent. Omit `scenarioId` and
`drillId` for the build baseline.

The handle can reset state, advance virtual time, set declared faults, extend the
session, and destroy it. Mutations retain uncertain requests so you can recover
with the same idempotency key instead of issuing a duplicate action.

## Run a drill with your agent

```ts theme={null}
import { FiredrillClient, runHostedDrill } from "@firedrill-run/cloud";

const client = new FiredrillClient({
  token: process.env.FIREDRILL_CREDENTIAL,
});

const run = await runHostedDrill(client, {
  projectId: "prj_...",
  hostedRunId: "hrun_...", // or pass sessionId to create a run
  onCheckpoint: (checkpoint) => checkpointStore.save(checkpoint),
  runInteraction: async ({ interaction, binding, signal, capture }) => {
    capture.log("Starting agent interaction");
    const output = await runExistingAgent({
      task: interaction.task,
      binding,
      signal,
    });
    return {
      schemaVersion: 1,
      status: "completed",
      output,
      attachments: [],
    };
  },
});

console.log(run.hostedRunId, run.state);
```

The callback receives world-only access for the current interaction. The helper
submits the actual callback result, waits through sealing, and returns the
run. A callback result is not itself the verdict; inspect the terminal run and its
assertions.

## Existing CLI-based agents

For an agent that already invokes `firedrill world tools` or `firedrill world call`, use `createHostedCliBridge` inside its interaction callback:

```ts theme={null}
import { createHostedCliBridge } from "@firedrill-run/cloud";

const bridge = await createHostedCliBridge(binding, { signal });
try {
  await runExistingAgent({ task: interaction.task, environment: bridge.environment, signal });
} finally {
  await bridge.close();
}
```

This fragment uses `binding`, `signal`, and `interaction` from the callback above. Your `runExistingAgent` integration must pass the bridge environment only to the intended child process and still return the actual agent result to the drill helper.

The bridge starts a loopback HTTP transport, not a world runtime or an agent. The child receives a separate scoped transport credential; the service continues to enforce the world actor and interaction. Close the bridge even if the agent fails or is cancelled.

## Capture files and media

Pass `root` plus explicit policies to `runHostedDrill`:

```ts theme={null}
await runHostedDrill(client, {
  projectId: "prj_...",
  hostedRunId: "hrun_...",
  root: process.cwd(),
  capture: {
    logs: "always",
    screenshots: "retain-on-failure",
    video: "retain-on-failure",
    files: "off",
  },
  runInteraction: async ({ interaction, binding, signal, capture, attach }) => {
    capture.log("Invoking the agent");
    attach({ path: "artifacts/output.json", mediaType: "application/json" });
    const output = await runExistingAgent({ task: interaction.task, binding, signal });
    return { schemaVersion: 1, status: "completed", output, attachments: [] };
  },
});
```

Firedrill snapshots only files you select below the explicit root. It does not scan
your repository, intercept global logs, or create a browser recording automatically.
Register a capture driver or attach an existing file when you need those artifacts.

## Recovery rules

* Persist each run checkpoint before its callback returns.
* Retry an uncertain mutation with the same request and idempotency key.
* Do not rerun an agent after an upload or completion checkpoint.
* A local abort stops the client wait; it does not prove the server operation stopped.
* Keep control credentials separate from short-lived world bindings.

See [SDK errors](/sdk/errors) for the canonical error envelope.
