use serde::Serialize;
use serde_json::{json, Value};
use std::fmt::Display;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub enum AISdkEvent {
Start { message_id: Uuid },
TextStart { id: Uuid },
TextDelta { id: Uuid, delta: String },
TextEnd { id: Uuid },
ReasoningStart { id: Uuid },
ReasoningDelta { id: Uuid, delta: String },
ReasoningEnd { id: Uuid },
SourceUrl { source_id: String, url: String },
SourceDocument {
source_id: String,
media_type: String,
title: String,
},
File { url: String, media_type: String },
ToolInputStart {
tool_call_id: String,
tool_name: String,
},
ToolInputDelta { tool_call_id: String, delta: String },
ToolInputAvailable {
tool_call_id: String,
tool_name: String,
input: Value,
},
ToolOutputAvailable { tool_call_id: String, output: Value },
CustomData { name: String, data: Value },
StartStep,
FinishStep,
Finish,
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 } => {
json!({"type": "start", "messageId": message_id.to_string()})
}
AISdkEvent::TextStart { id } => {
json!({"type": "text-start", "id": id.to_string()})
}
AISdkEvent::TextDelta { id, delta } => {
json!({"type": "text-delta", "id": id.to_string(), "delta": delta})
}
AISdkEvent::TextEnd { id } => {
json!({"type": "text-end", "id": id.to_string()})
}
AISdkEvent::ReasoningStart { id } => {
json!({"type": "reasoning-start", "id": id.to_string()})
}
AISdkEvent::ReasoningDelta { id, delta } => {
json!({"type": "reasoning-delta", "id": id.to_string(), "delta": delta})
}
AISdkEvent::ReasoningEnd { id } => {
json!({"type": "reasoning-end", "id": id.to_string()})
}
AISdkEvent::SourceUrl { source_id, url } => {
json!({"type": "source-url", "sourceId": source_id, "url": url})
}
AISdkEvent::SourceDocument {
source_id,
media_type,
title,
} => {
json!({"type": "source-document", "sourceId": source_id, "mediaType": media_type, "title": title})
}
AISdkEvent::File { url, media_type } => {
json!({"type": "file", "url": url, "mediaType": media_type})
}
AISdkEvent::ToolInputStart {
tool_call_id,
tool_name,
} => {
json!({"type": "tool-input-start", "toolCallId": tool_call_id, "toolName": tool_name})
}
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,
} => {
json!({"type": "tool-input-available", "toolCallId": tool_call_id, "toolName": tool_name, "input": input})
}
AISdkEvent::ToolOutputAvailable {
tool_call_id,
output,
} => {
json!({"type": "tool-output-available", "toolCallId": tool_call_id, "output": output})
}
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 => json!({"type": "finish"}),
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())
}
}