TypeScript SDK
The TypeScript SDK allows you to programatically define agents, start sessions, send prompts, stream updates, and attach MCP tools.
Install
Section titled “Install”npm install @aether-agent/sdkThe package depends on @aether-agent/cli, which bundles the aether binary for your platform. You do not need to install the CLI separately.
Run an agent
Section titled “Run an agent”import { AetherSession } from "@aether-agent/sdk";
await using session = await AetherSession.start({ cwd: process.cwd(), agent: "planner",});
for await (const message of session.prompt("Find TODOs in this repo")) { if (message.type === "session_update") { console.log(message.update); }
if (message.type === "result") { console.log(`Agent stopped: ${message.stopReason}`); }}Selecting an agent or model
Section titled “Selecting an agent or model”AetherSession.start() accepts either an Aether agent name from .aether/settings.json or a direct model id.
await AetherSession.start({ cwd: "/path/to/repo", agent: "codebase-explorer",});
await AetherSession.start({ cwd: "/path/to/repo", model: "anthropic:claude-sonnet-4-5", reasoningEffort: "high",});agent and model are mutually exclusive. reasoningEffort requires model.
Session options
Section titled “Session options”| Option | Description |
|---|---|
cwd | Working directory for the spawned aether acp process. Defaults to process.cwd(). |
agent | Agent name from .aether/settings.json. |
model | Direct model id, such as anthropic:claude-sonnet-4-5. |
reasoningEffort | "minimal", "low", "medium", "high", "xhigh", or "max" when using a direct model. |
settings | Inline Aether settings object using the .aether/settings.json shape. SDK-hosted and external MCP servers live here under mcps. |
settingsFile | Path to an alternate settings JSON file. |
binaryPath | Override the bundled CLI binary with an absolute path or command available on PATH. |
env | Environment variables for the spawned aether acp process. |
logDir | Custom directory for ACP logs. |
providers | Provider connection overrides, such as custom Bedrock endpoints or auth behavior. |
traceContext | A remote W3C traceparent with optional tracestate, or a standalone traceId for root spans without a parent. See Telemetry for validation and sampling semantics. |
abortSignal | Cancel the active session and tear down the subprocess. |
onPermissionRequest | Custom policy for ACP permission requests. Defaults to autoApprovePermissions. |
onElicitation | Handler for Aether’s _aether/elicitation extension request. |
settings and settingsFile are mutually exclusive.
Multi-turn sessions
Section titled “Multi-turn sessions”A session is stateful. Send follow-up prompts on the same session to continue the conversation.
await using session = await AetherSession.start({ cwd: process.cwd() });
for await (const message of session.prompt("Explain the architecture")) { console.log(message);}
for await (const message of session.prompt("Now list likely refactors")) { console.log(message);}Only one prompt can be in progress per session.
Run a single headless prompt
Section titled “Run a single headless prompt”runHeadless() is the programmatic equivalent of aether headless. It runs one prompt to completion (with no streaming iterator), captures the process output, and resolves with a result. Use it for scripts, CI jobs, and batch workflows where you want the final output rather than streamed updates.
import { runHeadless } from "@aether-agent/sdk";
const result = await runHeadless({ cwd: process.cwd(), prompt: "Summarize the architecture", output: "text",});
console.log(result.stdout);Result
Section titled “Result”runHeadless() resolves to an AetherHeadlessResult:
| Field | Type | Description |
|---|---|---|
stdout | string | Captured stdout from the aether headless process. |
stderr | string | Captured stderr (diagnostics/tracing). |
exitCode | number | Process exit code. 0 on success; non-zero when the turn ends with a failed outcome. |
signal | NodeJS.Signals | null | Termination signal, if the process was killed instead of exiting normally. |
Because aether headless exits non-zero on a failed turn, runHeadless() rejects with an AetherSdkError (code: "process_exited") in that case instead of resolving. Wrap the call in try/catch (or read the error’s details) when a failed turn is an expected outcome.
Options
Section titled “Options”AetherHeadlessOptions mirrors the headless CLI flags plus SDK process controls:
| Option | Description |
|---|---|
prompt | Required. Prompt text to run. |
agent | Named agent from .aether/settings.json. Mutually exclusive with model. |
model | Direct model id, such as anthropic:claude-sonnet-4-5. Mutually exclusive with agent. |
cwd | Working directory. Defaults to process.cwd(). |
settings | Inline Aether settings object. Mutually exclusive with settingsFile. |
settingsFile | Path to an alternate settings JSON file. |
systemPrompt | Additional system prompt text. |
output | "text", "pretty", or "json". Defaults to "text". See output formats. |
events | Event kinds to emit (only meaningful with output: "json"). See event filtering. |
verbose | Verbose diagnostic logging to stderr. |
providers | Provider connection overrides, such as custom Bedrock endpoints or auth behavior. |
traceContext | A remote W3C traceparent with optional tracestate, or a standalone traceId. See Telemetry. |
binaryPath | Override the bundled CLI binary with an absolute path or command available on PATH. |
env | Environment variables for the spawned aether headless process. |
stdout | "pipe" (capture, the default) or "inherit" (write directly to the parent stdio). |
stderr | "pipe" (the default) or "inherit". |
abortSignal | Cancel the run and tear down the subprocess. |
Unlike AetherSession.start(), headless runs are non-interactive, so there are no permission or elicitation handlers.
Messages
Section titled “Messages”session.prompt() yields AetherMessage objects. Each has a type discriminator:
| Type | Description |
|---|---|
session_update | An ACP session update (agent text, tool calls, tool results, progress, etc.). Access .update. |
ext_notification | A custom extension notification (e.g. plan or task updates). Access .method and .params. |
result | The prompt finished. .stopReason describes why (e.g. end_turn, cancelled). |
error | An unrecoverable error occurred. Access .error. |
A result message is always emitted last (unless the stream errors), so it is a reliable place to stop consuming the iterator.
TypeScript tools
Section titled “TypeScript tools”Use tool() to define a TypeScript function and mcp() to host one or more of
them as an MCP server. Tool handlers run in your Node.js process, so closures and
in-memory state work normally.
import { AetherSession, mcp, tool } from "@aether-agent/sdk";import { z } from "zod";
let submitted: { answer: string } | null = null;
const submitAnswer = tool({ name: "submit_answer", description: "Submit the final answer", inputSchema: { answer: z.string() }, handler: async ({ answer }) => { submitted = { answer }; return { content: [{ type: "text", text: "Submitted." }] }; },});
await using custom = await mcp({ name: "custom", tools: [submitAnswer] });
await using session = await AetherSession.start({ cwd: process.cwd(), settings: { agents: [], mcps: [custom.spec], },});
for await (const _message of session.prompt( "Call custom__submit_answer with the final answer.",)) { // Consume streamed agent updates until the prompt completes.}
console.log(submitted);The handle implements Symbol.asyncDispose, so await using stops the server on
scope exit.
Per-agent tools
Section titled “Per-agent tools”A spec on the top-level mcps is available to every agent. Put it on a single
agent’s mcps instead to scope those tools to that agent.
await using planner = await mcp({ name: "planner-tools", tools: [plan] });await using reviewer = await mcp({ name: "reviewer-tools", tools: [review] });await using session = await AetherSession.start({ settings: { agents: [ { name: "planner", description: "Planner", model: "anthropic:claude-sonnet-4-5", userInvocable: true, mcps: [planner.spec], }, { name: "reviewer", description: "Reviewer", model: "anthropic:claude-sonnet-4-5", userInvocable: true, mcps: [reviewer.spec], }, ], },});External MCP servers
Section titled “External MCP servers”Attach external MCP servers by adding an inline source to settings.mcps.
await using session = await AetherSession.start({ cwd: process.cwd(), settings: { agents: [], mcps: [ { type: "inline", servers: { filesystem: { type: "stdio", command: "uvx", args: ["mcp-server-filesystem", process.cwd()], }, remote: { type: "http", url: "https://mcp.example.com/mcp", headers: { Authorization: "Bearer ..." }, }, }, }, ], },});Permissions and elicitation
Section titled “Permissions and elicitation”By default, the SDK uses autoApprovePermissions, which selects the first allow_* permission option. That is convenient for trusted development contexts. For production or untrusted prompts, provide an explicit permission policy.
import { AetherSession } from "@aether-agent/sdk";
await AetherSession.start({ onPermissionRequest: async (request) => { const safeOption = request.options.find( (option) => option.kind === "allow_once", ); return safeOption ? { outcome: { outcome: "selected", optionId: safeOption.optionId } } : { outcome: { outcome: "cancelled" } }; }, onElicitation: async (request) => { console.log(request.params); return { action: "cancel" }; },});Provider overrides
Section titled “Provider overrides”Provider overrides can route a provider to a custom endpoint or change auth behavior.
await AetherSession.start({ model: "bedrock:anthropic.claude-sonnet-4-5-20250929-v1:0", providers: { bedrock: { url: "http://127.0.0.1:8787", auth: "none", }, },});Set auth: "none" only when a trusted proxy injects or signs provider authentication.
The onElicitation response action may be "accept" (with optional content), "decline", or "cancel". When onElicitation is omitted, the SDK cancels by default.