use serde::{Deserialize, Serialize};
use super::{
AgentMessage, CustomMessageRegistry, LlmMessage, deserialize_custom_message,
serialize_custom_message,
};
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind")]
pub enum MessageSlot {
Llm { index: usize },
Custom { index: usize },
}
#[non_exhaustive]
#[derive(Debug, Clone, Default)]
pub struct SerializedMessages {
pub llm_messages: Vec<LlmMessage>,
pub custom_messages: Vec<serde_json::Value>,
pub message_order: Vec<MessageSlot>,
}
impl SerializedMessages {
#[must_use]
pub const fn new(
llm_messages: Vec<LlmMessage>,
custom_messages: Vec<serde_json::Value>,
message_order: Vec<MessageSlot>,
) -> Self {
Self {
llm_messages,
custom_messages,
message_order,
}
}
}
pub fn serialize_messages(messages: &[AgentMessage], kind: &str) -> SerializedMessages {
let mut llm_messages = Vec::new();
let mut custom_messages = Vec::new();
let mut message_order = Vec::new();
for message in messages {
match message {
AgentMessage::Llm(llm) => {
message_order.push(MessageSlot::Llm {
index: llm_messages.len(),
});
llm_messages.push(llm.clone());
}
AgentMessage::Custom(custom) => {
if let Some(envelope) = serialize_custom_message(custom.as_ref()) {
message_order.push(MessageSlot::Custom {
index: custom_messages.len(),
});
custom_messages.push(envelope);
} else {
tracing::warn!(
kind,
type_name = custom.type_name().unwrap_or("<unknown>"),
"skipping non-serializable CustomMessage"
);
}
}
}
}
SerializedMessages {
llm_messages,
custom_messages,
message_order,
}
}
pub fn restore_messages(
llm_messages: &[LlmMessage],
custom_messages: &[serde_json::Value],
message_order: &[MessageSlot],
registry: Option<&CustomMessageRegistry>,
kind: &str,
) -> Vec<AgentMessage> {
if !message_order.is_empty() {
let mut result = Vec::with_capacity(message_order.len());
for slot in message_order {
match slot {
MessageSlot::Llm { index } => {
if let Some(llm) = llm_messages.get(*index) {
result.push(AgentMessage::Llm(llm.clone()));
}
}
MessageSlot::Custom { index } => {
if let Some(reg) = registry
&& let Some(envelope) = custom_messages.get(*index)
{
match deserialize_custom_message(reg, envelope) {
Ok(custom) => result.push(AgentMessage::Custom(custom)),
Err(error) => {
tracing::warn!(
"failed to deserialize custom message from {kind}: {error}"
);
}
}
}
}
}
}
return result;
}
let mut result: Vec<AgentMessage> = llm_messages
.iter()
.cloned()
.map(AgentMessage::Llm)
.collect();
if let Some(reg) = registry {
for envelope in custom_messages {
match deserialize_custom_message(reg, envelope) {
Ok(custom) => result.push(AgentMessage::Custom(custom)),
Err(error) => {
tracing::warn!("failed to deserialize custom message from {kind}: {error}");
}
}
}
}
result
}
pub fn restore_single_custom(
registry: Option<&CustomMessageRegistry>,
envelope: &serde_json::Value,
) -> Result<Option<Box<dyn super::CustomMessage>>, String> {
registry.map_or_else(
|| Ok(None),
|reg| deserialize_custom_message(reg, envelope).map(Some),
)
}
#[derive(Debug, Clone)]
pub struct SerializedCustomMessage {
name: String,
json: serde_json::Value,
}
impl SerializedCustomMessage {
#[must_use]
pub fn new(name: impl Into<String>, json: serde_json::Value) -> Self {
Self {
name: name.into(),
json,
}
}
#[must_use]
pub fn from_custom(msg: &dyn super::CustomMessage) -> Option<Self> {
Some(Self {
name: msg.type_name()?.to_string(),
json: msg.to_json()?,
})
}
}
impl super::CustomMessage for SerializedCustomMessage {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn type_name(&self) -> Option<&str> {
Some(&self.name)
}
fn to_json(&self) -> Option<serde_json::Value> {
Some(self.json.clone())
}
fn clone_box(&self) -> Option<Box<dyn super::CustomMessage>> {
Some(Box::new(self.clone()))
}
}
pub fn clone_messages_for_send(messages: &[AgentMessage]) -> Vec<AgentMessage> {
messages
.iter()
.filter_map(|m| match m {
AgentMessage::Llm(llm) => Some(AgentMessage::Llm(llm.clone())),
AgentMessage::Custom(custom) => {
let snapshot = SerializedCustomMessage::from_custom(custom.as_ref())?;
Some(AgentMessage::Custom(Box::new(snapshot)))
}
})
.collect()
}
#[cfg(test)]
#[path = "message_codec_tests.rs"]
mod tests;