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
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
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
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
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 prompt | Yes | Fixed overhead every request |
| Conversation history | Yes | Grows linearly with steps |
| Tool definitions | Yes | 800–1,500 tokens for moderate schemas |
| Tool results | Yes | Can 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.
Related guides
Tool schema token overhead
Function schemas add 800–1,500 input tokens per turn. Measure tool overhead before your agent loop scales.
Claude dynamic max_tokens
Claude rejects requests when input + max_tokens > context limit. Measure first, then set max_tokens.
tiktoken vs OpenAI API
tiktoken.encode(prompt) ignores message framing. That gap causes context_length_exceeded errors in production.
AI SDK compaction triggers
Your AI SDK agent loop grows every step. This guide tells you when to compact — before safeMaxTokens hits zero and streamText returns a 400.
Start counting before you send
Add tokens-usage to your stack today. Source-available license — see LICENSE.md for terms.
npm install tokens-usage