StreamEvent

Sealed event stream emitted by LanguageModel.stream and surfaced to application code via Agent.stream. Mirrors the v6 stream-part taxonomy (per packages/ai/src/generate-text/stream-text-result.ts in vercel/ai) adapted to Kotlin sealed classes for exhaustive when.

v6 organizes blocks (text, reasoning, tool input) with block IDs so a single message can interleave multiple separate text segments, reasoning blocks, and tool calls without merging them. Each *-start event opens a block by ID; *-delta events grow it; *-end closes it.

providerMetadata (per historical parity gap #11) rides on every content + tool-lifecycle variant — the Map<String, JsonElement>? escape hatch v6 uses for provider-specific payloads (Anthropic thinking signatures + cache_control, OpenAI reasoning effort / logprobs, prompt-cache hints). On-device LiteRT-LM leaves it null; it's the slot a cloud provider fills. Excluded from the pure control singletons (StreamStart, Abort), the terminal Error, and Raw (whose rawValue already IS the provider payload).

Sequence the model can produce in one step:

StreamStart(warnings)
StepStart
TextStart("t1") → TextDelta("t1", "...") → TextEnd("t1")
ReasoningStart("r1") → ReasoningDelta("r1", "...") → ReasoningEnd("r1")
ToolInputStart("ti1", "weather") → ToolInputDelta("ti1", "{...") → ToolInputEnd("ti1")
ToolCall("call_1", "weather", inputJson)
[ToolApprovalRequest("call_1", "weather", inputJson) → loop ends]
[or] ToolResult("call_1", "weather", outputJson)
[or] ToolError("call_1", "weather", message)
TextStart("t2") → TextDelta(...) → TextEnd("t2")
SourcePart(...) → FilePart(...)
Raw(...) — provider-specific escape hatch
StepFinish(stepNumber, finishReason, usage)
...
Finish(totalSteps, finishReason, usage)
Abort | Error — terminal alternatives

Inheritors

Types

Link copied to clipboard
@SerialName(value = "abort")
data object Abort : StreamEvent

Generation aborted via AbortSignal. Loop unwinds.

Link copied to clipboard
object Companion

Wire-protocol mapping helpers for ai.torad.aisdk.ui.ToUIMessageStream: the StreamEvent→chunk dispatch table plus the JSON-chunk builder and finish-reason mapping. Member-extensions so callers use a member-import, not a loose top-level fn.

Link copied to clipboard
@SerialName(value = "data")
class Data(val name: String, val data: JsonElement, val id: String? = null, val transient: Boolean = false) : StreamEvent

Custom UI data part. Encodes to the v6 UI-message chunk shape { type: "data-$name", id?, data, transient? }.

Link copied to clipboard
@SerialName(value = "error")
class Error(val message: String, val cause: Throwable? = null) : StreamEvent

Terminal error. Loop unwinds.

Link copied to clipboard
@SerialName(value = "file")
class FilePart(val id: String, val mediaType: String, val base64: String, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

Generated file (image, audio, etc.).

Link copied to clipboard
@SerialName(value = "finish")
class Finish(val totalSteps: Int, val finishReason: FinishReason, val usage: Usage, val providerMetadata: ProviderMetadata = ProviderMetadata.None, val rawFinishReason: String? = null) : StreamEvent

Loop ended — final aggregated finish reason + usage.

Link copied to clipboard
@SerialName(value = "raw")
class Raw(val rawValue: JsonElement) : StreamEvent

Unprocessed provider-specific chunk. Escape hatch for cutting-edge provider features that the SDK doesn't yet wrap in a typed event. Off by default — providers opt in via call params. rawValue already IS the provider payload, so no separate providerMetadata slot.

Link copied to clipboard
@SerialName(value = "reasoning-delta")
class ReasoningDelta(val id: String, val text: String, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent
Link copied to clipboard
@SerialName(value = "reasoning-end")
class ReasoningEnd(val id: String, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent
Link copied to clipboard
@SerialName(value = "reasoning-start")
class ReasoningStart(val id: String, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent
Link copied to clipboard
@SerialName(value = "response-metadata")
class ResponseMetadata(val id: String? = null, val timestampMillis: Long? = null, val modelId: String? = null, val headers: Map<String, String> = emptyMap(), val body: JsonElement? = null) : StreamEvent

Provider response metadata that becomes available after the stream request starts. Mirrors v6's response-metadata stream part so StreamTextResult.response can expose IDs, timestamps, model IDs, headers, and retained response bodies.

Link copied to clipboard
@SerialName(value = "source")
class SourcePart(val id: String, val sourceType: StreamEvent.SourcePart.SourceType, val url: String? = null, val title: String? = null, val mediaType: String? = null, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

Citation / web grounding source.

Link copied to clipboard
@SerialName(value = "step-finish")
class StepFinish(val stepNumber: Int, val finishReason: FinishReason, val usage: Usage, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

Step ended — aggregated finish reason + usage for that one step. Per historical parity gap #18 (slice), providerMetadata carries provider-specific payloads (Anthropic prompt-cache hints, OpenAI reasoning trace tokens, etc.) so consumers can

Link copied to clipboard
@SerialName(value = "step-start")
class StepStart(val stepNumber: Int, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

New step (one LLM call) began. Emitted at the start of every loop iteration.

Link copied to clipboard
@SerialName(value = "stream-start")
class StreamStart(val warnings: List<CallWarning> = emptyList()) : StreamEvent

Stream began. Emitted exactly once at the very top.

Link copied to clipboard
@SerialName(value = "text-delta")
class TextDelta(val id: String, val text: String, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent
Link copied to clipboard
@SerialName(value = "text-end")
class TextEnd(val id: String, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent
Link copied to clipboard
@SerialName(value = "text-start")
class TextStart(val id: String, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent
Link copied to clipboard
@SerialName(value = "tool-approval-request")
class ToolApprovalRequest(val toolCallId: String, val toolName: String, val inputJson: JsonElement, val approvalId: String? = null, val signature: String? = null, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

Tool requires approval. Per v6 RPC semantics, the loop ends after emitting this event — the corresponding ai.torad.aisdk.ContentPart.ToolApprovalRequest is added to the assistant message in the result, and the host calls Agent.generate again with a tool message containing ai.torad.aisdk.ContentPart.ToolApprovalResponse to resume.

Link copied to clipboard
@SerialName(value = "tool-call")
class ToolCall(val toolCallId: String, val toolName: String, val inputJson: JsonElement, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

Final, parsed tool call envelope. Emitted once ToolInputEnd fires and JSON parses.

Link copied to clipboard
@SerialName(value = "tool-error")
class ToolError(val toolCallId: String, val toolName: String, val message: String, val error: AgentError? = null, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

Tool execution failed. error carries the typed AgentError (NoSuchTool / InvalidToolInput / ToolExecution / ToolCallRepairFailed) so in-process consumers can when (event.error) instead of substring-matching message. @Transient — it's dropped on serialization (AgentError isn't @Serializable); message is the wire-stable rendering and stays the single source of display text.

Link copied to clipboard
@SerialName(value = "tool-input-delta")
class ToolInputDelta(val id: String, val delta: String, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

Incremental input bytes for the in-flight tool call.

Link copied to clipboard
@SerialName(value = "tool-input-end")
class ToolInputEnd(val id: String, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

Tool input streaming ends — full JSON has been received.

Link copied to clipboard
@SerialName(value = "tool-input-start")
class ToolInputStart(val id: String, val toolName: String, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

Tool input streaming opens — the model has decided which tool to call.

Link copied to clipboard
@SerialName(value = "tool-output-denied")
class ToolOutputDenied(val toolCallId: String, val toolName: String, val approvalId: String, val reason: String? = null, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

The host denied a previously requested approval. v6's tool-output-denied (per historical parity gaps #6 + #7). Distinct from ToolError — denial is a CHOICE, not a failure. The matching UI part transitions through ai.torad.aisdk.ui.ToolCallState.OutputDenied.

Link copied to clipboard
@SerialName(value = "tool-result")
class ToolResult(val toolCallId: String, val toolName: String, val outputJson: JsonElement, val output: ToolResultOutput = ToolResultOutputs.toolResultOutputFromJson(outputJson), val modelOutput: ToolResultOutput = output, val isError: Boolean = with(ToolResultOutputs) { modelOutput.isToolResultError() }, val preliminary: Boolean = false, val providerMetadata: ProviderMetadata = ProviderMetadata.None) : StreamEvent

Tool execution emitted a result. By default this is the final result — appended to the model's message log + dispatched to UI converters.

Properties

Link copied to clipboard