# TypeScript SDK

The whole API from TypeScript and JavaScript. It waits for a machine to be running, sends a request again where that is safe, and throws every refusal as an error with the API's code.

```shell
npm install agental-sdk
```

- Node 20.16 or later, and nothing else. It uses only `fetch` and Web Crypto, so Bun, Deno and Cloudflare Workers should run it too, but it is tested on Node alone.
- An ES module, with its types.
- The [agental command](https://agental.io/docs/cli) is the package `agental`, beside it.

## Make a client

Make an API key in the console, under API keys, and put it in `AGENTAL_API_KEY`. A client made with nothing reads it from there.

```typescript
import { Agental } from "agental-sdk";

const agental = new Agental(); // reads AGENTAL_API_KEY

const sandbox = await agental.sandboxes.create({ size: "small" });
const { exitCode, stdout } = await sandbox.exec("uname -a");
console.log(exitCode, stdout);

await sandbox.files.write("app/main.py", "print('hello')\n");
console.log((await sandbox.exec(["python3", "/opt/agental/app/main.py"])).stdout);

await sandbox.stop(); // the disk is kept
```

- `create` answers when the machine is running. A sandbox you have already is `agental.sandboxes.get(id)`, and `agental.sandboxes.list()` gives all of them.
- A command given as a string is a line of shell, run by `sh -c`. Given as an array, its arguments are passed exactly, with no shell.
- A command gets 60 seconds unless you pass `timeoutMs`, up to `90000`, since a proxy in front of the API stops waiting after 100 seconds. One that runs out is killed and exits `124`. Longer work goes to `sandbox.commands.start`, which takes up to `600000` and answers at once.
- File paths are relative to the work folder, `/opt/agental`. `files.read` gives text, and `files.readBytes` a `Uint8Array`.

## Output as it arrives

```typescript
for await (const event of sandbox.execStream("npm test")) {
  if (event.type === "exit") console.log("exited", event.exitCode);
  else process.stdout.write(event.data);
}
```

For a command that takes more than a minute, `sandbox.commands.run` runs it without holding a request open, and `commands.start` starts it to be looked at later. See [Running commands](https://agental.io/docs/commands).

## Where the key comes from

- `apiKey`, when you pass one.
- Otherwise `AGENTAL_API_KEY`.
- Otherwise the key the [agental command](https://agental.io/docs/cli) keeps on this computer, in `~/.config/agental/credentials.json`, for the API it signed in to and no other.

The API is `baseUrl`, otherwise `AGENTAL_API_URL`, otherwise the API the command signed in to, otherwise `https://agental.io`.

## Options

| Option | What it does |
| --- | --- |
| `apiKey` | The API key. Left out, as above. |
| `baseUrl` | Where the API answers. Left out, as above. |
| `maxRetries` | How many times a request that is safe to send again is sent again. 3 unless you say. |
| `retryDelayMs` | The shortest wait before it is sent again, in milliseconds. 1000 unless you say. |
| `pollIntervalMs` | How often something waited for, such as a machine starting, is looked at, in milliseconds. 2000 unless you say. |
| `fetch` | What requests are sent with. Left out, the runtime's own `fetch`. |

## What is in it

Each guide shows its calls in TypeScript. Where to find them:

| Calls | Guide |
| --- | --- |
| `agental.sandboxes.create`, `get`, `list`; `sandbox.stop`, `start`, `update`, `delete` | [Sandbox lifecycle](https://agental.io/docs/lifecycle) |
| `sandbox.exec`, `execStream`, `commands.run`, `commands.start` | [Running commands](https://agental.io/docs/commands) |
| `sandbox.files.read`, `readBytes`, `write` | [Files](https://agental.io/docs/files) |
| `sandbox.browser` | [Browser](https://agental.io/docs/browser) |
| `sandbox.desktop` | [Desktop](https://agental.io/docs/desktop) |
| `sandbox.snapshot`, `restore`; `agental.snapshots` | [Snapshots](https://agental.io/docs/snapshots) |
| `sandbox.fork` | [Forking](https://agental.io/docs/fork) |
| `agental.templates`, `sandbox.secrets` | [Templates and secrets](https://agental.io/docs/templates) |
| `template.setNetwork`, `sandbox.setNetwork` | [Outbound rules](https://agental.io/docs/outbound-rules) |
| `sandbox.expose`, `routes` | [Public URLs](https://agental.io/docs/ports) |
| `sandbox.email`, `agental.email` | [Email](https://agental.io/docs/email) |
| `agental.createWebhook`, `listWebhooks`; `verifyWebhook`, imported on its own | [Webhooks](https://agental.io/docs/webhooks) |
| `sandbox.metrics`, `agental.metrics`, `runningOverTime` | [Metrics](https://agental.io/docs/metrics) |
| `agental.usage`, `sandbox.usage` | [Usage](https://agental.io/docs/usage) |
| `agental.setTelemetry`, `testTelemetry` | [OpenTelemetry](https://agental.io/docs/opentelemetry) |
| `agental.billing`, `prices`, `checkout`, `burst`, `setBurstBudget` | [Billing](https://agental.io/docs/billing) |
| `agental.apiKeys` | [API keys](https://agental.io/docs/api-keys) |
| `agental.exportAccount` | [Your account and data](https://agental.io/docs/account) |

## What it does for you

- A create or a fork is sent with an `Idempotency-Key`. If its answer is lost, or the machine is not confirmed yet, the same request is sent again with the same key, which gives the same sandbox back and never a second one. Pass `idempotencyKey` yourself to be able to ask again after your process has died.
- Reads, file writes, starts and stops are sent again when a proxy answers in the API's place, when the connection drops, and when the sandbox was moved to another server during the request.
- A command is never sent twice. One that was cut off may have done part of its work, so whether to run it again is yours to decide.
- A delete carries the confirm header the API asks for.

## Errors

```typescript
import { ApiError } from "agental-sdk";

try {
  await sandbox.exec("make");
} catch (error) {
  if (error instanceof ApiError && error.code === "sandbox_not_running") {
    await sandbox.start();
  } else {
    throw error;
  }
}
```

| Error | What it means |
| --- | --- |
| `ApiError` | The API's own refusal, with `status`, `code` and `message`. Decide from `code`: [Errors](https://agental.io/docs/errors) lists them. |
| `ProxyError` | A 502, a 504 or any answer with no code in it: something in front of the API answered, and the request may still be running. |
| `ConnectionError` | The API was not reached. |
| `TimeoutError` | A wait ran out, such as for a machine to start. |

All of them are `AgentalError`. A command that exits non-zero is not an error: read `exitCode`.

> **Next:** The [Python SDK](https://agental.io/docs/python) is the same API in Python. For another language, the whole API is [OpenAPI 3.1](https://agental.io/openapi.json).

---

Every page of these docs as Markdown: [llms.txt](https://agental.io/llms.txt)
