use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
use crate::tool::ToolInfo;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IdentityType {
Human,
Agent,
Subagent,
System,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageIdentity {
pub sub: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "type")]
pub kind: IdentityType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MessageRole {
User,
Assistant,
Thinking,
ToolCall,
ToolResult,
System,
Info,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ContentPart {
Text {
text: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
markdown: Option<bool>,
},
Code {
code: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
language: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "filePath")]
file_path: Option<String>,
},
FileRef {
path: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
lines: Option<[u32; 2]>,
#[serde(default, skip_serializing_if = "Option::is_none")]
change: Option<FileChange>,
},
Diff {
path: String,
added: u32,
removed: u32,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "originalPath"
)]
original_path: Option<String>,
},
Tree {
label: String,
children: Vec<TreeNode>,
},
Button {
label: String,
action: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
data: Option<BTreeMap<String, Value>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
style: Option<ButtonStyle>,
},
Progress {
message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
percent: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "elapsedMs")]
elapsed_ms: Option<u64>,
},
Image {
url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
alt: Option<String>,
},
Anchor {
uri: String,
title: String,
},
Table {
headers: Vec<String>,
rows: Vec<Vec<String>>,
},
#[serde(other, deserialize_with = "deserialize_unknown")]
Unknown,
}
fn deserialize_unknown<'de, D>(deserializer: D) -> Result<(), D::Error>
where
D: serde::Deserializer<'de>,
{
serde::de::IgnoredAny::deserialize(deserializer).map(|_| ())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileChange {
pub added: u32,
pub removed: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TreeNode {
pub label: String,
#[serde(rename = "type")]
pub kind: TreeNodeType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub children: Option<Vec<TreeNode>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TreeNodeType {
File,
Directory,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ButtonStyle {
Primary,
Secondary,
Danger,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionMessage {
pub session_id: String,
pub message_id: String,
pub role: MessageRole,
pub content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parts: Option<Vec<ContentPart>>,
pub identity: MessageIdentity,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool: Option<ToolInfo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<BTreeMap<String, Value>>,
pub timestamp: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionMessageDelta {
pub session_id: String,
pub message_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_append: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parts_append: Option<Vec<ContentPart>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parts_update: Option<Vec<PartsUpdate>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_state_update: Option<crate::tool::ToolState>,
pub timestamp: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PartsUpdate {
pub index: u32,
pub part: ContentPart,
}