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

@charivo/realtime

v0.7.2

Published

Realtime manager and browser adapters for Charivo

Downloads

1,131

Readme

@charivo/realtime

Provider-agnostic realtime session manager and typed config helpers for Charivo.

Install

pnpm add @charivo/realtime

Usage

import {
  createRealtimeManager,
  type RealtimeToolRegistration,
} from "@charivo/realtime";
import { createRemoteRealtimeClient } from "@charivo/realtime/remote";

const client = createRemoteRealtimeClient({ apiEndpoint: "/api/realtime" });
const tools: RealtimeToolRegistration[] = [
  {
    definition: {
      type: "function",
      name: "describeCharacterProfile",
      description: "Return the active character profile.",
      parameters: {
        type: "object",
        properties: {},
      },
    },
    async handler(_args, context) {
      return {
        success: true,
        name: context.character?.name ?? null,
      };
    },
  },
];

const manager = createRealtimeManager(client, { tools });
manager.setCharacter({
  id: "hiyori",
  name: "Hiyori",
  personality: "Cheerful and helpful assistant",
  voice: { voiceId: "marin" },
});

await manager.startSession({
  provider: "openai",
  model: "gpt-realtime-mini",
});

await manager.updateSession({
  voice: "alloy",
});

If the live transport drops temporarily, RealtimeManager now keeps the session active and drives reconnect attempts internally. During that window state.session.status stays "active" while state.connection moves back to "connecting".

Exports

  • createRealtimeManager(client, options?)
  • buildRealtimeSessionConfig({ character, baseConfig? })
  • DEFAULT_REALTIME_AGENT_INSTRUCTIONS
  • realtime-related types re-exported from @charivo/core
  • RealtimeToolResultProjector
  • RealtimeLogger

Instruction Layering

@charivo/realtime keeps its default instructions generic: spoken-output constraints, tool-use restraint, stage-direction suppression, and basic in-character behavior.

buildRealtimeSessionConfig({ character, baseConfig? }) already folds in:

  • character identity (You are ...)
  • character.description
  • character.personality
  • the generic realtime defaults
  • character.voice.voiceId when available

If your app needs stronger product-specific acting guidance, append it in the app layer instead of expanding the library default prompt:

const base = buildRealtimeSessionConfig({ character });

await manager.startSession({
  provider: "openai",
  model: "gpt-realtime-mini",
  instructions: [
    base.instructions,
    "Keep replies short and natural for this product.",
  ].join("\n"),
});

buildRealtimeSessionConfig(...) does not fill provider-specific fields such as provider or model. Prefer building on top of it for instructions and character voice, then pass provider/model explicitly at startSession(...). OpenAI transport packages and @charivo/server/openai keep their own OpenAI-specific fallbacks for omitted model or voice values.

Avatar-specific realtime tools and avatar-specific instruction addenda now live in @charivo/realtime-avatar. Append those instructions only in sessions that register avatar tools so @charivo/realtime stays tool-agnostic.

Result Projectors And Logging

RealtimeManager stays renderer-neutral. If your app wants domain-specific events from tool outputs, pass resultProjectors:

import {
  buildRealtimeSessionConfig,
  createRealtimeManager,
} from "@charivo/realtime";
import {
  buildAvatarControlInstructions,
  createAvatarResultProjector,
} from "@charivo/realtime-avatar";

const base = buildRealtimeSessionConfig({ character });

const manager = createRealtimeManager(client, {
  tools,
  resultProjectors: [createAvatarResultProjector()],
  logger: console,
});

await manager.startSession({
  provider: "openai",
  instructions: [
    base.instructions,
    buildAvatarControlInstructions(avatarCatalog),
  ].join("\n"),
});

resultProjectors run after successful local tool execution and can emit additional app-level events such as realtime:expression.

When a logger is configured, RealtimeManager also injects a per-session sessionId into every log context. If the caller also supplies sessionId in its own logger context, the manager value wins. The same sessionId is included in realtime:usage payloads.

Session Refresh

updateSession(...) patches the active provider session in place using the latest requested config, current character, and current tool registry.

  • inactive managers only cache the requested base config for the next startSession(...)
  • active managers keep the current connection open and issue a transport-level session patch
  • successful patches update realtime:state only and do not emit synthetic realtime:session:end/start refresh boundaries
  • patch failures keep the current live session and previous state.session.config in place
  • state.session.config is only replaced after the patch succeeds
  • repeated updateSession(...) calls are coalesced to the latest config
  • stopSession() wins over an in-flight refresh and converges to a stopped session

Reconnect Semantics

Successful reconnects are treated as a continuation of the same live session.

  • successful recovery does not emit synthetic realtime:session:end/start
  • updateSession(...) still updates the cached base config while reconnecting
  • sendMessage(...), sendAudioChunk(...), interrupt(), and transport-level tool results reject while connection === "connecting"
  • the next reconnect attempt always rebuilds from the latest effective config
  • in-flight assistant responses are marked as interrupted and are not resumed
  • old tool-call ids are not replayed after reconnect

Observability events emitted by the manager:

  • realtime:reconnect:attempt
  • realtime:reconnect:success
  • realtime:reconnect:exhausted

Tool Registry

RealtimeManager owns the tool registry. Definitions sent to the provider come from the registry, not from defaultSessionConfig.tools.

Before invoking a local tool handler, the manager validates incoming tool arguments against the tool definition's required, enum, and basic JSON Schema type fields. Invalid arguments follow the normal tool failure path: realtime:tool:error is emitted and a { success: false, error } result is sent back to the transport. Nested object/array schemas, additionalProperties, and union type arrays are not enforced.

manager.registerTool({
  definition: {
    type: "function",
    name: "describeScene",
    description: "Describe the current scene.",
    parameters: {
      type: "object",
      properties: {},
    },
  },
  async handler() {
    return {
      success: true,
      scene: "Cafe",
    };
  },
});

Current limitation:

  • registerTool(...) and unregisterTool(...) update the local manager registry immediately
  • active provider sessions are not updated until updateSession(...) or the next startSession(...)
  • unregistered tools may still be called by an already-active provider session and will return a failure result

Event Bridge

RealtimeManager accepts an emit-only event bridge through setEventEmitter(...). It relays client output into the Charivo event stream, but it does not subscribe through the shared event bus.

When connected, the manager relays:

  • realtime:session:start
  • realtime:session:end
  • realtime:state
  • realtime:user:transcript
  • realtime:assistant:start
  • realtime:assistant:delta
  • realtime:assistant:done
  • realtime:tool:call
  • realtime:tool:result
  • realtime:tool:error
  • realtime:usage
  • realtime:text:delta
  • realtime:error
  • tts:lipsync:update
  • tts:audio:start
  • tts:audio:end