use crate::{Result, invalid};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ContentPart {
#[serde(rename = "type")]
pub kind: String,
pub text: String,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ToolCall {
pub name: String,
pub arguments: String,
pub id: String,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct ChatMessage {
pub role: String,
pub content: String,
pub content_parts: Vec<ContentPart>,
pub tool_calls: Vec<ToolCall>,
pub reasoning_content: String,
pub tool_name: String,
pub tool_call_id: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MessageDiff {
pub reasoning_content_delta: String,
pub content_delta: String,
pub tool_call_index: Option<usize>,
pub tool_call_delta: ToolCall,
}
fn string_diff(previous: &str, current: &str) -> Result<String> {
current.strip_prefix(previous).map_or_else(
|| {
if previous.starts_with(current) {
Ok(String::new())
} else {
Err(invalid("chat delta is not a prefix extension"))
}
},
|suffix| Ok(suffix.to_owned()),
)
}
impl ChatMessage {
pub fn diff(&self, current: &Self) -> Result<Vec<MessageDiff>> {
let mut diffs = Vec::new();
if self.reasoning_content != current.reasoning_content {
diffs.push(MessageDiff {
reasoning_content_delta: string_diff(
&self.reasoning_content,
¤t.reasoning_content,
)?,
..MessageDiff::default()
});
}
if self.content != current.content {
diffs.push(MessageDiff {
content_delta: string_diff(&self.content, ¤t.content)?,
..MessageDiff::default()
});
}
if current.tool_calls.len() < self.tool_calls.len() {
return Err(invalid("chat delta removes tool calls"));
}
if let Some(index) = self.tool_calls.len().checked_sub(1) {
let before = &self.tool_calls[index];
let after = ¤t.tool_calls[index];
if before.name != after.name
&& !before.name.is_empty()
&& !after.name.is_empty()
&& !after.name.starts_with(&before.name)
{
return Err(invalid("chat delta tool name mismatch"));
}
let arguments = string_diff(&before.arguments, &after.arguments)?;
if !arguments.is_empty() || before.id != after.id || before.name != after.name {
diffs.push(MessageDiff {
tool_call_index: Some(index),
tool_call_delta: ToolCall {
name: if before.id != after.id || before.name != after.name {
after.name.clone()
} else {
String::new()
},
id: if before.id != after.id || before.name != after.name {
after.id.clone()
} else {
String::new()
},
arguments,
},
..MessageDiff::default()
});
}
}
for (index, call) in current
.tool_calls
.iter()
.enumerate()
.skip(self.tool_calls.len())
{
diffs.push(MessageDiff {
tool_call_index: Some(index),
tool_call_delta: call.clone(),
..MessageDiff::default()
});
}
Ok(diffs)
}
}