use chrono::DateTime;
use chrono::Utc;
use serde::Deserialize;
use serde::Serialize;
use super::content::CustomKind;
use super::content::ProviderToolResult;
use super::content::Source;
use super::content::ToolCall;
use super::finish_reason::FinishReason;
use super::usage::Usage;
use crate::error::ProviderError;
use crate::json::JsonValue;
use crate::shared::ApprovalId;
use crate::shared::FileData;
use crate::shared::MediaType;
use crate::shared::ModelId;
use crate::shared::PartId;
use crate::shared::ProviderMetadata;
use crate::shared::ToolCallId;
use crate::shared::ToolName;
use crate::shared::Warning;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
#[non_exhaustive]
pub enum StreamPart {
StreamStart {
#[serde(default)]
warnings: Vec<Warning>,
},
ResponseMetadata {
#[serde(default, skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
timestamp: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
model_id: Option<ModelId>,
},
TextStart {
id: PartId,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
TextDelta {
id: PartId,
delta: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
TextEnd {
id: PartId,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
ReasoningStart {
id: PartId,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
ReasoningDelta {
id: PartId,
delta: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
ReasoningEnd {
id: PartId,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
ToolInputStart {
id: ToolCallId,
tool_name: ToolName,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
provider_executed: bool,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
dynamic: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
ToolInputDelta {
id: ToolCallId,
delta: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
ToolInputEnd {
id: ToolCallId,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
ToolCall(ToolCall),
ToolResult(ProviderToolResult),
ToolApprovalRequest {
approval_id: ApprovalId,
tool_call_id: ToolCallId,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
File {
data: FileData,
media_type: MediaType,
#[serde(default, skip_serializing_if = "Option::is_none")]
filename: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
ReasoningFile {
data: FileData,
media_type: MediaType,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
Source(Source),
Custom {
kind: CustomKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
Finish {
finish_reason: FinishReason,
usage: Usage,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_metadata: Option<ProviderMetadata>,
},
Raw {
raw_value: JsonValue,
},
Error {
error: StreamError,
},
}
impl StreamPart {
#[must_use]
pub fn stream_start() -> Self {
Self::StreamStart {
warnings: Vec::new(),
}
}
#[must_use]
pub fn text_delta(id: impl Into<PartId>, delta: impl Into<String>) -> Self {
Self::TextDelta {
id: id.into(),
delta: delta.into(),
provider_metadata: None,
}
}
#[must_use]
pub fn finish(finish_reason: FinishReason, usage: Usage) -> Self {
Self::Finish {
finish_reason,
usage,
provider_metadata: None,
}
}
#[must_use]
pub fn error(error: &ProviderError) -> Self {
Self::Error {
error: StreamError::from_provider_error(error),
}
}
#[must_use]
pub fn kind_name(&self) -> &'static str {
match self {
Self::StreamStart { .. } => "stream-start",
Self::ResponseMetadata { .. } => "response-metadata",
Self::TextStart { .. } => "text-start",
Self::TextDelta { .. } => "text-delta",
Self::TextEnd { .. } => "text-end",
Self::ReasoningStart { .. } => "reasoning-start",
Self::ReasoningDelta { .. } => "reasoning-delta",
Self::ReasoningEnd { .. } => "reasoning-end",
Self::ToolInputStart { .. } => "tool-input-start",
Self::ToolInputDelta { .. } => "tool-input-delta",
Self::ToolInputEnd { .. } => "tool-input-end",
Self::ToolCall(_) => "tool-call",
Self::ToolResult(_) => "tool-result",
Self::ToolApprovalRequest { .. } => "tool-approval-request",
Self::File { .. } => "file",
Self::ReasoningFile { .. } => "reasoning-file",
Self::Source(_) => "source",
Self::Custom { .. } => "custom",
Self::Finish { .. } => "finish",
Self::Raw { .. } => "raw",
Self::Error { .. } => "error",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StreamError {
pub message: String,
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub error_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub code: Option<StreamErrorCode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status_code: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_retryable: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<JsonValue>,
}
impl StreamError {
#[must_use]
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
error_type: None,
code: None,
status_code: None,
is_retryable: None,
data: None,
}
}
#[must_use]
pub fn from_provider_error(error: &ProviderError) -> Self {
let mut stream_error = Self::new(error.to_string());
stream_error.is_retryable = Some(error.is_retryable());
stream_error.status_code = error.status_code().map(|status| status.as_u16());
if let ProviderError::ApiCall(api) = error {
stream_error.data = api.data.clone();
}
stream_error
}
}
impl std::fmt::Display for StreamError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for StreamError {}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum StreamErrorCode {
Text(String),
Number(i64),
}
impl std::fmt::Display for StreamErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Text(text) => f.write_str(text),
Self::Number(number) => write!(f, "{number}"),
}
}
}