Billing export
Inject AGENT_GOVERNANCE_QUERIES and build your own CSV/JSON spend export off the same read-model the standalone dashboard and Telescope tab consume — the interface and the diagnostics channel are both public.
The shipped governance console (@dudousxd/nestjs-agent-dashboard) and the Telescope "Agent" tab
both read spend and usage off one interface. Nothing stops your own code from reading the same
interface — reach for this when finance wants a CSV per actor, you're feeding a third-party billing
system, or you just don't want to stand up either shipped surface.
The contract
From governance-queries.ts:
/** Inclusive UTC day range, each YYYY-MM-DD. */
interface GovernanceRange {
fromDay: string;
toDay: string;
}
interface ActorSpendRow {
actorRef: string;
requests: number;
totalTokens: number;
costUsd: number;
}
interface AgentGovernanceQueries {
spendByModel(range: GovernanceRange): Promise<ModelSpendRow[]>;
spendByActor(range: GovernanceRange): Promise<ActorSpendRow[]>;
usageTrend(range: GovernanceRange): Promise<UsageTrendPoint[]>;
recentToolCalls(limit: number): Promise<ToolCallActivityRow[]>;
recentThreads(limit: number): Promise<ThreadActivityRow[]>;
// ...plus reliability (runMetrics/runsByAgent/runErrors/runTrend/recentRuns), toolStats,
// pendingApprovals, and paginated toolCallsPage/threadsPage/runsPage — all REQUIRED members
// an external adapter must implement (see the source link above for the full, current shape).
}Consumers inject it via the AGENT_GOVERNANCE_QUERIES token; whichever store adapter you configured
(MikroORM, Drizzle, or InMemoryGovernanceQueries in tests) binds it. The interface has grown a lot
beyond spend/usage since this recipe was first written — this snippet keeps only the members a
billing export actually reads; see Endpoints
for what the rest feed.
A CSV export service
// src/billing/billing-export.service.ts
import { AGENT_GOVERNANCE_QUERIES, type AgentGovernanceQueries } from '@dudousxd/nestjs-agent';
import { Inject, Injectable } from '@nestjs/common';
@Injectable()
export class BillingExportService {
constructor(
@Inject(AGENT_GOVERNANCE_QUERIES)
private readonly governance: AgentGovernanceQueries,
) {}
async spendCsv(fromDay: string, toDay: string): Promise<string> {
const rows = await this.governance.spendByActor({ fromDay, toDay });
const header = 'actorRef,requests,totalTokens,costUsd';
const lines = rows.map(
(row) => `${row.actorRef},${row.requests},${row.totalTokens},${row.costUsd.toFixed(4)}`,
);
return [header, ...lines].join('\n');
}
}Mount it under your own controller — this is ordinary application code, not a library surface:
// src/billing/billing-export.controller.ts
import { Controller, Get, Header, Query, Res } from '@nestjs/common';
import type { Response } from 'express';
import { BillingExportService } from './billing-export.service.js';
@Controller('billing')
export class BillingExportController {
constructor(private readonly billing: BillingExportService) {}
@Get('export.csv')
@Header('content-type', 'text/csv')
async export(
@Query('from') fromDay: string,
@Query('to') toDay: string,
@Res() res: Response,
): Promise<void> {
res.send(await this.billing.spendCsv(fromDay, toDay));
}
}@Module({
controllers: [BillingExportController],
providers: [BillingExportService],
})
export class BillingModule {}The read-model and diagnostics are both public
Beyond the shipped surfaces, your app can query AGENT_GOVERNANCE_QUERIES for its own billing
export, or subscribe to the aviary:agent:* diagnostics channel for alerts and metrics — the
library instruments nothing in your code, so neither integration point is off-limits. spendByModel
and usageTrend follow the same GovernanceRange shape if you need a per-model or a daily-trend
export instead.
Related
- Cost & Governance — the ledger, reported-cost-wins, cache-aware pricing, and the two shipped surfaces this export sits alongside
- Offline testing —
InMemoryGovernanceQueriesfor testing an export like this one without a database
Functional tools with DI
Register a tool that needs constructor-injected dependencies without a class — provideAgentTool(factory, inject) resolves it through Nest's container and returns { spec, handler }, auto-discovered at boot exactly like an @AiTool class.
Offline testing
Exercise the whole agent loop — chat, tool calling, HITL approval — with @dudousxd/nestjs-agent-testing's in-memory doubles. No API key, no Redis, fully deterministic.