Skip to content

Event model

Every adapter turns its source’s on-disk format into the same normalized events. The types live in the core package, @ayagmar/agent-sessions (packages/core/src/domain/usage-event.ts and session-event.ts); the CLI and library users see the same shapes.

The event unionEvery event has source, sessionId and timestamp, plus raw when the read asks for it, and a kind that is one of four. usage: inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens and totalTokens, with optional singleRequest, costUsd, model, provider, repoRoot, rawProvider, messageId and requestId. message: role and textLength, with optional text, model and messageId. tool_call: name and status, with optional toolCallId, completedAt, durationMs and messageId. session_meta: all optional, cwd, title, gitBranch, agentVersion, parentSessionId, model and rawProvider. read() yields usage events only; readEvents() also yields the other three kinds for claude, codex and pi.every kindsource sessionId timestamp raw?kind: usage | message | tool_call | session_metausageone usage record: tokens and costinputTokensoutputTokensreasoningTokenscacheReadTokenscacheWriteTokenscacheWrite1hTokenstotalTokenssingleRequest?costUsd?model?provider?repoRoot?rawProvider?messageId?requestId?messagea user, assistant or system turnroletextLengthtext?model?messageId?tool_callone tool call and its outcomenamestatustoolCallId?completedAt?durationMs?messageId?session_metafacts about the sessioncwd?title?gitBranch?agentVersion?parentSessionId?model?rawProvider?? marks an optional field. read() yields usage only;readEvents() adds the other kinds for claude, codex, pi.

SessionEvent is a union on kind (EVENT_KINDS, in this order):

  • usage: one usage record as the source logged it. An event with singleRequest: true is one model request; other records can sum several requests (see per-request usage). Requires inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, and totalTokens.
  • message: a user, assistant, or system turn. Requires role (user, assistant, or system) and textLength.
  • tool_call: one tool call, yielded when its result is seen. Requires name and status (ok, error, or unknown).
  • session_meta: facts about the session. Every field is optional: cwd, title, gitBranch, agentVersion, parentSessionId, model, rawProvider.

Every kind also carries source, sessionId, and timestamp (ISO 8601, UTC). raw holds the source record only when the read asks for it.

Message text is optional: text is set only when the read asks for it, and is kept exactly as logged. textLength is always set. A tool call with no result by the end of the file is still yielded, with status: 'unknown'.

  • adapter.read(filePath) yields plain UsageEvent objects: the usage fields without kind. This is what the CLI uses for every report.
  • readEvents(adapter, filePath, options) yields SessionEvent objects. The claude, codex, and pi adapters implement it and can yield all four kinds; for any other adapter it wraps read() and yields kind: 'usage' records. capabilities.kinds lists what an adapter can yield; unset means usage only.

A usage record from readEvents can also carry messageId, requestId, and rawProvider (the provider as logged, unset when the adapter inferred it). The library guide shows both reads.

The usage fields follow one convention across all sources:

  • inputTokens is fresh input. It excludes cache reads and cache writes.
  • outputTokens includes reasoning. reasoningTokens is a breakdown of it, not an extra bucket.
  • cacheWrite1hTokens is the part of cacheWriteTokens cached for one hour; it is capped at cacheWriteTokens.
  • totalTokens is the total the source logged. When a source logs none, createUsageEvent sums input, output, reasoning, cache reads, and cache writes, so an adapter whose output includes reasoning passes totalTokens itself (claude does).
  • costUsd is a cost the source logged, in USD. It is unset when the source logged none; pricing fills in estimates later.

The constructors (createUsageEvent, createUsageRecord, createMessageEvent, createToolCallEvent, createSessionMetaEvent) are the only way adapters build events, so normalization happens once:

  • An empty source or sessionId, or a timestamp that does not parse, throws. An ISO date and time with no offset (2026-01-02T03:04:05) reads as UTC, so the events do not depend on the machine’s time zone.
  • Token counts become non-negative integers; a missing or non-finite count becomes 0. A usage count above 10¹² throws, and the adapter skips the row and counts it among the skipped rows.
  • model is trimmed, lowercased, and capped at 256 characters. Text fields lose control characters.
  • provider is normalized to the billing entity: openai-codex and any openai- or openai/ prefix become openai, github-copilot becomes github. When a source logs no provider, adapters infer one from the model name (gpt-, o1/o3/o4 → openai, claude → anthropic, gemini → google).
  • createUsageEvent always sets repoRoot, provider, model, and costUsd, to undefined when unknown, so Object.hasOwn(event, 'costUsd') can be true with no cost. The other constructors leave undefined keys out, as does createUsageRecord for the messageId, requestId, rawProvider, and raw it adds. JSON.stringify drops undefined values, so serialized events of every kind hold only the fields that are set.

USAGE_EVENT_NORMALIZATION_VERSION (currently 3) is part of every parser version. Changing normalization changes it, which re-reads every stored file; see Dedup and cursors.

No event has a project field. Usage events carry repoRoot, the working directory the source logged (for Gemini, looked up in its project map), never resolved to a .git root; session metadata carries cwd. Reports group by project on repoRoot without trailing separators; usage with no repoRoot is (unattributed).

The CLI’s own UsageEvent (src/domain/usage-event.ts) extends the core type with two fields:

  • costMode: explicit when the event has a logged costUsd, else estimated. Pricing reads it to decide which events to estimate.
  • machine: the configured machine an event came from; unset for this machine. See Machine merge.