ferrin_spec/language_model/
stream_part.rs1use 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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33#[serde(tag = "type", rename_all = "kebab-case")]
34#[non_exhaustive]
35pub enum StreamPart {
36 StreamStart {
38 #[serde(default)]
40 warnings: Vec<Warning>,
41 },
42 ResponseMetadata {
44 #[serde(default, skip_serializing_if = "Option::is_none")]
46 id: Option<String>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
49 timestamp: Option<DateTime<Utc>>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
52 model_id: Option<ModelId>,
53 },
54 TextStart {
56 id: PartId,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
60 provider_metadata: Option<ProviderMetadata>,
61 },
62 TextDelta {
64 id: PartId,
66 delta: String,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
70 provider_metadata: Option<ProviderMetadata>,
71 },
72 TextEnd {
74 id: PartId,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
78 provider_metadata: Option<ProviderMetadata>,
79 },
80 ReasoningStart {
82 id: PartId,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
86 provider_metadata: Option<ProviderMetadata>,
87 },
88 ReasoningDelta {
90 id: PartId,
92 delta: String,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
96 provider_metadata: Option<ProviderMetadata>,
97 },
98 ReasoningEnd {
100 id: PartId,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
104 provider_metadata: Option<ProviderMetadata>,
105 },
106 ToolInputStart {
108 id: ToolCallId,
110 tool_name: ToolName,
112 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
114 provider_executed: bool,
115 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
117 dynamic: bool,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
120 title: Option<String>,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
123 provider_metadata: Option<ProviderMetadata>,
124 },
125 ToolInputDelta {
127 id: ToolCallId,
129 delta: String,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
133 provider_metadata: Option<ProviderMetadata>,
134 },
135 ToolInputEnd {
137 id: ToolCallId,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
141 provider_metadata: Option<ProviderMetadata>,
142 },
143 ToolCall(ToolCall),
145 ToolResult(ProviderToolResult),
147 ToolApprovalRequest {
149 approval_id: ApprovalId,
151 tool_call_id: ToolCallId,
153 #[serde(default, skip_serializing_if = "Option::is_none")]
155 provider_metadata: Option<ProviderMetadata>,
156 },
157 File {
159 data: FileData,
161 media_type: MediaType,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
165 filename: Option<String>,
166 #[serde(default, skip_serializing_if = "Option::is_none")]
168 provider_metadata: Option<ProviderMetadata>,
169 },
170 ReasoningFile {
172 data: FileData,
174 media_type: MediaType,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
178 provider_metadata: Option<ProviderMetadata>,
179 },
180 Source(Source),
182 Custom {
184 kind: CustomKind,
186 #[serde(default, skip_serializing_if = "Option::is_none")]
188 provider_metadata: Option<ProviderMetadata>,
189 },
190 Finish {
192 finish_reason: FinishReason,
194 usage: Usage,
196 #[serde(default, skip_serializing_if = "Option::is_none")]
198 provider_metadata: Option<ProviderMetadata>,
199 },
200 Raw {
202 raw_value: JsonValue,
204 },
205 Error {
207 error: StreamError,
209 },
210}
211
212impl StreamPart {
213 #[must_use]
215 pub fn stream_start() -> Self {
216 Self::StreamStart {
217 warnings: Vec::new(),
218 }
219 }
220
221 #[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 #[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 #[must_use]
243 pub fn error(error: &ProviderError) -> Self {
244 Self::Error {
245 error: StreamError::from_provider_error(error),
246 }
247 }
248
249 #[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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
280pub struct StreamError {
281 pub message: String,
283 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
285 pub error_type: Option<String>,
286 #[serde(default, skip_serializing_if = "Option::is_none")]
288 pub code: Option<StreamErrorCode>,
289 #[serde(default, skip_serializing_if = "Option::is_none")]
291 pub status_code: Option<u16>,
292 #[serde(default, skip_serializing_if = "Option::is_none")]
294 pub is_retryable: Option<bool>,
295 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub data: Option<JsonValue>,
298}
299
300impl StreamError {
301 #[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 #[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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
337#[serde(untagged)]
338pub enum StreamErrorCode {
339 Text(String),
341 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}