> ## Documentation Index
> Fetch the complete documentation index at: https://growthx-search-attribute-identifier.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# v0.13.0 → v0.14.0

> Upgrading Output.ai projects from v0.13.0 to v0.14.0: failed and aborted LLM calls now report the usage and cost they already spent, and trace node ids for steps and activities changed shape.

This guide covers a billing behavior change in `@outputai/llm`: LLM calls that fail after the model ran now report usage and cost instead of reporting nothing, so the same traffic produces more metering events and a higher total. One smaller break comes with it: a failure in the SDK's own response handling is no longer retryable.

A separate change in `@outputai/core` affects anything that reads trace files: step, evaluator, and internal activity nodes have a new `id` shape. See [Trace node ids](#trace-node-ids) at the end of this guide.

## What changed

Usage and cost used to be read from the value a call returned. A call that threw returned nothing, so everything it had already spent went unreported - even when the provider had answered and the failure came afterwards, in output validation, in the tool loop, or in your own code.

Usage is now collected from the AI SDK lifecycle events as the call progresses, so billing no longer depends on reaching a return statement. Every generation that produced at least one completed step reports what that step cost, whether the call ends with a response, an error, or an abort.

Nothing changed in the shape of `LLMGenerationUsage`, `LLMGenerationCost`, the `llm:generation:usage` / `llm:generation:cost` trace attributes, or the `llm:generation:metering` and `cost:llm:request` events. A failing call still throws, and the thrown error still carries no cost - the numbers reach the trace and the events only.

Response sources are now filtered on the way out: entries carrying neither a url nor an id are dropped, and a malformed `sources` payload is logged and yields no sources instead of failing the call. Sources that survive are returned exactly as the AI SDK produced them.

## Where cost is now recovered

### `generateText` / `Agent.generate`

* Output or schema validation failure after the model answered (`NoObjectGeneratedError`, `NoOutputGeneratedError`)
* Tool loop failing after at least one step finished
* Your own post-response code throwing, including an `Agent.generate` message store failure
* Abort after at least one step finished

### `generateTextWithStreaming` / `Agent.generateWithStreaming`

* Provider error part mid stream, once the stream closes
* Abort mid stream, after at least one step finished
* Output parse or validation failure on the structured output
* Message store failure after the stream finished (`Agent.generateWithStreaming`)

### `streamText` / `Agent.stream`

* Error part that ends the run with steps already collected
* Abort after at least one step finished

## Where it still is not

Nothing was spent, or nothing was reported, so there is nothing to bill:

* Any failure before the first step completes: authentication, a rejected request, an unknown model or provider, a connection error
* An abort before the first step completes, for every method
* A provider that stalls without closing the stream after a failure, if the step events had not arrived before the stream went quiet for 250 ms (`generateTextWithStreaming`, `Agent.generateWithStreaming`)
* A `streamText` consumer that abandons the stream instead of reading it
* `generateImage`, which has no step loop: a failure there always precedes any usage

## Migration steps

### Re-baseline cost dashboards and budgets

Totals rise for unchanged traffic, by the cost of failures that were previously invisible. The size of the shift depends on how often your workflows hit the paths above - a workflow with a strict output schema and a retrying activity can move noticeably, since each attempt bills the tokens it spent.

If you alert on a cost threshold, expect the step change at the upgrade and reset the baseline rather than treating it as a spike.

### Expect metering events on failed runs

Code that assumed a `llm:generation:metering` or `cost:llm:request` event implies a successful generation no longer holds. Billing is emitted once per call, and per attempt when an activity retries.

To distinguish the two cases, read the trace: an LLM event that also carries an error is a failed call that still spent tokens.

### Re-check retries around response-handling failures

A failure in the SDK's own response handling - reading the model response, merging sources, building the returned proxy - now throws a non-retryable `FatalError` wrapping the original error, where before the original error surfaced unchanged. Such a failure is deterministic, so a retry pays for the model call again and fails the same way; an activity that used to recover on a later attempt now fails permanently. Errors from the provider and from the AI SDK are unaffected and stay as retryable as they were.

The generic streaming error messages were normalized at the same time: `Streaming generation aborted.` is now `Streaming aborted.`, `Streaming generation failed.` is now `Streaming failed.`, and both `Streaming generation completed without a response.` and `Agent streaming generation completed without a response.` are now `Streaming completed without a response.`. They only appear when the AI SDK reports a failure that carries no error of its own, so this matters only to code matching on the text.

### Nothing to change in call sites

No signatures, options, or return shapes changed, and `cost` on a returned response behaves as before. The only code-level effect is the one above: the retry semantics of a response-handling failure.

## Trace node ids

In trace files, the `id` of step, evaluator, and internal activity nodes was Temporal's activity id. It is now `<runId>:<activityId>:<attempt>`:

```json theme={null}
"id": "019a3c1f-8e4d-7a52-b0c6-1f7d9e2a4b83:2:1"
```

Temporal numbers activity ids per workflow run and reuses them on retry, so the old ids were not unique within a trace file. Entries that collided were merged into one node, which was then listed once per colliding entry. A retried step reported only its last attempt's input, error, and timing while carrying every attempt's LLM and HTTP calls as its children, and a child workflow's step appeared a second time directly under the root, overwriting whatever the root's own activity with that number was.

Two visible differences in new traces:

* A retried step appears once per attempt, as sibling nodes sharing the same `name`. A step that failed twice before succeeding is three nodes, not one.
* A child workflow's steps appear only inside that child workflow's node.

Reported cost is unchanged. Duplicated nodes were already ignored by id when costs were summed, so the same generations are counted, once each.

### Update readers that key on node ids

Code that treats a step's `id` as Temporal's activity id needs to change. Run ids contain no colon, so splitting the `id` on `:` always yields three parts and the activity id is the middle one. Prefer matching on `name` and the enclosing workflow node where you can, since that does not depend on the id format.

Code that reads a step's `children` to reason about a run should expect them spread across one node per attempt rather than pooled on a single node.

`output workflow debug`, `output workflow cost`, and `output workflow dataset generate` need no changes. The run detail view lists one entry per attempt, so a retried step appears more than once.
