@amarsia/sdk
v1.5.1
Published
The official JavaScript and TypeScript SDK for Amarsia — run, stream, and manage AI assistants.
Maintainers
Readme
@amarsia/sdk
Official TypeScript/JavaScript SDK for Amarsia APIs.
- docs: docs.amarsia.com
- npm: @amarsia/sdk
- repository: amarsia-packages/packages/sdk
- React wrapper: @amarsia/react
@amarsia/sdk gives you a transparent API-style interface with state built in:
- Initialize once with
amarsia.init(...) - Use
client.run(...)for one-shot responses - Use
client.stream(...)for streaming responses - Use
client.conversationfor stateful conversation workflows withconversation.idandconversation.data - Use
client.agentfor durable, reconnectable workflows with client tools
What Is @amarsia/sdk?
@amarsia/sdk is the official Amarsia AI SDK for JavaScript and TypeScript. It provides:
runfor one-shot runner API requestsstreamfor streaming AI outputconversationfor stateful multi-turn chat with conversation historyagentfor durable turns, hidden polling, and explicit or automatic client-tool resolution
It is designed for Node.js, browser apps, Next.js apps, and agentic workflows where you need simple API access with built-in state.
Install
npm install @amarsia/sdkQuick Start
import { amarsia } from "@amarsia/sdk";
const client = amarsia.init({
apiKey: process.env.AMARSIA_API_KEY!,
deploymentId: "dep_123"
});
const result = await client.run({
content: [{ type: "text", text: "Write a short intro for Amarsia." }]
});
console.log(result.content);
console.log(client.run.meta); // token/model metadata when availableInitialization
import { amarsia } from "@amarsia/sdk";
const client = amarsia.init({
apiKey: "YOUR_API_KEY", // optional; required only when the workflow has authentication enabled
deploymentId: "dep_123", // optional default deployment for all calls
baseUrl: "https://api.amarsia.com", // optional, defaults to this
dangerouslyAllowBrowserApiKey: true // optional browser risk acknowledgment
});Public (auth-less) workflows
If a workflow has Authentication turned off in the dashboard, you can
call it without an API key. The request is authorized by the workflow's
Allowlist based on the browser's Origin / Referer, so the
initialization can simply omit apiKey:
const client = amarsia.init({
deploymentId: "dep_123"
});When apiKey is omitted, the SDK will not send the x-api-key header.
If the workflow still requires authentication server-side, the API will
return a 401.
Deployment ID behavior
runandstreamusedeploymentIdfromamarsia.init(...)by default.runandstreamcan still overridedeploymentIdper call.conversationuses the init-level deployment by default.- For most apps, if you need a different deployment for conversations, create a new client instance with
amarsia.init({ deploymentId: "dep_..." }). - If no deployment id is available from either source, SDK throws a configuration error.
API overview
const client = amarsia.init({ apiKey: "...", deploymentId: "dep_123" });
await client.run({ content: [...] });
await client.stream({ content: [...] });
client.conversation.start();
await client.conversation.stream({ content: [...] });
await client.agent.start({ content: [...] });All controllers expose state:
.status->idle | loading | streaming | success | error.data-> latest complete response payload.live-> live stream buffer only during streaming (cleared after completion).error-> typed SDK error object.meta-> token/model/request metadata when present.raw-> raw response payload.getState()and.subscribe(...)for reactive UI updates
.data vs .live
- Use
.livefor live token/chunk rendering while a stream is in progress. - Use
.datafor the final completed response after the call finishes. - For streaming calls,
.liveis intentionally transient and reset to empty on completion.
Run API
const data = await client.run({
content: [{ type: "text", text: "Explain vector search in one paragraph." }],
variables: { audience: "developer" }
});
console.log(data.content);
console.log(client.run.data);Stream API
const unsubscribe = client.stream.subscribe((state) => {
if (state.status === "streaming") {
// progressively updated during stream
console.log(state.live);
}
});
const data = await client.stream({
content: [{ type: "text", text: "Generate a checklist for API launch." }]
});
console.log(data.content); // full final content
unsubscribe();Abort an in-flight stream:
client.stream.abort();Conversation API (stateful)
client.conversation keeps an instance-scoped conversation context (conversation.id, deploymentId, state, history helpers).
1) Start a conversation context
const conversation = client.conversation;
conversation.start(); // new local conversation context, use init deploymentId
conversation.start("conv_existing_123"); // bind to existing conversation idFor most apps, prefer creating a new client instance when switching deployments:
const supportClient = amarsia.init({ apiKey: "...", deploymentId: "dep_support_uuid" });
const salesClient = amarsia.init({ apiKey: "...", deploymentId: "dep_sales_uuid" });Resume an existing conversation id
Store conversation_id from a previous API response in your DB, then resume that thread later:
const conversation = client.conversation;
// Example: loaded from your database/session
const storedConversationId = "conv_abc123";
conversation.start(storedConversationId);
const data = await conversation.run({
content: [{ type: "text", text: "Continue from where we left off." }]
});
console.log(data.conversation_id); // "conv_abc123"Important:
conversation.start(...)is the only place to set conversation id and conversation deployment context.conversation.run(...)andconversation.stream(...)do not accept conversation id or deployment id overrides.- Calling
conversation.start(...)resets local conversation state for the new context. - Do not switch deployment mid-thread and expect to continue the same conversation history.
- The second argument on
conversation.start(conversationId?, deploymentId?)is an advanced option; most users should switch deployments by initializing a new client.
2) Continue conversation (run vs stream)
Use conversation.run(...) for single complete responses and conversation.stream(...) for live chunked output.
// non-stream continuation (final response only)
await conversation.run({
content: [{ type: "text", text: "Summarize the previous answer." }],
historyLimit: 10
});
// stream continuation (live chunks + final response)
await conversation.stream({
content: [{ type: "text", text: "Now explain in bullet points." }],
historyLimit: 10
});Fresh conversation rule:
variablesandmetaare accepted only when no active conversation id exists (fresh conversation creation path).- If a conversation id is already active, passing
variablesormetathrows a validation error.
3) Query old conversations and messages
// list old conversations using meta filters
const conversations = await conversation.list({
page: 1,
pageSize: 20,
meta: { team: "growth" }
});
// get messages for the active conversation id
const firstPage = await conversation.loadMessages(); // API defaults
const nextPage = await conversation.loadMessages({ page: 2, pageSize: 20, append: true });
// or fetch by explicit id without mutating local conversation state
const byId = await conversation.loadMessages({
conversationId: "conv_existing_123",
page: 1,
pageSize: 20
});append: true merges new pages into local state and deduplicates by message id.
4) Conversation state fields
conversation.id: current active conversation idconversation.deploymentId: active deployment context for conversation callsconversation.status:idle | loading | streaming | success | errorconversation.data: latest complete conversation response payloadconversation.live: transient streaming buffer (cleared on completion)conversation.meta: model/token/request metadata when presentconversation.messages: locally cached messages fromloadMessages(...)conversation.messagesPageInfo: paging info for messagesconversation.conversations: locally cached list fromlist(...)conversation.conversationsPageInfo: paging info for conversationsconversation.error: typed SDK error objectconversation.raw: latest raw response envelope/payload
Durable Agent API
client.agent starts and resumes turns through the v2 conversation endpoint, then polls the ordered v2 agent-history timeline for durable state. history contains messages, tools, actions, and client-tool activity in chronological order; messages remains a derived message-only view.
const lookupCustomer = Object.assign(
async ({ customerId }: Record<string, unknown>) => ({ customerId, active: true }),
{
inputSchema: {
type: "object",
properties: { customerId: { type: "string" } },
required: ["customerId"]
}
}
);
await client.agent.start({
triggerId: "trigger-id",
clientTools: { lookupCustomer }
});
// One-time status and message snapshot; does not poll:
const snapshot = await client.agent.get("conversation-id", {
pageSize: 25
});
// Open an interactive session and keep it synchronized:
await client.agent.open("conversation-id", { clientTools: { lookupCustomer } });
// Tools without handlers remain available for explicit UI resolution:
await client.agent.resolveTool(client.agent.pendingToolCalls[0].callId, { approved: true });
// Fetch the next older history page when requested by your UI:
if (client.agent.historyPageInfo?.hasMore) {
await client.agent.loadMoreHistory();
}get() is a one-time read. open() starts SDK-managed refresh for an interactive UI, and subscribe() notifies your code when that state changes. Completed conversations are read-only. Call close() or abort() to stop polling. The controller does not use SSE or local storage; it manages opaque history cursors internally.
React / Next.js
For React/Next usage, use @amarsia/react instead of manually wiring useState + useEffect.
- React package README: amarsia-packages/packages/react
- Full docs: docs.amarsia.com
API Reference (Types and field meaning)
amarsia.init contract
type InitConfig = {
apiKey?: string;
deploymentId?: string;
baseUrl?: string;
dangerouslyAllowBrowserApiKey?: boolean;
fetch?: typeof globalThis.fetch;
};Stateful controller contract
All stateful controllers (client.run, client.stream, client.conversation) expose:
{
status: "idle" | "loading" | "streaming" | "success" | "error";
data: unknown | null; // latest complete response payload
live: string; // transient live stream buffer
error: { name: string; message: string; ... } | null;
meta: Record<string, unknown> | null;
raw: unknown;
getState(): Readonly<State>;
subscribe(listener: (state: Readonly<State>) => void): () => void;
}MessageContent
type MessageContent =
| { type: "text"; text: string }
| { type: "image" | "video" | "audio" | "url"; mime_type: string; file_uri: string };run request body
{
content: MessageContent[];
variables?: Record<string, unknown>;
deploymentId?: string;
}run response (common fields):
{
content: string | Record<string, unknown>;
model?: string;
input_tokens?: number;
output_tokens?: number;
[key: string]: unknown;
}stream request body
Same as run, plus optional signal.
conversation.start
start(conversationId?: string, deploymentId?: string): voidBehavior:
- Sets active conversation context for future conversation calls.
- If
conversationIdis omitted, nextconversation.run/streamcreates a fresh conversation. - If
deploymentIdis omitted, conversation context keeps previous deployment id (or init default).
conversation.run / conversation.stream request body
{
content: MessageContent[];
historyLimit?: number;
signal?: AbortSignal;
variables?: Record<string, unknown>; // fresh-conversation only
meta?: Record<string, string | number | boolean>; // fresh-conversation only
}Conversation behavior contract:
- Fresh conversation (no active
conversation.id):run/streamcreates conversation using/conversation.variablesandmetaare accepted.
- Existing conversation (active
conversation.id):runcontinues via non-stream conversation endpoint.streamcontinues via stream conversation endpoint.variablesandmetaare rejected with validation error.
Response highlights
content: model output (string or structured object depending on deployment behavior)conversation_id: conversation identifier for conversation APIsmodel: model identifier usedinput_tokens/output_tokens: token usagecreated_at/updated_at: timestamps when present
conversation state fields
{
id: string | null;
deploymentId: string | null;
status: "idle" | "loading" | "streaming" | "success" | "error";
data: ConversationData | null;
live: string;
error: AmarsiaSdkErrorData | null;
meta: UsageMetadata | null;
raw: unknown;
messages: ConversationMessage[];
messagesPageInfo: { page: number; page_size: number; total: number; has_more: boolean } | null;
conversations: ChatConversation[];
conversationsPageInfo: { page: number; page_size: number; total: number; has_more: boolean } | null;
}History/query helpers
interface ChatConversation {
id: string;
name?: string | null;
created_at?: string;
updated_at?: string;
message_count?: number;
meta?: Record<string, unknown>;
}
interface ConversationListResponse {
items: ChatConversation[];
total: number;
page: number;
page_size: number;
has_more: boolean;
}
interface ConversationMessagesResponse {
items: ConversationMessage[];
total: number;
page: number;
page_size: number;
has_more: boolean;
}
conversation.loadMessages({ conversationId?, page?, pageSize?, append? });
conversation.list({ page?, pageSize?, meta? }): Promise<ChatConversation[]>;conversation.list(...) resolves deployment from conversation context (or client init config) and calls /v1/runner/{deployment_id}/conversations.
The list and message-history endpoint responses use items; the SDK returns those item arrays and stores them in conversation.conversations or conversation.messages.
See full examples and endpoint docs at docs.amarsia.com.
Error handling
Every SDK method (run, stream, conversation.run, conversation.stream, conversation.loadMessages, conversation.list) throws AmarsiaSdkError on failure with normalized fields.
import { AmarsiaSdkError } from "@amarsia/sdk";
try {
await client.run({
content: [{ type: "text", text: "Hello" }]
});
} catch (error) {
if (error instanceof AmarsiaSdkError) {
console.error(error.name); // e.g. "AmarsiaHttpError"
console.error(error.status); // HTTP status, e.g. 401
console.error(error.code); // stable string code, e.g. "unauthorized"
console.error(error.message); // human-readable message from the API
} else {
console.error(error);
}
}Error field reference
| Field | Description |
|---|---|
| name | Error class name: AmarsiaHttpError, AmarsiaValidationError, AmarsiaConfigurationError, AmarsiaAbortError, AmarsiaNetworkError. |
| status | HTTP status for API errors. undefined for SDK-side errors. |
| code | Stable symbolic code you can switch on. For HTTP errors derived from the status when the API doesn't send one. |
| message | Human-readable message, extracted from the API response. |
| details | The full response body (parsed) or raw string. |
Common codes
| code | Typical cause |
|---|---|
| bad_request | Malformed payload or unknown deployment id. |
| unauthorized | API key is required, invalid, expired, or for the wrong project. |
| forbidden | The workflow is public but the request origin is not in its allowlist. |
| not_found | The workflow or resource does not exist. |
| unprocessable_entity | Request body failed server-side validation (FastAPI 422). |
| rate_limited | Usage or action rate limits exceeded. |
| internal_server_error | Unhandled error on the server. |
Example: branching on code
try {
await client.run({ content: [{ type: "text", text: "Hello" }] });
} catch (error) {
if (!(error instanceof AmarsiaSdkError)) throw error;
switch (error.code) {
case "unauthorized":
// Missing or invalid apiKey for a workflow that requires authentication.
break;
case "forbidden":
// Public workflow, but the current origin is not on the allowlist.
break;
default:
throw error;
}
}Controller state
Every controller (client.run, client.stream, client.conversation) also mirrors the error on its state, so UIs that subscribe to getState() / subscribe(...) get status === "error" and the same AmarsiaSdkErrorData on .error without needing a try/catch.
Security guidance
If you use long-lived API keys in browser apps, keys can be extracted and abused.
Recommended production approach:
- Keep primary API keys on your backend
- Call Amarsia from a backend route/proxy
- If you must call from browser, use short-lived tokens and rotate frequently
The SDK warns in browser contexts unless dangerouslyAllowBrowserApiKey: true is set during init.
FAQ
Is this the official Amarsia SDK?
Yes. @amarsia/sdk is the official SDK for Amarsia APIs.
Does it support streaming AI responses?
Yes. Use client.stream(...) for streaming output and client.conversation.stream(...) for stateful streaming conversation continuation.
Should I use this with React?
For raw SDK usage, use @amarsia/sdk directly. For React hooks with no manual useState/useEffect wiring, use @amarsia/react.
Search terms
Amarsia SDK, Amarsia TypeScript SDK, Amarsia JavaScript SDK, Amarsia conversation API SDK, Amarsia streaming API SDK.
