use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use crate::schemas::{FunctionCallBehavior, FunctionCallResponse, FunctionDefinition, Message};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub(crate) struct ClaudeMessage {
pub role: String,
pub content: Value,
}
impl ClaudeMessage {
pub fn new<S: Into<String>>(role: S, content: Value) -> Self {
Self {
role: role.into(),
content,
}
}
pub fn text<S: Into<String>>(role: S, text: &str) -> Self {
Self::new(role, Value::String(text.to_string()))
}
pub fn assistant(message: &Message) -> Self {
let calls = message
.tool_calls
.clone()
.and_then(|v| serde_json::from_value::<Vec<FunctionCallResponse>>(v).ok())
.unwrap_or_default();
if calls.is_empty() {
return Self::text("assistant", &message.content);
}
let mut blocks = Vec::new();
if !message.content.trim().is_empty() {
blocks.push(json!({"type": "text", "text": message.content}));
}
for call in calls {
blocks.push(json!({
"type": "tool_use",
"id": call.id,
"name": call.function.name,
"input": arguments_to_input(&call.function.arguments),
}));
}
Self::new("assistant", Value::Array(blocks))
}
pub fn tool_result(message: &Message) -> Self {
Self::new(
"user",
json!([{
"type": "tool_result",
"tool_use_id": message.id.clone().unwrap_or_default(),
"content": message.content,
}]),
)
}
fn is_tool_result(&self) -> bool {
self.role == "user"
&& self.content.as_array().is_some_and(|blocks| {
!blocks.is_empty()
&& blocks
.iter()
.all(|b| b.get("type").and_then(Value::as_str) == Some("tool_result"))
})
}
}
pub(crate) fn to_claude_messages(messages: &[&Message]) -> Vec<ClaudeMessage> {
use crate::schemas::MessageType;
let mut out: Vec<ClaudeMessage> = Vec::new();
for message in messages {
let next = match message.message_type {
MessageType::AIMessage => ClaudeMessage::assistant(message),
MessageType::HumanMessage | MessageType::SystemMessage => {
ClaudeMessage::text("user", &message.content)
}
MessageType::ToolMessage => ClaudeMessage::tool_result(message),
};
match out.last_mut() {
Some(prev) if prev.is_tool_result() && next.is_tool_result() => {
if let (Some(prev_blocks), Value::Array(blocks)) =
(prev.content.as_array_mut(), next.content)
{
prev_blocks.extend(blocks);
}
}
_ => out.push(next),
}
}
out
}
fn arguments_to_input(arguments: &str) -> Value {
if arguments.trim().is_empty() {
return json!({});
}
match serde_json::from_str::<Value>(arguments) {
Ok(v @ Value::Object(_)) => v,
Ok(other) => json!({ "input": other }),
Err(_) => json!({ "input": arguments }),
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub(crate) struct ClaudeTool {
pub name: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub description: String,
pub input_schema: Value,
}
impl From<&FunctionDefinition> for ClaudeTool {
fn from(f: &FunctionDefinition) -> Self {
Self {
name: f.name.clone(),
description: f.description.clone(),
input_schema: f.parameters.clone(),
}
}
}
pub(crate) fn tool_choice(behavior: &FunctionCallBehavior) -> Value {
match behavior {
FunctionCallBehavior::Auto => json!({"type": "auto"}),
FunctionCallBehavior::None => json!({"type": "none"}),
FunctionCallBehavior::Named(name) => json!({"type": "tool", "name": name}),
}
}
#[derive(Serialize, Deserialize)]
pub(crate) struct Payload {
pub model: String,
pub messages: Vec<ClaudeMessage>,
pub max_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_sequences: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_k: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ClaudeTool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub(crate) struct ApiResponse {
pub content: Vec<Content>,
pub id: String,
pub model: String,
pub role: String,
pub stop_reason: Option<String>,
pub stop_sequence: Option<serde_json::Value>,
#[serde(rename = "type")]
pub message_type: String,
pub usage: Usage,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub(crate) struct Content {
#[serde(rename = "type")]
pub content_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub(crate) struct Usage {
#[serde(default)]
pub input_tokens: u32,
#[serde(default)]
pub output_tokens: u32,
}
pub(crate) fn generation_from_blocks(blocks: &[Content]) -> String {
let calls: Vec<Value> = blocks
.iter()
.filter(|b| b.content_type == "tool_use")
.map(|b| {
json!({
"id": b.id.clone().unwrap_or_default(),
"type": "function",
"function": {
"name": b.name.clone().unwrap_or_default(),
"arguments": b.input.clone().unwrap_or_else(|| json!({})).to_string(),
},
})
})
.collect();
if !calls.is_empty() {
return Value::Array(calls).to_string();
}
blocks
.iter()
.filter(|b| b.content_type == "text")
.filter_map(|b| b.text.as_deref())
.collect()
}