use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::ag_ui::Metadata;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "role", rename_all = "lowercase")]
pub enum Message {
Developer(TextOnlyMessage),
System(TextOnlyMessage),
Assistant(AssistantMessage),
User(UserMessage),
Tool(ToolMessage),
Activity(ActivityMessage),
Reasoning(ReasoningMessage),
}
impl Message {
pub fn id(&self) -> &str {
match self {
Self::Developer(m) | Self::System(m) => &m.id,
Self::Assistant(m) => &m.id,
Self::User(m) => &m.id,
Self::Tool(m) => &m.id,
Self::Activity(m) => &m.id,
Self::Reasoning(m) => &m.id,
}
}
pub fn user(id: impl Into<String>, text: impl Into<String>) -> Self {
Self::User(UserMessage {
id: id.into(),
content: Content::Text(text.into()),
name: None,
encrypted_value: None,
metadata: None,
subagent_run_id: None,
})
}
pub fn assistant(id: impl Into<String>, text: impl Into<String>) -> Self {
Self::Assistant(AssistantMessage {
id: id.into(),
content: Some(text.into()),
..AssistantMessage::default()
})
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TextOnlyMessage {
pub id: String,
pub content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encrypted_value: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subagent_run_id: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AssistantMessage {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCall>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encrypted_value: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subagent_run_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserMessage {
pub id: String,
pub content: Content,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encrypted_value: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subagent_run_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolMessage {
pub id: String,
pub content: Content,
pub tool_call_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encrypted_value: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subagent_run_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ActivityMessage {
pub id: String,
pub activity_type: String,
pub content: serde_json::Map<String, Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subagent_run_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReasoningMessage {
pub id: String,
pub content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encrypted_value: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subagent_run_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Content {
Text(String),
Parts(Vec<ContentPart>),
}
impl Content {
pub fn to_text(&self) -> String {
match self {
Self::Text(text) => text.clone(),
Self::Parts(parts) => parts
.iter()
.filter_map(|part| match part {
ContentPart::Text(part) => Some(part.text.as_str()),
_ => None,
})
.collect(),
}
}
pub fn has_media(&self) -> bool {
matches!(self, Self::Parts(parts) if parts.iter().any(|p| !matches!(p, ContentPart::Text(_))))
}
}
impl From<String> for Content {
fn from(text: String) -> Self {
Self::Text(text)
}
}
impl From<&str> for Content {
fn from(text: &str) -> Self {
Self::Text(text.to_owned())
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum ContentPart {
Text(TextPart),
Image(MediaPart),
Audio(MediaPart),
Video(MediaPart),
Document(MediaPart),
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct TextPart {
pub text: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MediaPart {
pub source: PartSource,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum PartSource {
Data {
value: String,
#[serde(rename = "mimeType")]
mime_type: String,
},
Url {
value: String,
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
},
File {
value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider: Option<String>,
#[serde(rename = "mimeType", default, skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolCall {
pub id: String,
#[serde(rename = "type")]
pub kind: ToolCallType,
pub function: FunctionCall,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub encrypted_value: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
}
impl ToolCall {
pub fn function(
id: impl Into<String>,
name: impl Into<String>,
arguments: impl Into<String>,
) -> Self {
Self {
id: id.into(),
kind: ToolCallType::Function,
function: FunctionCall {
name: name.into(),
arguments: arguments.into(),
},
encrypted_value: None,
metadata: None,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ToolCallType {
#[default]
Function,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FunctionCall {
pub name: String,
pub arguments: String,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TextMessageRole {
Developer,
System,
#[default]
Assistant,
User,
}