use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmMessage {
pub role: MessageRole,
pub content: MessageContent,
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum MessageRole {
System,
User,
Assistant,
Tool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MessageContent {
Text(String),
MultiModal(Vec<ContentBlock>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
Text {
text: String,
},
Image {
data: String,
mime_type: String,
},
ToolUse {
id: String,
name: String,
input: serde_json::Value,
},
ToolResult {
tool_use_id: String,
is_error: Option<bool>,
content: String,
},
}
impl LlmMessage {
pub fn system<S: Into<String>>(content: S) -> Self {
Self {
role: MessageRole::System,
content: MessageContent::Text(content.into()),
metadata: None,
}
}
pub fn user<S: Into<String>>(content: S) -> Self {
Self {
role: MessageRole::User,
content: MessageContent::Text(content.into()),
metadata: None,
}
}
pub fn assistant<S: Into<String>>(content: S) -> Self {
Self {
role: MessageRole::Assistant,
content: MessageContent::Text(content.into()),
metadata: None,
}
}
pub fn tool<S: Into<String>>(content: S) -> Self {
Self {
role: MessageRole::Tool,
content: MessageContent::Text(content.into()),
metadata: None,
}
}
pub fn get_text(&self) -> Option<String> {
match &self.content {
MessageContent::Text(text) => Some(text.clone()),
MessageContent::MultiModal(blocks) => {
let mut text_parts = Vec::new();
for block in blocks {
if let ContentBlock::Text { text } = block {
text_parts.push(text.clone());
}
}
if text_parts.is_empty() {
None
} else {
Some(text_parts.join("\n"))
}
}
}
}
pub fn has_tool_use(&self) -> bool {
match &self.content {
MessageContent::Text(_) => false,
MessageContent::MultiModal(blocks) => {
blocks.iter().any(|block| matches!(block, ContentBlock::ToolUse { .. }))
}
}
}
pub fn get_tool_uses(&self) -> Vec<&ContentBlock> {
match &self.content {
MessageContent::Text(_) => Vec::new(),
MessageContent::MultiModal(blocks) => {
blocks.iter()
.filter(|block| matches!(block, ContentBlock::ToolUse { .. }))
.collect()
}
}
}
}
impl From<String> for MessageContent {
fn from(text: String) -> Self {
MessageContent::Text(text)
}
}
impl From<&str> for MessageContent {
fn from(text: &str) -> Self {
MessageContent::Text(text.to_string())
}
}