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

@ensoul-ai/sdk

v0.3.0

Published

Official TypeScript SDK for the Ensoul API. Build AI NPCs and personas with memory and personality that evolve through real conversation, scale to thousands, and run simulations where they change over time.

Downloads

243

Readme

Ensoul TypeScript SDK

Official TypeScript SDK for the Ensoul API. Build AI NPCs and personas with memory and personality that evolve through real conversation. Scale to thousands of personas, and run simulations where they grow and change over time.

Installation

npm install @ensoul-ai/sdk

Quick Start

A persona needs no world. This one stands alone, and talks to one of your own end users:

import { Ensoul } from "@ensoul-ai/sdk";

const client = new Ensoul({ apiKey: "YOUR_API_KEY" });

// A persona needs no world. This one stands alone.
const persona = await client.personas.create({ name: "Ada" });

// userId is your own id for the person talking.
// Each end user gets their own conversations with the persona.
const reply = await client.chat.send(
  persona.id,
  "Hi! Who are you?",
  { userId: "YOUR_END_USER_ID" },
);
console.log(reply.response);

// End the conversation when the person is done.
await client.chat.endConversation(
  persona.id,
  reply.conversation_id,
  { userId: "YOUR_END_USER_ID" },
);

Pass world: "my_world" to create to place a persona in a world instead.

Streaming

Chat and aggregate endpoints support server-sent events. The stream is an async iterable.

Each event's data is a JSON object. The delta text lives in the chunk field:

| Field | Type | Notes | |-------|------|-------| | chunk | string | The text delta. Append these to build the full reply. | | conversationId | string | Stable across the stream. Pass it back to continue the conversation. | | chunkIndex | number | 0-based position of this chunk in the stream. | | isFinal | boolean | true on the last event. Its chunk is empty (""). | | tokenUsage | object \| undefined | Present only on the final event. |

Use parseChatEvent to turn each raw SSEEvent into a typed ChatStreamEvent (camelCase fields), then write chunk as it arrives:

import { parseChatEvent } from "@ensoul-ai/sdk";

const stream = await client.chat.stream("persona_abc123", "Tell me a story.");

for await (const event of stream.events()) {
  const parsed = parseChatEvent(event);
  process.stdout.write(parsed.chunk); // print text as it streams
  if (parsed.isFinal) {
    process.stdout.write("\n");
    if (parsed.tokenUsage) console.log("tokens:", parsed.tokenUsage);
  }
}

If you prefer to read the raw payload yourself, the JSON uses snake_case keys (chunk, conversation_id, chunk_index, is_final, token_usage):

for await (const event of stream.events()) {
  const data = JSON.parse(event.data);
  process.stdout.write(data.chunk);
}

Pagination

List endpoints return a Page<T> object that implements Symbol.asyncIterator, so you can iterate over all records without manual cursor management.

// Fetch a page
const page = await client.personas.list({ perPage: 50 });
console.log(page.items, page.total);

// Auto-paginate through all results
for await (const persona of page.autoPagingIter()) {
  console.log(persona.id, persona.name);
}

Error Handling

All errors extend EnsoulError. Import the specific subclasses you need.

import { Ensoul, AuthenticationError, RateLimitError, NotFoundError } from "@ensoul-ai/sdk";

try {
  const persona = await client.personas.get("missing_id");
} catch (err) {
  if (err instanceof NotFoundError) {
    console.error("Persona not found:", err.message);
  } else if (err instanceof RateLimitError) {
    console.error("Rate limited. Retry after:", err.retryAfter);
  } else if (err instanceof AuthenticationError) {
    console.error("Invalid or missing API key.");
  } else {
    throw err;
  }
}

Error hierarchy:

EnsoulError
  APIError
    AuthenticationError    (401)
    PaymentRequiredError   (402)
      PersonaCallsLimitError  (402, resource "persona_calls")
    AuthorizationError     (403)
    NotFoundError          (404)
    ValidationError        (422)
      EndUserRequiredError    (400, "user_id_required")
    ConflictError          (409)
      TurnInFlightError       (409, "turn_in_flight")
      ConversationEndedError  (409, "conversation_ended")
      EndUserForgottenError   (409, "end_user_forgotten")
    RateLimitError         (429)
    ServerError            (5xx)

Each new subclass carries the fields its error body adds (ConversationEndedError.conversationId, PersonaCallsLimitError.personaId, and so on) and still matches a catch written against its parent, so instanceof ConflictError keeps catching TurnInFlightError. See MIGRATION.md.

Configuration

The client reads two environment variables as defaults:

| Variable | Purpose | |----------|---------| | ENSOUL_API_KEY | API key (avoids passing apiKey in code) | | ENSOUL_BASE_URL | API base URL (default: https://api.ensoul-ai.com) |

Demo API: the current hosted demo is available at:

export ENSOUL_BASE_URL="https://api.demo.ensoul-ai.com"
export ENSOUL_API_KEY="your-api-key"

With these set, new Ensoul() connects to the demo with no constructor options.

You can also pass the base URL explicitly:

const client = new Ensoul({ apiKey: "ens_...", baseUrl: "https://api.demo.ensoul-ai.com" });

Authentication

API key:

const client = new Ensoul({ apiKey: "ens_live_..." });
// or rely on process.env.ENSOUL_API_KEY
const client = new Ensoul();

Bearer token:

const client = new Ensoul({ bearerToken: "eyJ..." });

OAuth2 token exchange (password flow, form-encoded):

const token = await client.auth.token("[email protected]", "your-password");
const authedClient = new Ensoul({ bearerToken: token.access_token });

Resources

| Namespace | Description | |-----------|-------------| | client.personas | CRUD, list (paginated), batch create, personality vectors | | client.chat | Send messages, streaming SSE, conversation history, explicit end | | client.endUsers | Forget one of your end users: erase their conversations and memories | | client.domains | World configuration management | | client.simulations | Time-based evolution simulations | | client.aggregate | Aggregate queries with streaming | | client.memory | Memory management per persona | | client.sessions | Hierarchical session orchestration | | client.frameworks | Framework management | | client.auth | OAuth2 token exchange | | client.health | Health checks | | client.info | Server configuration and metadata, and your account info and key binding (info.me()) |

Requirements

  • Node.js 18+ (native fetch, no runtime dependencies)
  • TypeScript 5.7+ (for full type inference)