LogoRobo.js

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:

config/plugins/robojs/ai.ts
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)
		}
	}
}
config/plugins/robojs/ai.js
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:

src/commands/ask.ts
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)
	}
}
src/commands/ask.js
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 -- The TokenLimitRule that was breached.
  • usageKind -- The type of usage that triggered the limit.
  • displayMessage -- Uses rule.message if set, otherwise auto-generated.

Querying usage

Pull usage data through the AI singleton or tokenLedger directly:

src/commands/usage.ts
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')
src/commands/usage.js
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:

src/commands/check.ts
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.total
src/commands/check.js
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.total

Events

Subscribe to usage events for monitoring and alerting:

src/robo/start.ts
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}`)
		}
	})
}
src/robo/start.js
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:

src/robo/start.ts
import { tokenLedger } from '@robojs/ai'

export default () => {
	tokenLedger.configure({
		limits: {
			perModel: {
				'gpt-4o': { window: 'day', maxTokens: 50000, mode: 'block' }
			}
		},
		hooks: {
			onRecorded: (payload) => { /* ... */ },
			onLimitReached: (payload) => { /* ... */ }
		}
	})
}
src/robo/start.js
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:

WindowKey formatExample
dayISO date2025-01-15
weekISO week2025-W03
monthYear-month2025-01
lifetimeCumulativeAll-time totals

Persistence

Token data is persisted via Flashcore under the @robojs/ai/tokens namespace:

  • aggregates key -- Per-model lifetime and window totals.
  • entries:YYYY-MM-DD keys -- Daily entry logs for audit trails.

Data survives restarts and is available immediately on the next boot.

Next steps

On this page