//! Request/response shapes for the Anthropic Messages API. //! //! Hand-rolled rather than pulled from an SDK: pydantic-ai has no Rust //! equivalent, and the surface area we actually need (text, tool_use, //! tool_result, forced tool choice) is small enough to own directly. use serde::{Deserialize, Serialize}; use serde_json::Value; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { pub role: String, pub content: Vec, } impl Message { pub fn user_text(text: impl Into) -> Self { Self { role: "user".into(), content: vec![ContentBlock::Text { text: text.into() }], } } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ContentBlock { Text { text: String, }, ToolUse { id: String, name: String, input: Value, }, ToolResult { tool_use_id: String, content: String, }, /// Anything else the API sends back (e.g. a `thinking` block) that this /// client doesn't act on but must not fail to deserialize. #[serde(other)] Unknown, } #[derive(Debug, Clone, Serialize)] pub struct ToolSpec { pub name: String, pub description: String, pub input_schema: Value, } #[derive(Debug, Clone, Serialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ToolChoice { Auto, Tool { name: String }, } #[derive(Debug, Serialize)] pub struct RequestBody<'a> { pub model: &'a str, pub max_tokens: u32, pub system: &'a str, pub messages: &'a [Message], pub tools: &'a [ToolSpec], pub tool_choice: ToolChoice, } #[derive(Debug, Deserialize)] pub struct ResponseBody { #[serde(default)] pub content: Vec, #[serde(default)] pub stop_reason: Option, }