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/llm

v0.8.3

Published

LLM manager and browser adapters for Charivo

Readme

@charivo/llm

Stateful LLM manager for Charivo conversations.

@charivo/llm owns character-aware prompt building and message history. It wraps an LLMClient implementation from another package.

Install

pnpm add @charivo/llm

Usage

import { createLLMManager } from "@charivo/llm";
import { createRemoteLLMClient } from "@charivo/llm/remote";

const manager = createLLMManager(
  createRemoteLLMClient({ apiEndpoint: "/api/chat" }),
);

manager.setCharacter({
  id: "hiyori",
  name: "Hiyori",
  personality: "Cheerful and helpful assistant",
});

const reply = await manager.generateResponse({
  id: "1",
  content: "Hello",
  timestamp: new Date(),
  type: "user",
});

History Retention

LLMManager keeps the latest 40 turns by default. A turn is one user message plus one character response, so the stored conversation history is capped at 80 messages. This bounds memory growth and the context sent to your LLM client.

Pass maxHistoryTurns to change the limit:

const manager = createLLMManager(client, {
  maxHistoryTurns: 20,
});

Use maxHistoryTurns: null to opt out and keep unbounded history.

Tool Calling

LLMManager can run a tool-calling loop instead of a plain call(...) when both sides opt in: at least one tool is registered and the injected LLMClient implements the optional callWithTools(messages, tools) method. Otherwise generateResponse(...) falls back to the plain call(...) path.

import { createLLMManager } from "@charivo/llm";
import { createRemoteLLMClient } from "@charivo/llm/remote";
import {
  buildAvatarControlInstructions,
  createAvatarControlTools,
  createAvatarResultProjector,
} from "@charivo/avatar";

const catalog = { expressions: ["Smile"], motions: { Idle: 2 } };

const manager = createLLMManager(
  createRemoteLLMClient({ apiEndpoint: "/api/chat" }),
  {
    tools: createAvatarControlTools(catalog),
    resultProjectors: [createAvatarResultProjector()],
    toolInstructions: buildAvatarControlInstructions(catalog),
  },
);

LLMManagerOptions tool fields:

  • tools?: ToolRegistration[] — registered at construction; also available after construction via registerTool(tool) / unregisterTool(name)
  • resultProjectors?: ToolResultProjector[] — run after a successful tool call, when an event emitter is attached (Charivo.attachLLM(...) wires one automatically). A projector throwing turns into an llm:error event (Error message: LLM result projector failed for tool "<name>": <cause>) instead of failing the reply.
  • toolInstructions?: string — appended to the character system prompt, but only on the tools path (registered tools + a tool-capable client); it has no effect on the plain call(...) path.

Tools are ToolRegistration values from @charivo/core — the same contract @charivo/realtime uses, so tool builders such as @charivo/avatar's createAvatarControlTools(...) work with both managers. Tool arguments are validated against each definition's schema, and results are timed out (defaultToolTimeoutMs, 10s default, overridable per tool via timeoutMs) and asserted to be plain objects before a projector runs. Any failure — unknown tool, invalid arguments, handler throw/timeout, non-object result — becomes a { success: false, error } tool output so the reply always continues instead of throwing.

Tool Events

When an event emitter is attached, the manager emits tool:call before each tool executes, then either tool:result on success or tool:error on any failure. @charivo/realtime emits the same three events, so a listener can observe tool activity without caring which modality ran the tool.

tool:result carries the JSON-serialized snapshot of the handler result — the same value the model's tool turn receives — so a result with a toJSON() surfaces as its round-tripped form.

resultProjectors receive that same snapshot, as they do in @charivo/realtime, so a projector behaves identically no matter which modality ran the tool. It is the wire form, not the live handler object: a Date arrives as its ISO string and an undefined property is gone, so read output as plain JSON. Values that cannot survive JSON were never part of the tool result anyway — the model only ever sees the serialized form.

Round Cap

The tool loop executes at most 3 tool-calling rounds. After the third round, the manager makes one more callWithTools(...) call with an empty tools array so the model is forced to answer in text instead of requesting another tool call; that terminal response's content becomes the reply.

Remote Protocol

@charivo/llm/remote's callWithTools(messages, tools) posts { messages, tools } (tools included as-is, even when empty) to your chat route and expects { success: true, message: string, toolCalls?: LLMToolCall[] } back. toolCalls is omitted (or empty) when the model didn't call a tool. On the wire, tools: [] is a valid, distinct request from omitting tools entirely — server routes typically treat any request carrying tools (even empty) or a tool-call/tool-result turn in messages as needing the tool-calling provider path, and only map an empty tools array to "no tools" when calling the underlying model.

History Exclusion

Only the final assistant text is added to LLMManager's history. Intermediate assistant toolCalls turns and role: "tool" result turns exist only inside one generateResponse(...) call's tool loop — getHistory() and the stored conversation always stay a plain user/character transcript that other modalities can reuse.

Exports

  • createLLMManager(client, options?)
  • LLMManagerOptions
  • @charivo/llm/openai: createOpenAILLMClient(config) (browser client, dev/testing only) and, for server-side use, createOpenAILLMProvider(config), OpenAILLMProvider, type OpenAILLMConfig
  • @charivo/llm/openclaw: createOpenClawLLMClient(config) (browser client, dev/testing only) and, for server-side use, createOpenClawLLMProvider(config), OpenClawLLMProvider, type OpenClawLLMConfig. sessionKey exists only on the provider config — LLMManager.clearHistory() can only clear local history, not rotate a pinned gateway session, so a client-side sessionKey would silently replay the old transcript after a reset. Server routes that construct the provider directly can rotate sessionKey themselves.

Manager API

  • setCharacter(character)
  • getCharacter()
  • generateResponse(message)
  • getHistory()
  • clearHistory()
  • setEventEmitter(eventEmitter) — wired automatically by Charivo.attachLLM(...)
  • registerTool(tool) / unregisterTool(name) / getRegisteredTools()
  • setToolInstructions(instructions | null)