Skip to content
Theme:

TypeScript SDK

The TypeScript SDK allows you to programatically define agents, start sessions, send prompts, stream updates, and attach MCP tools.

Terminal window
npm install @aether-agent/sdk

The package depends on @aether-agent/cli, which bundles the aether binary for your platform. You do not need to install the CLI separately.

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}`);
}
}

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.

OptionDescription
cwdWorking directory for the spawned aether acp process. Defaults to process.cwd().
agentAgent name from .aether/settings.json.
modelDirect model id, such as anthropic:claude-sonnet-4-5.
reasoningEffort"minimal", "low", "medium", "high", "xhigh", or "max" when using a direct model.
settingsInline Aether settings object using the .aether/settings.json shape. SDK-hosted and external MCP servers live here under mcps.
settingsFilePath to an alternate settings JSON file.
binaryPathOverride the bundled CLI binary with an absolute path or command available on PATH.
envEnvironment variables for the spawned aether acp process.
logDirCustom directory for ACP logs.
providersProvider connection overrides, such as custom Bedrock endpoints or auth behavior.
traceContextA remote W3C traceparent with optional tracestate, or a standalone traceId for root spans without a parent. See Telemetry for validation and sampling semantics.
abortSignalCancel the active session and tear down the subprocess.
onPermissionRequestCustom policy for ACP permission requests. Defaults to autoApprovePermissions.
onElicitationHandler for Aether’s _aether/elicitation extension request.

settings and settingsFile are mutually exclusive.

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.

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);

runHeadless() resolves to an AetherHeadlessResult:

FieldTypeDescription
stdoutstringCaptured stdout from the aether headless process.
stderrstringCaptured stderr (diagnostics/tracing).
exitCodenumberProcess exit code. 0 on success; non-zero when the turn ends with a failed outcome.
signalNodeJS.Signals | nullTermination 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.

AetherHeadlessOptions mirrors the headless CLI flags plus SDK process controls:

OptionDescription
promptRequired. Prompt text to run.
agentNamed agent from .aether/settings.json. Mutually exclusive with model.
modelDirect model id, such as anthropic:claude-sonnet-4-5. Mutually exclusive with agent.
cwdWorking directory. Defaults to process.cwd().
settingsInline Aether settings object. Mutually exclusive with settingsFile.
settingsFilePath to an alternate settings JSON file.
systemPromptAdditional system prompt text.
output"text", "pretty", or "json". Defaults to "text". See output formats.
eventsEvent kinds to emit (only meaningful with output: "json"). See event filtering.
verboseVerbose diagnostic logging to stderr.
providersProvider connection overrides, such as custom Bedrock endpoints or auth behavior.
traceContextA remote W3C traceparent with optional tracestate, or a standalone traceId. See Telemetry.
binaryPathOverride the bundled CLI binary with an absolute path or command available on PATH.
envEnvironment 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".
abortSignalCancel the run and tear down the subprocess.

Unlike AetherSession.start(), headless runs are non-interactive, so there are no permission or elicitation handlers.

session.prompt() yields AetherMessage objects. Each has a type discriminator:

TypeDescription
session_updateAn ACP session update (agent text, tool calls, tool results, progress, etc.). Access .update.
ext_notificationA custom extension notification (e.g. plan or task updates). Access .method and .params.
resultThe prompt finished. .stopReason describes why (e.g. end_turn, cancelled).
errorAn 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.

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.

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],
},
],
},
});

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 ..." },
},
},
},
],
},
});

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 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.