use serde::{Deserialize, Serialize};
use super::common::{StopReason, ToolDefinition, Usage};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ContentBlock {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "thinking")]
Thinking {
thinking: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
signature: Option<String>,
},
#[serde(rename = "tool_use")]
ToolUse {
id: String,
name: String,
input: serde_json::Value,
},
#[serde(rename = "tool_result")]
ToolResult {
tool_use_id: String,
content: String,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
is_error: bool,
},
}
impl ContentBlock {
pub fn text(text: impl Into<String>) -> Self {
Self::Text { text: text.into() }
}
pub fn tool_use(
id: impl Into<String>,
name: impl Into<String>,
input: serde_json::Value,
) -> Self {
Self::ToolUse {
id: id.into(),
name: name.into(),
input,
}
}
pub fn tool_result(
tool_use_id: impl Into<String>,
content: impl Into<String>,
is_error: bool,
) -> Self {
Self::ToolResult {
tool_use_id: tool_use_id.into(),
content: content.into(),
is_error,
}
}
pub fn thinking(thinking: impl Into<String>, signature: Option<String>) -> Self {
Self::Thinking {
thinking: thinking.into(),
signature,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Message {
pub role: String,
pub content: Vec<ContentBlock>,
}
impl Message {
pub fn user(content: Vec<ContentBlock>) -> Self {
Self {
role: "user".to_string(),
content,
}
}
pub fn assistant(content: Vec<ContentBlock>) -> Self {
Self {
role: "assistant".to_string(),
content,
}
}
pub fn user_text(text: impl Into<String>) -> Self {
Self::user(vec![ContentBlock::text(text)])
}
pub fn assistant_text(text: impl Into<String>) -> Self {
Self::assistant(vec![ContentBlock::text(text)])
}
pub fn tool_results(results: Vec<ContentBlock>) -> Self {
Self::user(results)
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum ThinkingParam {
Adaptive,
Enabled { budget_tokens: u32 },
}
#[derive(Debug, Clone, Serialize)]
pub struct OutputConfig {
pub effort: String,
}
#[derive(Debug, Serialize)]
pub struct MessagesRequest {
pub model: String,
pub max_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<String>,
pub messages: Vec<Message>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ToolDefinition>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thinking: Option<ThinkingParam>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_config: Option<OutputConfig>,
}
#[derive(Debug, Deserialize)]
pub struct MessagesResponse {
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub model: Option<String>,
pub content: Vec<ContentBlock>,
pub stop_reason: StopReason,
#[serde(default)]
pub usage: Option<Usage>,
}
impl MessagesResponse {
pub fn text(&self) -> String {
self.content
.iter()
.filter_map(|b| match b {
ContentBlock::Text { text } => Some(text.as_str()),
_ => None,
})
.collect::<Vec<_>>()
.join("")
}
pub fn thinking_text(&self) -> Option<String> {
let texts: Vec<&str> = self
.content
.iter()
.filter_map(|b| match b {
ContentBlock::Thinking { thinking, .. } => Some(thinking.as_str()),
_ => None,
})
.collect();
if texts.is_empty() {
None
} else {
Some(texts.join(""))
}
}
pub fn has_tool_use(&self) -> bool {
self.stop_reason.is_tool_use()
}
pub fn tool_uses(&self) -> Vec<&ContentBlock> {
self.content
.iter()
.filter(|b| matches!(b, ContentBlock::ToolUse { .. }))
.collect()
}
}