use serde::{Deserialize, Serialize};
use crate::error::ErrorCode;
use crate::types::{CharacterInfo, Message, StreamMetadata};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ServerHello {
pub v: u32,
pub server_name: String,
#[serde(default)]
pub characters: Vec<CharacterInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct History {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
pub messages: Vec<Message>,
#[serde(default, skip_serializing_if = "is_zero")]
pub active_start: usize,
#[serde(default)]
pub config: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub selected_character: Option<String>,
#[serde(default)]
pub revision: u64,
}
#[expect(
clippy::trivially_copy_pass_by_ref,
reason = "serde skip_serializing_if requires a &T predicate signature"
)]
fn is_zero(value: &usize) -> bool {
*value == 0
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Shutdown {}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Ping {}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommandOutput {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
pub name: String,
pub data: serde_json::Value,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Error {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
pub code: ErrorCode,
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct StreamStart {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
#[serde(default)]
pub regen: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct StreamChunk {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
pub text: String,
#[serde(default = "default_content_type")]
pub content_type: String,
}
fn default_content_type() -> String {
"text".to_string()
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct StreamEnd {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub msg_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub revision: Option<u64>,
pub content: String,
pub metadata: StreamMetadata,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub finish_reason: String,
#[serde(default = "default_true")]
pub is_final: bool,
}
fn default_true() -> bool {
true
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Phase {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
pub phase: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum MessageOrigin {
UserInput,
AssistantReply,
Autonomous,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NewMessage {
#[serde(default)]
pub revision: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub character: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub origin: Option<MessageOrigin>,
#[serde(flatten)]
pub message: Message,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ToolCall {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
pub tool_id: String,
pub tool_name: String,
pub input: serde_json::Value,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ToolResult {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
pub tool_id: String,
pub tool_name: String,
pub output: String,
#[serde(default)]
pub is_error: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SendImage {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
pub path: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CacheWarning {
pub expected_tokens: u32,
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProviderFallbackWarning {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
pub provider: String,
pub from_key: String,
pub to_key: String,
pub kind: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<u16>,
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UsageWarning {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rid: Option<String>,
pub budget: String,
pub message: String,
pub current_cost: f64,
pub cost_limit: f64,
pub percent_used: f64,
pub crossed_warn_at: Vec<f64>,
pub period: String,
pub period_start: String,
pub reset_at: String,
#[serde(default)]
pub reset_at_display: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ServerMessage {
Hello(ServerHello),
History(History),
Shutdown(Shutdown),
Ping(Ping),
CommandOutput(CommandOutput),
Error(Error),
StreamStart(StreamStart),
StreamChunk(StreamChunk),
StreamEnd(StreamEnd),
Phase(Phase),
NewMessage(NewMessage),
ToolCall(ToolCall),
ToolResult(ToolResult),
SendImage(SendImage),
CacheWarning(CacheWarning),
ProviderFallbackWarning(ProviderFallbackWarning),
UsageWarning(UsageWarning),
}
impl ServerMessage {
#[must_use]
pub fn with_rid(mut self, rid: Option<String>) -> Self {
match &mut self {
ServerMessage::History(msg) => msg.rid = rid,
ServerMessage::CommandOutput(msg) => msg.rid = rid,
ServerMessage::Error(msg) => msg.rid = rid,
ServerMessage::StreamStart(msg) => msg.rid = rid,
ServerMessage::StreamChunk(msg) => msg.rid = rid,
ServerMessage::StreamEnd(msg) => msg.rid = rid,
ServerMessage::Phase(msg) => msg.rid = rid,
ServerMessage::ToolCall(msg) => msg.rid = rid,
ServerMessage::ToolResult(msg) => msg.rid = rid,
ServerMessage::SendImage(msg) => msg.rid = rid,
ServerMessage::ProviderFallbackWarning(msg) => msg.rid = rid,
ServerMessage::UsageWarning(msg) => msg.rid = rid,
ServerMessage::Hello(_)
| ServerMessage::Shutdown(_)
| ServerMessage::Ping(_)
| ServerMessage::NewMessage(_)
| ServerMessage::CacheWarning(_) => {}
}
self
}
}