Skip to main content

llmsdk_provider/language_model/
stream_part.rs

1//! Streamed delta parts produced by `do_stream`.
2//!
3//! Mirrors `language-model-v4-stream-part.ts`. Wire format and tag names
4//! match ai-sdk exactly.
5// Rust guideline compliant 2026-02-21
6
7use serde::{Deserialize, Serialize};
8
9use crate::json::JsonValue;
10use crate::shared::{ProviderMetadata, Warning};
11
12use crate::shared::FileData;
13
14use super::content::{Source, ToolApprovalRequest, ToolResult};
15use super::finish_reason::FinishReason;
16use super::prompt::{FilePart, ToolCallPart};
17use super::result::ResponseMetadata;
18use super::usage::Usage;
19
20/// One unit emitted on the stream returned by
21/// [`super::LanguageModel::do_stream`].
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23#[serde(tag = "type", rename_all = "kebab-case")]
24pub enum StreamPart {
25    /// Start of a text block.
26    TextStart {
27        /// Block id (used to correlate `text-delta` / `text-end`).
28        id: String,
29        /// Provider-specific metadata.
30        #[serde(
31            default,
32            rename = "providerMetadata",
33            skip_serializing_if = "Option::is_none"
34        )]
35        provider_metadata: Option<ProviderMetadata>,
36    },
37    /// Incremental text fragment.
38    TextDelta {
39        /// Block id.
40        id: String,
41        /// Text fragment.
42        delta: String,
43        /// Provider-specific metadata.
44        #[serde(
45            default,
46            rename = "providerMetadata",
47            skip_serializing_if = "Option::is_none"
48        )]
49        provider_metadata: Option<ProviderMetadata>,
50    },
51    /// End of a text block.
52    TextEnd {
53        /// Block id.
54        id: String,
55        /// Provider-specific metadata.
56        #[serde(
57            default,
58            rename = "providerMetadata",
59            skip_serializing_if = "Option::is_none"
60        )]
61        provider_metadata: Option<ProviderMetadata>,
62    },
63    /// Start of a reasoning block.
64    ReasoningStart {
65        /// Block id.
66        id: String,
67        /// Provider-specific metadata.
68        #[serde(
69            default,
70            rename = "providerMetadata",
71            skip_serializing_if = "Option::is_none"
72        )]
73        provider_metadata: Option<ProviderMetadata>,
74    },
75    /// Incremental reasoning fragment.
76    ReasoningDelta {
77        /// Block id.
78        id: String,
79        /// Reasoning fragment.
80        delta: String,
81        /// Provider-specific metadata.
82        #[serde(
83            default,
84            rename = "providerMetadata",
85            skip_serializing_if = "Option::is_none"
86        )]
87        provider_metadata: Option<ProviderMetadata>,
88    },
89    /// End of a reasoning block.
90    ReasoningEnd {
91        /// Block id.
92        id: String,
93        /// Provider-specific metadata.
94        #[serde(
95            default,
96            rename = "providerMetadata",
97            skip_serializing_if = "Option::is_none"
98        )]
99        provider_metadata: Option<ProviderMetadata>,
100    },
101    /// Start of a tool input being streamed.
102    ToolInputStart {
103        /// Tool call id.
104        id: String,
105        /// Tool name.
106        #[serde(rename = "toolName")]
107        tool_name: String,
108        /// `true` if executed by the provider.
109        #[serde(
110            default,
111            rename = "providerExecuted",
112            skip_serializing_if = "Option::is_none"
113        )]
114        provider_executed: Option<bool>,
115        /// `true` if defined at runtime.
116        #[serde(default, skip_serializing_if = "Option::is_none")]
117        dynamic: Option<bool>,
118        /// Optional display title.
119        #[serde(default, skip_serializing_if = "Option::is_none")]
120        title: Option<String>,
121        /// Provider-specific metadata.
122        #[serde(
123            default,
124            rename = "providerMetadata",
125            skip_serializing_if = "Option::is_none"
126        )]
127        provider_metadata: Option<ProviderMetadata>,
128    },
129    /// Streamed chunk of a tool's input JSON.
130    ToolInputDelta {
131        /// Tool call id.
132        id: String,
133        /// JSON fragment.
134        delta: String,
135        /// Provider-specific metadata.
136        #[serde(
137            default,
138            rename = "providerMetadata",
139            skip_serializing_if = "Option::is_none"
140        )]
141        provider_metadata: Option<ProviderMetadata>,
142    },
143    /// End of a tool's input stream.
144    ToolInputEnd {
145        /// Tool call id.
146        id: String,
147        /// Provider-specific metadata.
148        #[serde(
149            default,
150            rename = "providerMetadata",
151            skip_serializing_if = "Option::is_none"
152        )]
153        provider_metadata: Option<ProviderMetadata>,
154    },
155    /// Approval requested for a provider-executed tool call.
156    ToolApprovalRequest(ToolApprovalRequest),
157    /// Final tool call with assembled input.
158    ToolCall(ToolCallPart),
159    /// Tool result emitted by a provider-executed tool.
160    ToolResult(ToolResult),
161    /// Provider-specific custom content.
162    Custom {
163        /// Custom kind tag, e.g. `"openai.web_search_result"`.
164        kind: String,
165        /// Provider-specific metadata.
166        #[serde(
167            default,
168            rename = "providerMetadata",
169            skip_serializing_if = "Option::is_none"
170        )]
171        provider_metadata: Option<ProviderMetadata>,
172    },
173    /// Citation / grounding source.
174    Source(Source),
175    /// File generated by the model (mid-stream emission).
176    ///
177    /// Mirrors ai-sdk's `LanguageModelV4File` stream part. The wire tag is
178    /// `"file"` and shape matches a [`FilePart`] (filename / data / media
179    /// type / provider options).
180    File(FilePart),
181    /// File generated as part of a reasoning trace (mid-stream emission).
182    ///
183    /// Mirrors ai-sdk's `LanguageModelV4ReasoningFile` stream part. Wire
184    /// tag is `"reasoning-file"`.
185    ReasoningFile {
186        /// File payload.
187        data: FileData,
188        /// IANA media type.
189        #[serde(rename = "mediaType")]
190        media_type: String,
191        /// Provider-specific metadata.
192        #[serde(
193            default,
194            rename = "providerMetadata",
195            skip_serializing_if = "Option::is_none"
196        )]
197        provider_metadata: Option<ProviderMetadata>,
198    },
199    /// Stream-start metadata.
200    StreamStart {
201        /// Warnings for the call.
202        warnings: Vec<Warning>,
203    },
204    /// Response-level metadata available mid-stream.
205    ResponseMetadata(ResponseMetadata),
206    /// Terminal frame with totals.
207    Finish {
208        /// Final token usage.
209        usage: Usage,
210        /// Why the model stopped.
211        #[serde(rename = "finishReason")]
212        finish_reason: FinishReason,
213        /// Provider-specific metadata.
214        #[serde(
215            default,
216            rename = "providerMetadata",
217            skip_serializing_if = "Option::is_none"
218        )]
219        provider_metadata: Option<ProviderMetadata>,
220    },
221    /// Raw provider chunk (only when `include_raw_chunks` is set).
222    Raw {
223        /// Provider-native value.
224        #[serde(rename = "rawValue")]
225        raw_value: JsonValue,
226    },
227    /// In-stream error from the provider.
228    ///
229    /// The stream is still alive; the outer `Result` is `Ok`.
230    Error {
231        /// Error payload as provided by the upstream.
232        error: JsonValue,
233    },
234}