use modkit_macros::domain_model;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
use crate::domain::llm::{Citation, ToolPhase, Usage};
#[domain_model]
#[derive(Debug, Clone, ToSchema)]
pub enum StreamEvent {
StreamStarted(StreamStartedData),
Ping,
Delta(DeltaData),
Tool(ToolData),
Citations(CitationsData),
Done(Box<DoneData>),
Error(ErrorData),
}
#[domain_model]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct DeltaData {
pub r#type: &'static str,
pub content: String,
}
#[domain_model]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct ToolData {
pub phase: ToolPhase,
pub name: String,
pub details: serde_json::Value,
}
#[domain_model]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct CitationsData {
pub items: Vec<Citation>,
}
#[domain_model]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct DoneData {
pub usage: Option<Usage>,
pub effective_model: String,
pub selected_model: String,
pub quota_decision: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub downgrade_from: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub downgrade_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quota_warnings: Option<Vec<QuotaWarning>>,
}
#[domain_model]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct ErrorData {
pub code: String,
pub message: String,
}
#[domain_model]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct ThreadSummaryInfo {
pub token_estimate: u32,
}
#[domain_model]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct StreamStartedData {
pub request_id: Uuid,
pub message_id: Uuid,
pub is_new_turn: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub thread_summary_applied: Option<ThreadSummaryInfo>,
}
#[domain_model]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum QuotaTier {
Premium,
Total,
}
#[domain_model]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum QuotaPeriod {
Daily,
Monthly,
}
#[domain_model]
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct QuotaWarning {
pub tier: QuotaTier,
pub period: QuotaPeriod,
pub remaining_percentage: u8,
pub warning: bool,
pub exhausted: bool,
#[serde(
skip_serializing_if = "Option::is_none",
with = "time::serde::rfc3339::option"
)]
pub next_reset: Option<time::OffsetDateTime>,
}
#[domain_model]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamEventKind {
StreamStarted,
Ping,
Delta,
Tool,
Citations,
Terminal,
}
impl StreamEvent {
#[must_use]
pub fn event_kind(&self) -> StreamEventKind {
match self {
StreamEvent::StreamStarted(_) => StreamEventKind::StreamStarted,
StreamEvent::Ping => StreamEventKind::Ping,
StreamEvent::Delta(_) => StreamEventKind::Delta,
StreamEvent::Tool(_) => StreamEventKind::Tool,
StreamEvent::Citations(_) => StreamEventKind::Citations,
StreamEvent::Done(_) | StreamEvent::Error(_) => StreamEventKind::Terminal,
}
}
#[must_use]
pub fn is_terminal(&self) -> bool {
matches!(self, StreamEvent::Done(_) | StreamEvent::Error(_))
}
}