llm:generation:metering- recommended for LLM integrations. It carries normalized usage and cost, including calls without available pricing.cost:llm:request- the compatible legacy LLM cost event. Existing handlers can keep using it.cost:http:request- emitted when you attach a dollar cost to an HTTP response withaddRequestCostfrom@outputai/core/http.
Setup
Cost events use the same hooks system as error hooks:- Create a hook file and import
onfrom@outputai/core/hooks. - Register a handler for
llm:generation:metering,cost:llm:request,cost:http:request, or a combination. - Add the file path to
outputai.hookFilesinpackage.json.
src/llm_metering_hooks.ts
src/http_cost_hooks.ts
LLM generation metering
When events fire
llm:generation:metering is emitted after a generation ends for text, image, and Agent calls whenever usage is available. For direct streamText() and Agent.stream() usage, it fires when the stream finishes, not when it starts.
A failed generation also emits. Usage is collected as the call progresses, so a call that throws or is aborted after completing at least one step reports what it spent - an output that never validated, a tool loop that broke mid-run, an aborted stream, or a failure in your own code after the model answered. An event is therefore not proof of a successful call; a trace event carrying both usage and an error is a failure that still cost money. Failures before the first step completes report nothing, because no usage exists yet.
One event is emitted per call. Activity retries emit one per attempt, since each attempt calls the provider.
Payload
The handler receives the standard event envelope.payload.usage is always present, and unpriced models produce an incomplete payload.cost:
Normalized usage
LLMGenerationUsage records usage independently from pricing. Aggregate input, output, and total fields make common reads direct; items preserve the provider breakdown.
When the detailed counts do not reconcile with the aggregate count, Output keeps the aggregate input or output item instead of recording a misleading breakdown.
Tool items are recorded independently from tokens and are excluded from the
input, output, and total token aggregates above.
Normalized cost
LLMGenerationCost contains priced items for the normalized usage. Token rates come from the built-in pricing source and are cached for 24 hours; provider-specific tool rates are maintained locally. Each item total is (amount / 1_000_000) * ppm. When the token pricing source is unreachable, its rates come from the last cached copy or from a snapshot bundled with the package, and pricingFreshness records which.
For models priced by context size, Output selects the tier separately for every provider request. The request’s complete input count, including cache reads and writes, determines the tier; output does not count toward the threshold. A tier applies only when input is greater than its threshold, and its rates price the entire request. Multi-step calls can therefore contain multiple cost items with the same group and label but different ppm and tier values. When any step usage is incomplete, Output uses aggregate usage with base pricing and marks token items uncertain; this may understate the cost when an individual request crossed a higher-priced tier.
Legacy LLM request cost
cost:llm:request is emitted on the same completion path when a legacy-priced payload can be calculated. The same payload is retained on the LLM trace as attributes["llm:usage"]. Both remain supported with the same payload shape, so existing event handlers and trace readers do not need to migrate immediately. Its LLMUsageEvent type is deprecated only to steer new integrations toward the normalized event and attributes.
src/legacy_llm_cost_hooks.ts
input, input_cached, output, and reasoning line types only. It intentionally retains the old aggregate pricing, cache-write folding, and reasoning fallback behavior, and its shape is frozen: context tiers and per-request charges such as grounding and provider-native web search are not exposed. Tier and tool costs are only available on the normalized llm:generation:cost attribute and llm:generation:metering event described above. The cost:llm:request payload and llm:usage trace attribute are generated from the same object.
Use llm:generation:metering for new integrations. It is more faithful to the provider response because usage is independent from pricing, cache writes and reasoning remain explicit, provider identity is included, and fallback or missing prices are represented rather than hidden.
HTTP request cost
Events fire only when your code callsaddRequestCost( response, total ) with a response returned by outputFetch or createKyClient. The SDK attaches the cost to the existing HTTP trace event and emits cost:http:request. If the response did not originate from this package, addRequestCost no-ops (with a console warning) and no hook event is emitted.
Payload
The handler receives an event envelope. The HTTP request cost attribute is available underpayload: