tokens-usage

Guide

Know your payload size before streamText sends it

Agent loops accumulate context fast. Count the full messages + tools payload across OpenAI, Anthropic, and Google — before you hit the API and get a 400.

Works with ModelMessage and UIMessage · Endpoint + local fallback · USD cost estimate

The problem

onFinish tells you too late

You built an agent with Vercel AI SDK. Each step appends tool results, assistant messages, and sometimes new tool definitions. By step five, your context is twice what you expected — but you only discover that when the API returns a 400 or when onFinish reports usage after the damage is done.

Developers have asked for an official way to count tokens of the full messages + tools payload before streamText since AI SDK 5.x. Agent loops need a preflight check, not a post-mortem.

  • usage in onFinish only arrives after the request completes — useless for preventing context overflow
  • Aborted streams may not report usage at all, leaving billing and debugging blind
  • Each agent step adds tool results that compound silently across turns

The solution

Preflight with tokens-usage before streamText

tokens-usage counts input tokens before you call streamText or generateText. Pass your ModelMessage or UIMessage array directly — the same shape AI SDK uses — and get an accurate count from the provider endpoint or a local fallback.

Run the count at the start of every agent step. If you are over budget, compact history or drop optional context before streaming — not after the error lands.

How it works

Three steps to a safe preflight

  1. 1

    Build your messages array

    Assemble the ModelMessage[] you pass to streamText — system prompt, conversation history, and tool-call / tool-result parts. tokens-usage counts message content and tool blocks in that array.

  2. 2

    Call countTokens as preflight

    Use tokens-usage with the same provider and model. Set mode to auto for endpoint-first counting with local fallback.

  3. 3

    Compact if needed, then streamText

    If tokens exceed your budget, trim history or summarize older turns. Only then call streamText with confidence.

Implementation

Production-ready code

Preflight pattern for Vercel AI SDK agent loops

typescript
import { streamText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
import { countTokens, type ModelMessage } from 'tokens-usage'

const CONTEXT_LIMIT = 200_000
const MAX_OUTPUT = 8_000

async function streamAgentStep(messages: ModelMessage[]) {
  const { tokens, price, method } = await countTokens({
    provider: 'anthropic',
    model: 'claude-sonnet-4-20250514',
    content: messages,
    mode: 'auto',
  })

  if (tokens > CONTEXT_LIMIT - MAX_OUTPUT) {
    messages = compactHistory(messages) // trim older turns / large tool results
  }

  console.log(`Preflight: ${tokens} tokens via ${method}`)
  if (price) console.log(`Estimated input cost: $${price.usd}`)

  return streamText({
    model: anthropic('claude-sonnet-4-20250514'),
    messages,
    maxTokens: MAX_OUTPUT,
    // tools add input tokens on top of the preflight count — budget separately
  })

Deep dive

What actually counts toward your payload

The billable payload is more than user-visible text. System instructions, prior assistant turns, tool_use blocks, tool_result content, and tool definitions all consume input tokens on every call. tokens-usage counts tool blocks embedded in your messages array; tool definitions passed to streamText are billed on top — measure those separately via response.usage.

Component Counted each turn? Typical impact
System promptYesFixed overhead every request
Conversation historyYesGrows linearly with steps
Tool definitionsYes800–1,500 tokens for moderate schemas
Tool resultsYesCan dominate after web search or file reads

UIMessage vs ModelMessage

tokens-usage accepts both AI SDK message formats. UIMessage arrays are converted internally via convertToModelMessages, which requires the ai package as a peer dependency. ModelMessage arrays work without extra setup.

  • Use ModelMessage[] when you already convert before streamText
  • Use UIMessage[] when you receive chat state directly from useChat
  • Pass countAssistantTools: false to exclude prior tool blocks from the count

Multi-provider agent loops

Switching models mid-loop? tokens-usage uses the same countTokens call shape for OpenAI, Anthropic, and Google. One preflight function, three providers — no separate tokenizer setup per vendor.

FAQ

Common questions

Do I need AI SDK installed to use tokens-usage?

Only if you pass UIMessage arrays. ModelMessage arrays and native provider payloads work without the ai package. UIMessage conversion uses convertToModelMessages internally.

Does tokens-usage count output tokens?

No. tokens-usage counts input tokens only — the prompt and messages you send before the model responds. Reserve output budget separately when checking context limits.

Can I count tokens including tool definitions?

tokens-usage counts tool-call and tool-result blocks inside your messages array when countAssistantTools is true (default). Tool definitions passed separately to streamText are not counted yet — compare response.usage.prompt_tokens with and without tools, or see our tool schema guide.

How is this different from AI SDK countTokens?

tokens-usage is a standalone npm package you can use outside AI SDK, persist to your database, or call from backend jobs. It also returns USD cost estimates and supports mode auto with provider-specific fallbacks.

What if the count differs slightly from the API response?

Provider count_tokens endpoints return estimates. Anthropic documents that actual usage may differ by a small amount. Use a safety margin of 5–10% when setting context limits.

Start counting before you send

Add tokens-usage to your stack today. Source-available license — see LICENSE.md for terms.

npm install tokens-usage