use serde::Serialize;
use serde_json::{Value, json};
use std::fmt::Display;
pub type ProviderMetadata = Value;
pub type MessageMetadata = Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FinishReason {
Stop,
Length,
ToolCalls,
ContentFilter,
Unknown,
}
impl Display for FinishReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FinishReason::Stop => write!(f, "stop"),
FinishReason::Length => write!(f, "length"),
FinishReason::ToolCalls => write!(f, "tool-calls"),
FinishReason::ContentFilter => write!(f, "content-filter"),
FinishReason::Unknown => write!(f, "unknown"),
}
}
}
impl Serialize for FinishReason {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[derive(Debug, Clone)]
pub enum AISdkEvent {
Start {
message_id: String,
message_metadata: Option<MessageMetadata>,
provider_metadata: Option<ProviderMetadata>,
},
TextStart {
id: String,
provider_metadata: Option<ProviderMetadata>,
},
TextDelta {
id: String,
delta: String,
provider_metadata: Option<ProviderMetadata>,
},
TextEnd {
id: String,
provider_metadata: Option<ProviderMetadata>,
},
ReasoningStart {
id: String,
provider_metadata: Option<ProviderMetadata>,
},
ReasoningDelta {
id: String,
delta: String,
provider_metadata: Option<ProviderMetadata>,
},
ReasoningEnd {
id: String,
provider_metadata: Option<ProviderMetadata>,
},
SourceUrl {
source_id: String,
url: String,
title: Option<String>,
provider_metadata: Option<ProviderMetadata>,
},
SourceDocument {
source_id: String,
media_type: String,
title: String,
filename: Option<String>,
provider_metadata: Option<ProviderMetadata>,
},
File {
url: String,
media_type: String,
provider_metadata: Option<ProviderMetadata>,
},
ToolInputStart {
tool_call_id: String,
tool_name: String,
provider_executed: Option<bool>,
provider_metadata: Option<ProviderMetadata>,
dynamic: Option<bool>,
},
ToolInputDelta {
tool_call_id: String,
delta: String,
},
ToolInputAvailable {
tool_call_id: String,
tool_name: String,
input: Value,
provider_executed: Option<bool>,
provider_metadata: Option<ProviderMetadata>,
dynamic: Option<bool>,
},
ToolInputError {
tool_call_id: String,
tool_name: String,
input: Value,
error_text: String,
provider_executed: Option<bool>,
provider_metadata: Option<ProviderMetadata>,
dynamic: Option<bool>,
},
ToolOutputAvailable {
tool_call_id: String,
output: Value,
provider_executed: Option<bool>,
dynamic: Option<bool>,
preliminary: Option<bool>,
},
ToolOutputError {
tool_call_id: String,
error_text: String,
provider_executed: Option<bool>,
dynamic: Option<bool>,
},
MessageMetadata {
message_metadata: MessageMetadata,
},
CustomData { name: String, data: Value },
StartStep,
FinishStep,
Finish {
finish_reason: Option<FinishReason>,
message_metadata: Option<MessageMetadata>,
},
Abort { reason: String },
Error { error_text: String },
Done,
}
impl AISdkEvent {
pub fn error(error: impl Into<String>) -> AISdkEvent {
AISdkEvent::Error {
error_text: error.into(),
}
}
pub fn custom_data<V: Serialize>(name: impl Into<String>, data: V) -> AISdkEvent {
AISdkEvent::CustomData {
name: name.into(),
data: serde_json::to_value(data).unwrap_or_default(),
}
}
}
impl Display for AISdkEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = match self {
AISdkEvent::Start {
message_id,
message_metadata,
provider_metadata,
} => {
let mut obj = json!({"type": "start", "messageId": message_id});
if let Some(metadata) = message_metadata {
obj["messageMetadata"] = metadata.clone();
}
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
obj
}
AISdkEvent::TextStart {
id,
provider_metadata,
} => {
let mut obj = json!({"type": "text-start", "id": id});
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
obj
}
AISdkEvent::TextDelta {
id,
delta,
provider_metadata,
} => {
let mut obj = json!({"type": "text-delta", "id": id, "delta": delta});
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
obj
}
AISdkEvent::TextEnd {
id,
provider_metadata,
} => {
let mut obj = json!({"type": "text-end", "id": id});
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
obj
}
AISdkEvent::ReasoningStart {
id,
provider_metadata,
} => {
let mut obj = json!({"type": "reasoning-start", "id": id});
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
obj
}
AISdkEvent::ReasoningDelta {
id,
delta,
provider_metadata,
} => {
let mut obj = json!({"type": "reasoning-delta", "id": id, "delta": delta});
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
obj
}
AISdkEvent::ReasoningEnd {
id,
provider_metadata,
} => {
let mut obj = json!({"type": "reasoning-end", "id": id});
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
obj
}
AISdkEvent::SourceUrl {
source_id,
url,
title,
provider_metadata,
} => {
let mut obj = json!({"type": "source-url", "sourceId": source_id, "url": url});
if let Some(t) = title {
obj["title"] = json!(t);
}
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
obj
}
AISdkEvent::SourceDocument {
source_id,
media_type,
title,
filename,
provider_metadata,
} => {
let mut obj = json!({
"type": "source-document",
"sourceId": source_id,
"mediaType": media_type,
"title": title
});
if let Some(fn_name) = filename {
obj["filename"] = json!(fn_name);
}
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
obj
}
AISdkEvent::File {
url,
media_type,
provider_metadata,
} => {
let mut obj = json!({"type": "file", "url": url, "mediaType": media_type});
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
obj
}
AISdkEvent::ToolInputStart {
tool_call_id,
tool_name,
provider_executed,
provider_metadata,
dynamic,
} => {
let mut obj = json!({
"type": "tool-input-start",
"toolCallId": tool_call_id,
"toolName": tool_name
});
if let Some(executed) = provider_executed {
obj["providerExecuted"] = json!(executed);
}
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
if let Some(d) = dynamic {
obj["dynamic"] = json!(d);
}
obj
}
AISdkEvent::ToolInputDelta {
tool_call_id,
delta,
} => {
json!({
"type": "tool-input-delta",
"toolCallId": tool_call_id,
"inputTextDelta": delta
})
}
AISdkEvent::ToolInputAvailable {
tool_call_id,
tool_name,
input,
provider_executed,
provider_metadata,
dynamic,
} => {
let mut obj = json!({
"type": "tool-input-available",
"toolCallId": tool_call_id,
"toolName": tool_name,
"input": input
});
if let Some(executed) = provider_executed {
obj["providerExecuted"] = json!(executed);
}
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
if let Some(d) = dynamic {
obj["dynamic"] = json!(d);
}
obj
}
AISdkEvent::ToolInputError {
tool_call_id,
tool_name,
input,
error_text,
provider_executed,
provider_metadata,
dynamic,
} => {
let mut obj = json!({
"type": "tool-input-error",
"toolCallId": tool_call_id,
"toolName": tool_name,
"input": input,
"errorText": error_text
});
if let Some(executed) = provider_executed {
obj["providerExecuted"] = json!(executed);
}
if let Some(metadata) = provider_metadata {
obj["providerMetadata"] = metadata.clone();
}
if let Some(d) = dynamic {
obj["dynamic"] = json!(d);
}
obj
}
AISdkEvent::ToolOutputAvailable {
tool_call_id,
output,
provider_executed,
dynamic,
preliminary,
} => {
let mut obj = json!({
"type": "tool-output-available",
"toolCallId": tool_call_id,
"output": output
});
if let Some(executed) = provider_executed {
obj["providerExecuted"] = json!(executed);
}
if let Some(d) = dynamic {
obj["dynamic"] = json!(d);
}
if let Some(p) = preliminary {
obj["preliminary"] = json!(p);
}
obj
}
AISdkEvent::ToolOutputError {
tool_call_id,
error_text,
provider_executed,
dynamic,
} => {
let mut obj = json!({
"type": "tool-output-error",
"toolCallId": tool_call_id,
"errorText": error_text
});
if let Some(executed) = provider_executed {
obj["providerExecuted"] = json!(executed);
}
if let Some(d) = dynamic {
obj["dynamic"] = json!(d);
}
obj
}
AISdkEvent::MessageMetadata { message_metadata } => {
json!({"type": "message-metadata", "messageMetadata": message_metadata})
}
AISdkEvent::CustomData { name, data } => {
json!({"type": format!("data-{}", name), "data": data})
}
AISdkEvent::StartStep => json!({"type": "start-step"}),
AISdkEvent::FinishStep => json!({"type": "finish-step"}),
AISdkEvent::Finish {
finish_reason,
message_metadata,
} => {
let mut obj = json!({"type": "finish"});
if let Some(reason) = finish_reason {
obj["finishReason"] = json!(reason);
}
if let Some(metadata) = message_metadata {
obj["messageMetadata"] = metadata.clone();
}
obj
}
AISdkEvent::Abort { reason } => json!({"type": "abort", "reason": reason}),
AISdkEvent::Error { error_text } => json!({"type": "error", "errorText": error_text}),
AISdkEvent::Done => return write!(f, "[DONE]"),
};
write!(f, "{}", value)
}
}
#[cfg(feature = "axum-sse")]
impl From<AISdkEvent> for axum::response::sse::Event {
fn from(value: AISdkEvent) -> Self {
axum::response::sse::Event::default().data(value.to_string())
}
}