Skip to main content

ferrin_spec/language_model/
stream_part.rs

1//! Stream parts emitted by `do_stream`.
2
3use chrono::DateTime;
4use chrono::Utc;
5use serde::Deserialize;
6use serde::Serialize;
7
8use super::content::CustomKind;
9use super::content::ProviderToolResult;
10use super::content::Source;
11use super::content::ToolCall;
12use super::finish_reason::FinishReason;
13use super::usage::Usage;
14use crate::error::ProviderError;
15use crate::json::JsonValue;
16use crate::shared::ApprovalId;
17use crate::shared::FileData;
18use crate::shared::MediaType;
19use crate::shared::ModelId;
20use crate::shared::PartId;
21use crate::shared::ProviderMetadata;
22use crate::shared::ToolCallId;
23use crate::shared::ToolName;
24use crate::shared::Warning;
25
26/// A part of a language model stream, tagged by `type`.
27///
28/// Ordering contract: a stream starts with `StreamStart` and ends with
29/// `Finish` (or `Error`); `TextDelta` parts appear between `TextStart` and
30/// `TextEnd` with the same id; tool input appears as `ToolInputStart`, zero
31/// or more `ToolInputDelta`, `ToolInputEnd`, then `ToolCall`.
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33#[serde(tag = "type", rename_all = "kebab-case")]
34#[non_exhaustive]
35pub enum StreamPart {
36    /// First part of every stream.
37    StreamStart {
38        /// Warnings produced while preparing the call.
39        #[serde(default)]
40        warnings: Vec<Warning>,
41    },
42    /// Response metadata, emitted once known.
43    ResponseMetadata {
44        /// Provider response id.
45        #[serde(default, skip_serializing_if = "Option::is_none")]
46        id: Option<String>,
47        /// Response timestamp.
48        #[serde(default, skip_serializing_if = "Option::is_none")]
49        timestamp: Option<DateTime<Utc>>,
50        /// Model that produced the response.
51        #[serde(default, skip_serializing_if = "Option::is_none")]
52        model_id: Option<ModelId>,
53    },
54    /// Start of a text part.
55    TextStart {
56        /// Part id.
57        id: PartId,
58        /// Provider-specific metadata.
59        #[serde(default, skip_serializing_if = "Option::is_none")]
60        provider_metadata: Option<ProviderMetadata>,
61    },
62    /// Text increment.
63    TextDelta {
64        /// Part id.
65        id: PartId,
66        /// Appended text.
67        delta: String,
68        /// Provider-specific metadata.
69        #[serde(default, skip_serializing_if = "Option::is_none")]
70        provider_metadata: Option<ProviderMetadata>,
71    },
72    /// End of a text part.
73    TextEnd {
74        /// Part id.
75        id: PartId,
76        /// Provider-specific metadata.
77        #[serde(default, skip_serializing_if = "Option::is_none")]
78        provider_metadata: Option<ProviderMetadata>,
79    },
80    /// Start of a reasoning part.
81    ReasoningStart {
82        /// Part id.
83        id: PartId,
84        /// Provider-specific metadata.
85        #[serde(default, skip_serializing_if = "Option::is_none")]
86        provider_metadata: Option<ProviderMetadata>,
87    },
88    /// Reasoning increment.
89    ReasoningDelta {
90        /// Part id.
91        id: PartId,
92        /// Appended reasoning text.
93        delta: String,
94        /// Provider-specific metadata.
95        #[serde(default, skip_serializing_if = "Option::is_none")]
96        provider_metadata: Option<ProviderMetadata>,
97    },
98    /// End of a reasoning part.
99    ReasoningEnd {
100        /// Part id.
101        id: PartId,
102        /// Provider-specific metadata.
103        #[serde(default, skip_serializing_if = "Option::is_none")]
104        provider_metadata: Option<ProviderMetadata>,
105    },
106    /// Start of streamed tool input.
107    ToolInputStart {
108        /// Tool call id.
109        id: ToolCallId,
110        /// Name of the tool.
111        tool_name: ToolName,
112        /// Whether the provider executes the tool itself.
113        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
114        provider_executed: bool,
115        /// Whether the tool is dynamic.
116        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
117        dynamic: bool,
118        /// Human-readable title for display, if the provider supplies one.
119        #[serde(default, skip_serializing_if = "Option::is_none")]
120        title: Option<String>,
121        /// Provider-specific metadata.
122        #[serde(default, skip_serializing_if = "Option::is_none")]
123        provider_metadata: Option<ProviderMetadata>,
124    },
125    /// Tool input increment (raw JSON text).
126    ToolInputDelta {
127        /// Tool call id.
128        id: ToolCallId,
129        /// Appended JSON text.
130        delta: String,
131        /// Provider-specific metadata.
132        #[serde(default, skip_serializing_if = "Option::is_none")]
133        provider_metadata: Option<ProviderMetadata>,
134    },
135    /// End of streamed tool input.
136    ToolInputEnd {
137        /// Tool call id.
138        id: ToolCallId,
139        /// Provider-specific metadata.
140        #[serde(default, skip_serializing_if = "Option::is_none")]
141        provider_metadata: Option<ProviderMetadata>,
142    },
143    /// A complete tool call.
144    ToolCall(ToolCall),
145    /// A provider-executed tool result.
146    ToolResult(ProviderToolResult),
147    /// The provider asks for approval before executing a tool call.
148    ToolApprovalRequest {
149        /// Identifier of the approval request.
150        approval_id: ApprovalId,
151        /// Identifier of the tool call awaiting approval.
152        tool_call_id: ToolCallId,
153        /// Provider-specific metadata.
154        #[serde(default, skip_serializing_if = "Option::is_none")]
155        provider_metadata: Option<ProviderMetadata>,
156    },
157    /// A generated file.
158    File {
159        /// File payload.
160        data: FileData,
161        /// Media type of the payload.
162        media_type: MediaType,
163        /// Optional file name.
164        #[serde(default, skip_serializing_if = "Option::is_none")]
165        filename: Option<String>,
166        /// Provider-specific metadata.
167        #[serde(default, skip_serializing_if = "Option::is_none")]
168        provider_metadata: Option<ProviderMetadata>,
169    },
170    /// A reasoning artifact stored as a file.
171    ReasoningFile {
172        /// File payload.
173        data: FileData,
174        /// Media type of the payload.
175        media_type: MediaType,
176        /// Provider-specific metadata.
177        #[serde(default, skip_serializing_if = "Option::is_none")]
178        provider_metadata: Option<ProviderMetadata>,
179    },
180    /// A citation source.
181    Source(Source),
182    /// Provider-specific content.
183    Custom {
184        /// Kind of the content.
185        kind: CustomKind,
186        /// Provider-specific metadata carrying the payload.
187        #[serde(default, skip_serializing_if = "Option::is_none")]
188        provider_metadata: Option<ProviderMetadata>,
189    },
190    /// Last part of a successful stream.
191    Finish {
192        /// Why generation stopped.
193        finish_reason: FinishReason,
194        /// Token usage of the call.
195        usage: Usage,
196        /// Provider-specific metadata.
197        #[serde(default, skip_serializing_if = "Option::is_none")]
198        provider_metadata: Option<ProviderMetadata>,
199    },
200    /// A raw provider chunk; only emitted when `include_raw_chunks` is set.
201    Raw {
202        /// The provider chunk as JSON.
203        raw_value: JsonValue,
204    },
205    /// An error; the stream ends after this part.
206    Error {
207        /// The error.
208        error: StreamError,
209    },
210}
211
212impl StreamPart {
213    /// Creates a `StreamStart` without warnings.
214    #[must_use]
215    pub fn stream_start() -> Self {
216        Self::StreamStart {
217            warnings: Vec::new(),
218        }
219    }
220
221    /// Creates a `TextDelta` without metadata.
222    #[must_use]
223    pub fn text_delta(id: impl Into<PartId>, delta: impl Into<String>) -> Self {
224        Self::TextDelta {
225            id: id.into(),
226            delta: delta.into(),
227            provider_metadata: None,
228        }
229    }
230
231    /// Creates a `Finish` without metadata.
232    #[must_use]
233    pub fn finish(finish_reason: FinishReason, usage: Usage) -> Self {
234        Self::Finish {
235            finish_reason,
236            usage,
237            provider_metadata: None,
238        }
239    }
240
241    /// Creates an `Error` part from a provider error.
242    #[must_use]
243    pub fn error(error: &ProviderError) -> Self {
244        Self::Error {
245            error: StreamError::from_provider_error(error),
246        }
247    }
248
249    /// Returns the wire name of the variant (`text-delta`, `finish`, ...).
250    #[must_use]
251    pub fn kind_name(&self) -> &'static str {
252        match self {
253            Self::StreamStart { .. } => "stream-start",
254            Self::ResponseMetadata { .. } => "response-metadata",
255            Self::TextStart { .. } => "text-start",
256            Self::TextDelta { .. } => "text-delta",
257            Self::TextEnd { .. } => "text-end",
258            Self::ReasoningStart { .. } => "reasoning-start",
259            Self::ReasoningDelta { .. } => "reasoning-delta",
260            Self::ReasoningEnd { .. } => "reasoning-end",
261            Self::ToolInputStart { .. } => "tool-input-start",
262            Self::ToolInputDelta { .. } => "tool-input-delta",
263            Self::ToolInputEnd { .. } => "tool-input-end",
264            Self::ToolCall(_) => "tool-call",
265            Self::ToolResult(_) => "tool-result",
266            Self::ToolApprovalRequest { .. } => "tool-approval-request",
267            Self::File { .. } => "file",
268            Self::ReasoningFile { .. } => "reasoning-file",
269            Self::Source(_) => "source",
270            Self::Custom { .. } => "custom",
271            Self::Finish { .. } => "finish",
272            Self::Raw { .. } => "raw",
273            Self::Error { .. } => "error",
274        }
275    }
276}
277
278/// Serializable error carried inside a stream.
279#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
280pub struct StreamError {
281    /// Human-readable message.
282    pub message: String,
283    /// Provider error type, if reported.
284    #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
285    pub error_type: Option<String>,
286    /// Provider error code, if reported.
287    #[serde(default, skip_serializing_if = "Option::is_none")]
288    pub code: Option<StreamErrorCode>,
289    /// HTTP status code, if the error came from an HTTP response.
290    #[serde(default, skip_serializing_if = "Option::is_none")]
291    pub status_code: Option<u16>,
292    /// Whether retrying the call may succeed.
293    #[serde(default, skip_serializing_if = "Option::is_none")]
294    pub is_retryable: Option<bool>,
295    /// Raw provider error payload.
296    #[serde(default, skip_serializing_if = "Option::is_none")]
297    pub data: Option<JsonValue>,
298}
299
300impl StreamError {
301    /// Creates a stream error with only a message.
302    #[must_use]
303    pub fn new(message: impl Into<String>) -> Self {
304        Self {
305            message: message.into(),
306            error_type: None,
307            code: None,
308            status_code: None,
309            is_retryable: None,
310            data: None,
311        }
312    }
313
314    /// Projects a [`ProviderError`] into a stream error.
315    #[must_use]
316    pub fn from_provider_error(error: &ProviderError) -> Self {
317        let mut stream_error = Self::new(error.to_string());
318        stream_error.is_retryable = Some(error.is_retryable());
319        stream_error.status_code = error.status_code().map(|status| status.as_u16());
320        if let ProviderError::ApiCall(api) = error {
321            stream_error.data = api.data.clone();
322        }
323        stream_error
324    }
325}
326
327impl std::fmt::Display for StreamError {
328    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329        f.write_str(&self.message)
330    }
331}
332
333impl std::error::Error for StreamError {}
334
335/// Provider error code: a string or a number.
336#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
337#[serde(untagged)]
338pub enum StreamErrorCode {
339    /// Textual code.
340    Text(String),
341    /// Numeric code.
342    Number(i64),
343}
344
345impl std::fmt::Display for StreamErrorCode {
346    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
347        match self {
348            Self::Text(text) => f.write_str(text),
349            Self::Number(number) => write!(f, "{number}"),
350        }
351    }
352}