npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@amarsia/sdk

v1.5.1

Published

The official JavaScript and TypeScript SDK for Amarsia — run, stream, and manage AI assistants.

Readme

@amarsia/sdk

Official TypeScript/JavaScript SDK for Amarsia APIs.

@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.conversation for stateful conversation workflows with conversation.id and conversation.data
  • Use client.agent for durable, reconnectable workflows with client tools

What Is @amarsia/sdk?

@amarsia/sdk is the official Amarsia AI SDK for JavaScript and TypeScript. It provides:

  • run for one-shot runner API requests
  • stream for streaming AI output
  • conversation for stateful multi-turn chat with conversation history
  • agent for 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/sdk

Quick 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 available

Initialization

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

  • run and stream use deploymentId from amarsia.init(...) by default.
  • run and stream can still override deploymentId per call.
  • conversation uses 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 .live for live token/chunk rendering while a stream is in progress.
  • Use .data for the final completed response after the call finishes.
  • For streaming calls, .live is 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 id

For 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(...) and conversation.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:

  • variables and meta are accepted only when no active conversation id exists (fresh conversation creation path).
  • If a conversation id is already active, passing variables or meta throws 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 id
  • conversation.deploymentId: active deployment context for conversation calls
  • conversation.status: idle | loading | streaming | success | error
  • conversation.data: latest complete conversation response payload
  • conversation.live: transient streaming buffer (cleared on completion)
  • conversation.meta: model/token/request metadata when present
  • conversation.messages: locally cached messages from loadMessages(...)
  • conversation.messagesPageInfo: paging info for messages
  • conversation.conversations: locally cached list from list(...)
  • conversation.conversationsPageInfo: paging info for conversations
  • conversation.error: typed SDK error object
  • conversation.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.

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): void

Behavior:

  • Sets active conversation context for future conversation calls.
  • If conversationId is omitted, next conversation.run/stream creates a fresh conversation.
  • If deploymentId is 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/stream creates conversation using /conversation.
    • variables and meta are accepted.
  • Existing conversation (active conversation.id):
    • run continues via non-stream conversation endpoint.
    • stream continues via stream conversation endpoint.
    • variables and meta are rejected with validation error.

Response highlights

  • content: model output (string or structured object depending on deployment behavior)
  • conversation_id: conversation identifier for conversation APIs
  • model: model identifier used
  • input_tokens / output_tokens: token usage
  • created_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.