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

@yanib/context-budget

v0.1.0

Published

Fit chat history into any model's context window: token estimation, sliding-window packing with a running summary, and overflow detection. Zero dependencies, no AI inside — bring your own summarizer if you want one.

Downloads

135

Readme

@yanib/context-budget

npm license

Fit chat history into any model's context window.

Every chat app eventually writes the same code: estimate tokens, keep recent turns verbatim, compress the old ones, reserve room for the response, and handle the overflow error anyway. This package is that code — extracted from a production on-device assistant where the context windows are small and an overflow doesn't return a 400, it crashes the runtime.

Zero dependencies. No AI inside — the summarizer is a callback you can point at your model, with a deterministic extractive fallback built in. Works in Node, browsers, React Native, and edge runtimes; ships ESM + CJS.

import { packMessages } from "@yanib/context-budget";

const { messages, summary, summarizedCount, fits } = await packMessages(history, {
  contextTokens: 8192,          // your model's window
  responseReserve: 600,         // headroom for the reply
  summary: conversation.summary,             // carried from the last turn
  alreadySummarized: conversation.cursor,    // …so old turns aren't re-folded
  systemBlocks: [personaPrompt, ragContext], // these consume budget too
  summarize: (prev, dropped) =>              // optional: bring your model
    llm.generateText(foldPrompt(prev, dropped)),
});

const reply = await llm.chat(messages);
conversation.summary = summary;      // persist for next turn
conversation.cursor = summarizedCount;

How it packs

  1. The most recent turns stay verbatim (window size adapts to the model: 8 turns at 4k context, 16 at 8k, 24 at 16k+ — or set your own).
  2. Older turns are represented by a running summary, carried between turns via summary + summarizedCount so nothing is summarized twice.
  3. If the verbatim window still blows the budget, it's trimmed from the front and the trimmed turns are folded into the summary — one summarizer call per pack, not one per message.
  4. Final guard: if a single message + system blocks still overflow, the summary is hard-truncated into whatever room remains. If even that can't fit, you get fits: false and you decide (the messages are still returned).

System blocks and the summary are merged into one system message — providers like Apple's Foundation Models accept only a single instructions block, and every other provider tolerates it.

Token estimation

The default estimator is the ~4-chars-per-token heuristic — deliberately dependency-free (real tokenizers cost megabytes and vary per model, and budgets carry headroom anyway). Have exact counts? Plug them in:

import { encode } from "gpt-tokenizer";
await packMessages(history, { estimateTokens: (t) => encode(t).length });

Also exported: createCharEstimator(ratio), truncateToTokens(text, max, est?, "head" | "tail"), estimateTokensOf, contextLimitsFor.

The overflow retry

Estimates are estimates. When the provider still throws, detect it and retry aggressively — half the verbatim window:

import { isContextOverflowError } from "@yanib/context-budget";

try {
  return await llm.chat(messages);
} catch (err) {
  if (!isContextOverflowError(err)) throw err;
  const retry = await packMessages(history, { ...options, aggressive: true });
  return await llm.chat(retry.messages);
}

The summarizer seam

summarize(previousSummary, droppedTurns) may be sync or async, and may call anything:

  • Default: createExtractiveSummarizer() — one compact labeled line per dropped turn, capped total size keeping the tail. Deterministic, instant, offline.
  • Your model: semantic summaries when quality matters. If your call fails, return previousSummary — a memory hiccup should never block a chat turn.

Extra message fields (images, ids, timestamps) pass through packing untouched, so multimodal and app-specific metadata survive.

API

packMessages(history, options?) → Promise<{
  messages,          // one merged system message (if any) + verbatim window
  summary,           // persist and pass back next turn
  summarizedCount,   // pass back as alreadySummarized next turn
  dropped,           // turns folded into the summary this call
  usedTokens, budget, fits,
}>

All options: contextTokens (4096), responseReserve (600), recentWindow (auto), aggressive, estimateTokens, summary, alreadySummarized, systemBlocks, summarize.

License

MIT © Binaya Dhakal