import { z } from 'zod'; import type { AgentLLMRequestSource } from '#/agent/llmRequester/llmRequester'; import type { UsageStatus } from '#/agent/usage/usage'; import { AgentStatusUpdated } from '#/agent/usage/usageEvents'; import { copyUsage, UsageRecord, type UsageModelState, type UsageRecordScope, } from '#/agent/usage/usageOps'; import { addUsage, type TokenUsage } from '#human/llm/usage'; import { AgentModel, defineAgentModel, type AgentModelContext } from '#/state/agentModel'; export interface UsageRecordInput { readonly model: string; readonly usage: TokenUsage; readonly source?: AgentLLMRequestSource; } export class UsageAgentModel extends AgentModel { private currentTurnId: number | undefined; private currentTurn: TokenUsage | undefined; constructor(context: AgentModelContext) { super(context); this.on(UsageRecord, (event) => { const current = this.state.byModel[event.model]; this.state.byModel[event.model] = current === undefined ? copyUsage(event.usage) : addUsage(current, event.usage); }); } record(input: UsageRecordInput): Promise { const firstRecord = Object.keys(this.state.byModel).length === 0; const usageScope: UsageRecordScope = input.source?.type === 'turn' ? 'turn' : 'session'; const recorded = this.emit( new UsageRecord({ agentId: this.agent.agentId, model: input.model, usage: input.usage, usageScope, }), ); const turnId = input.source?.type === 'turn' ? input.source.turnId : undefined; if (turnId !== undefined) { if (this.currentTurnId !== turnId) { this.currentTurnId = turnId; this.currentTurn = copyUsage(input.usage); } else { this.currentTurn = this.currentTurn === undefined ? copyUsage(input.usage) : addUsage(this.currentTurn, input.usage); } } const notified = this.emit( new AgentStatusUpdated({ agentId: this.agent.agentId, usage: this.status() }), ); return recorded.then(() => notified).then(() => firstRecord); } status(): UsageStatus { const byModel = Object.fromEntries( Object.entries(this.state.byModel).map(([model, usage]) => [model, copyUsage(usage)]), ); const hasByModel = Object.keys(byModel).length > 0; let total: TokenUsage | undefined; if (hasByModel) { for (const usage of Object.values(byModel)) { total = total === undefined ? copyUsage(usage) : addUsage(total, usage); } } return { byModel: hasByModel ? byModel : undefined, total, currentTurn: this.currentTurn === undefined ? undefined : copyUsage(this.currentTurn), }; } } export const UsageAgentModelDefinition = defineAgentModel({ id: 'usage', model: UsageAgentModel, state: { initial: (): UsageModelState => ({ byModel: {} }), schema: z.custom(), }, events: [UsageRecord], });