A small, dependency-free Node.js utility for rough token estimation across OpenAI, Gemini, and Claude model families.
ai-token-counter helps developers estimate prompt size before making API calls. It is built for simple scripts, backend services, and internal tooling where you need a fast approximation without pulling in a full tokenizer.
It is intentionally lightweight and fast. The tradeoff is that estimates are approximate, not exact. Real provider token counts may differ.
The estimator now uses a chunk-based heuristic that weighs words, numbers, punctuation, CJK characters, and emoji separately, which produces more realistic results than a plain character count.
- Dependency-free Node.js package
- Simple API for both plain text and chat-style messages
- Small CLI for fast prompt checks in scripts and terminals
- TypeScript typings included
- Supports common OpenAI, Gemini, and Claude naming patterns
- Built for lightweight prompt budgeting and preflight validation
Install:
npm install ai-token-counterEstimate text:
const { countTokens } = require("ai-token-counter");
console.log(countTokens("Summarize this support issue.", "gpt-4o"));
console.log(countTokens("Summarize this support issue.", "gemini-2.0-flash"));Estimate chat messages:
const { countMessages } = require("ai-token-counter");
const messages = [
{ role: "system", content: "You are a concise assistant." },
{ role: "user", content: "Summarize this outage report." }
];
console.log(countMessages(messages, "sonnet-4"));
console.log(countMessages(messages, "gemini-1.5-pro"));TypeScript works out of the box:
import { countMessages, countTokens } from "ai-token-counter";
const textCount = countTokens("Summarize this support issue.", "gpt-4o");
const messageCount = countMessages(
[{ role: "user", content: "Review this changelog." }],
"sonnet-4"
);Check model metadata and prompt fit:
const { getModelInfo, fitsContextWindow } = require("ai-token-counter");
console.log(getModelInfo("sonnet-4"));
console.log(fitsContextWindow("Summarize this issue.", "gpt-4o", 2000));When building with LLM APIs, it is common to need a quick estimate of prompt size before sending a request. Exact tokenization can be overkill for:
- prompt budget checks before an API call
- request validation in backend services
- logging and analytics for prompt sizes
- simple CLI tools or internal automation scripts
- rough cost estimation during development
ai-token-counter gives you a simple way to do that with a single function call.
It now supports both raw text estimation and chat-style message arrays. It also includes model metadata, context-window checks, and approximate token cost estimation.
Install with npm:
npm install ai-token-counterOr with other package managers:
yarn add ai-token-counterpnpm add ai-token-counterconst { countTokens } = require("ai-token-counter");
const prompt = [
"You are a helpful assistant.",
"Summarize the following customer feedback in three bullet points,",
"highlight the main risk, and suggest a next action."
].join(" ");
const openAiEstimate = countTokens(prompt, "gpt-4o");
const geminiEstimate = countTokens(prompt, "gemini-2.0-flash");
const claudeEstimate = countTokens(prompt, "claude-3-5-sonnet");
const claudeAliasEstimate = countTokens(prompt, "sonnet-4");
const claudeOpusAliasEstimate = countTokens(prompt, "opus 4.6");
console.log("OpenAI estimate:", openAiEstimate);
console.log("Gemini estimate:", geminiEstimate);
console.log("Claude estimate:", claudeEstimate);
console.log("Claude alias estimate:", claudeAliasEstimate);
console.log("Claude Opus alias estimate:", claudeOpusAliasEstimate);Chat-style payloads are also supported:
const { countMessages } = require("ai-token-counter");
const messages = [
{
role: "system",
content: "You are a concise coding assistant."
},
{
role: "user",
content: "Review this pull request summary and list the top two risks."
}
];
console.log(countMessages(messages, "gpt-4o"));
console.log(countMessages(messages, "sonnet-4"));You can also use the estimate to enforce a rough prompt limit before making a request:
const { countTokens } = require("ai-token-counter");
function canSendPrompt(text, model, maxTokens) {
return countTokens(text, model) <= maxTokens;
}
console.log(canSendPrompt("Write a short product description.", "gpt-4.1-mini", 100));The library exports:
countTokens(text, model)countMessages(messages, model)getModelInfo(model)fitsContextWindow(input, model, maxOutputTokens?)estimateCost(input, model, options?)
The package also includes a small command-line interface.
Count inline text:
npx ai-token-counter "Summarize this pull request and call out the main risk." --model gpt-4oCount text from a file:
npx ai-token-counter --model gemini-1.5-pro --file ./prompt.txtCount chat-style messages from a JSON file:
npx ai-token-counter --model sonnet-4 --messages-file ./messages.jsonPipe plain text via stdin:
echo "Summarize this issue" | npx ai-token-counter --stdin --model gpt-4oPipe JSON message arrays via stdin:
cat messages.json | npx ai-token-counter --stdin --json --model sonnet-4Show help:
npx ai-token-counter --helpEstimate cost directly from the CLI:
npx ai-token-counter --cost --model gpt-4o "Explain Kubernetes in 2 sentences"Get machine-readable JSON output for scripts/CI:
npx ai-token-counter --json --model gpt-4o "Summarize this issue"npx ai-token-counter --json --cost --model gpt-4o --output-tokens 500 "Summarize this issue"--messages-file expects a JSON array:
[
{ "role": "system", "content": "You are a concise assistant." },
{ "role": "user", "content": "Summarize this release note." }
]Returns a rough integer token estimate for the supplied text and model.
text: string input to estimatemodel: model name string, such asgpt-4o,gpt-4.1-mini,gemini-2.0-flash,claude-3-opus,claude-3-5-sonnet,sonnet-4, oropus 4.6
Returns a rounded integer estimate. The function throws for unsupported model names or invalid input types.
Returns a rough token estimate for a chat-style message array.
messages: an array of objects withroleand stringcontentmodel: model name string, such asgpt-4o,gemini-1.5-pro,o3-mini,claude-3-5-sonnet,sonnet-4, oropus 4.6
The function sums the estimated content tokens and adds a small overhead for message structure.
Returns normalized metadata for a supported model family.
provider: recognized provider familynormalizedModel: normalized model string used internallycontextWindow: default context window estimate for the provider familysupportsMessages: whether message arrays are supported
Checks whether a prompt likely fits within the model's estimated context window.
input: either a raw text string or a chat-style message arraymodel: model name stringmaxOutputTokens: optional output-token budget to reserve
Returns an object with:
fitsinputTokensreservedOutputTokensavailableInputTokenscontextWindowprovider
Estimates approximate input and output token cost for text or chat messages.
input: either a raw text string or a chat-style message arraymodel: model name stringoptions.outputTokens: optional reserved output tokens
Returns:
providermodelinputTokensoutputTokensReservedtotalTokensEstimatedestimatedInputCostestimatedOutputCostestimatedTotalCost
const { estimateCost } = require("ai-token-counter");
const result = estimateCost("Explain Kubernetes.", "gpt-4o", {
outputTokens: 500
});
console.log(result);The library uses a lightweight pricing map and falls back to provider defaults when an exact model price is not found.
A lightweight browser playground is available in this repository.
Open it locally:
open playground/index.htmlThe playground lets you:
- paste prompt text
- select a model
- view token estimates
- view estimated cost
- view context window usage
The library uses provider-family heuristics based on the model name string.
Matches model names containing:
gpto1o3o4text-embeddingopenai
Examples:
gpt-4ogpt-4.1-minio1o3-minitext-embedding-3-small
Default family context window used by this package: 128000
Matches model names containing:
geminigoogle
Examples:
gemini-1.5-progemini-1.5-flashgemini-2.0-flashgoogle/gemini-2.0-flash-thinking
Default family context window used by this package: 1000000
Matches model names containing:
claudeanthropic
Examples:
claude-3-haikuclaude-3-opusclaude-3-5-sonnetanthropic/claude-3-7-sonnetsonnet-4haiku-3.5opus-4opus 4.6
Default family context window used by this package: 200000
If a model name does not match one of these families, countTokens throws a RangeError.
This package uses heuristic estimation rather than provider-native tokenizers. That keeps it lightweight and easy to drop into any Node.js project, but it also means:
- counts are best used as rough planning values
- provider-side totals may differ from local estimates
- exact billing or hard token limits should still be validated against the provider when precision matters
Run the included example:
node example.jsRun the built-in test suite:
npm testThis repository includes:
- README.md for package documentation
- CONTRIBUTING.md for contribution guidelines
- CHANGELOG.md for release notes
- CODE_OF_CONDUCT.md for community expectations
- SECURITY.md for security reporting guidance
- LICENSE for licensing terms
Contributions are welcome, especially if they improve the estimation heuristics while keeping the library simple and dependency-free.
To contribute:
- Fork the repository.
- Create a feature branch for your change.
- Make your changes and keep the package dependency-free unless there is a clear reason not to.
- Run
npm test. - Update the README or examples if behavior changes.
- Open a pull request with a clear explanation of what changed and why.
Good contribution ideas:
- Improve estimation heuristics for additional OpenAI or Claude model naming patterns
- Add edge-case tests for mixed-language or punctuation-heavy prompts
- Improve docs and examples for real-world prompt budgeting workflows
The package includes the required metadata to publish on npm. Before publishing:
- Update the version in
package.json. - Run
npm test. - Ensure you are logged in with
npm login. - Run
npm publish --access public.
MIT