Token usage
Track and limit AI token consumption with the token ledger.
The tokenLedger singleton tracks every AI operation's token consumption. It records usage per model, aggregates by time window, and enforces configurable limits.
Configuration
Set limits in the plugin config:
export default {
usage: {
limits: {
perModel: {
'gpt-4o': { window: 'day', maxTokens: 50_000, mode: 'block' },
'gpt-4o-mini': { window: 'month', maxTokens: 1_000_000, mode: 'warn' }
}
},
onRecorded: (payload) => {
console.log(`Used ${payload.entry.total} tokens on ${payload.model}`)
},
onLimitReached: (payload) => {
console.warn('Limit breached:', payload.breaches)
}
}
}export default {
usage: {
limits: {
perModel: {
'gpt-4o': { window: 'day', maxTokens: 50_000, mode: 'block' },
'gpt-4o-mini': { window: 'month', maxTokens: 1_000_000, mode: 'warn' }
}
},
onRecorded: (payload) => {
console.log(`Used ${payload.entry.total} tokens on ${payload.model}`)
},
onLimitReached: (payload) => {
console.warn('Limit breached:', payload.breaches)
}
}
}Limit rules
Prop
Type
Limit modes
Block
Throws TokenLimitError before the operation runs. The error includes a displayMessage for user-facing text. AI.chat() catches this and sends the message as a reply.
Warn
Emits a 'usage.limitReached' event without blocking. Operations continue normally. Use this for monitoring without interrupting users.
TokenLimitError
When mode is 'block', the thrown error carries context about the breach:
import { AI, TokenLimitError } from '@robojs/ai'
try {
const reply = await AI.chatSync([{ role: 'user', content: 'Hello' }], {})
} catch (error) {
if (error instanceof TokenLimitError) {
console.warn(error.displayMessage)
}
}import { AI, TokenLimitError } from '@robojs/ai'
try {
const reply = await AI.chatSync([{ role: 'user', content: 'Hello' }], {})
} catch (error) {
if (error instanceof TokenLimitError) {
console.warn(error.displayMessage)
}
}Properties on TokenLimitError:
model-- The model that hit the limit.window-- The time window ('day','week', or'month').windowKey-- The specific window key (e.g.,2025-01-15).rule-- TheTokenLimitRulethat was breached.usageKind-- The type of usage that triggered the limit.displayMessage-- Usesrule.messageif set, otherwise auto-generated.
Querying usage
Pull usage data through the AI singleton or tokenLedger directly:
import { AI, tokenLedger } from '@robojs/ai'
// Via AI singleton
const daily = await AI.getUsageSummary({ window: 'day', model: 'gpt-4o' })
const lifetime = await AI.getLifetimeUsage('gpt-4o')
// Via tokenLedger directly
const summary = await tokenLedger.getSummary({ window: 'week', limit: 10 })
const totals = await tokenLedger.getLifetimeTotals()
const entries = await tokenLedger.getEntriesForDay('2025-01-15')import { AI, tokenLedger } from '@robojs/ai'
// Via AI singleton
const daily = await AI.getUsageSummary({ window: 'day', model: 'gpt-4o' })
const lifetime = await AI.getLifetimeUsage('gpt-4o')
// Via tokenLedger directly
const summary = await tokenLedger.getSummary({ window: 'week', limit: 10 })
const totals = await tokenLedger.getLifetimeTotals()
const entries = await tokenLedger.getEntriesForDay('2025-01-15')Pre-checking limits
Check whether an operation would exceed limits before running it:
import { tokenLedger } from '@robojs/ai'
const willExceed = await tokenLedger.willExceedLimit('gpt-4o', 5000)
const state = await tokenLedger.getLimitState('gpt-4o')
// state.blocked, state.windows.day.remaining, state.windows.day.totalimport { tokenLedger } from '@robojs/ai'
const willExceed = await tokenLedger.willExceedLimit('gpt-4o', 5000)
const state = await tokenLedger.getLimitState('gpt-4o')
// state.blocked, state.windows.day.remaining, state.windows.day.totalEvents
Subscribe to usage events for monitoring and alerting:
import { tokenLedger } from '@robojs/ai'
export default () => {
tokenLedger.on('usage.recorded', (event) => {
console.log(`${event.model}: +${event.entry.total} tokens`)
})
tokenLedger.on('usage.limitReached', (event) => {
for (const breach of event.breaches) {
console.warn(`${breach.model} exceeded ${breach.window} limit by ${breach.exceededBy}`)
}
})
}import { tokenLedger } from '@robojs/ai'
export default () => {
tokenLedger.on('usage.recorded', (event) => {
console.log(`${event.model}: +${event.entry.total} tokens`)
})
tokenLedger.on('usage.limitReached', (event) => {
for (const breach of event.breaches) {
console.warn(`${breach.model} exceeded ${breach.window} limit by ${breach.exceededBy}`)
}
})
}The event emitter supports tokenLedger.on(), tokenLedger.once(), and tokenLedger.off().
Programmatic configuration
Configure the ledger at runtime instead of (or in addition to) the plugin config:
import { tokenLedger } from '@robojs/ai'
export default () => {
tokenLedger.configure({
limits: {
perModel: {
'gpt-4o': { window: 'day', maxTokens: 50000, mode: 'block' }
}
},
hooks: {
onRecorded: (payload) => { /* ... */ },
onLimitReached: (payload) => { /* ... */ }
}
})
}import { tokenLedger } from '@robojs/ai'
export default () => {
tokenLedger.configure({
limits: {
perModel: {
'gpt-4o': { window: 'day', maxTokens: 50000, mode: 'block' }
}
},
hooks: {
onRecorded: (payload) => { /* ... */ },
onLimitReached: (payload) => { /* ... */ }
}
})
}Time windows
Usage is aggregated by the following windows:
| Window | Key format | Example |
|---|---|---|
day | ISO date | 2025-01-15 |
week | ISO week | 2025-W03 |
month | Year-month | 2025-01 |
lifetime | Cumulative | All-time totals |
Persistence
Token data is persisted via Flashcore under the @robojs/ai/tokens namespace:
aggregateskey -- Per-model lifetime and window totals.entries:YYYY-MM-DDkeys -- Daily entry logs for audit trails.
Data survives restarts and is available immediately on the next boot.
