use crate::AgentMessage;
use super::jobs::{MAX_MESSAGES_BYTES, MAX_OUTPUT_BYTES, SubagentJob};
pub fn append_output(job: &mut SubagentJob, chunk: &str) {
if chunk.is_empty() {
return;
}
let chunk = if chunk.len() > MAX_OUTPUT_BYTES {
job.truncated = true;
tail_within_bytes(chunk, MAX_OUTPUT_BYTES)
} else {
chunk
};
if job.output.len() + chunk.len() > MAX_OUTPUT_BYTES {
let keep = MAX_OUTPUT_BYTES.saturating_sub(chunk.len());
job.output = tail_within_bytes(&job.output, keep).to_string();
job.output.push_str(chunk);
job.truncated = true;
} else {
job.output.push_str(chunk);
}
}
fn tail_within_bytes(value: &str, max_bytes: usize) -> &str {
let mut start = value.len().saturating_sub(max_bytes);
while start < value.len() && !value.is_char_boundary(start) {
start += 1;
}
&value[start..]
}
pub fn append_message(job: &mut SubagentJob, message: &serde_json::Value) {
job.messages.push(message.clone());
let mut total = 0usize;
for m in &job.messages {
total = total.saturating_add(serde_json::to_string(m).map_or(0, |s| s.len()));
}
if total <= MAX_MESSAGES_BYTES {
return;
}
job.messages_truncated = true;
while job.messages.len() > 1 && total > MAX_MESSAGES_BYTES {
let first = serde_json::to_string(&job.messages[0]).map_or(0, |s| s.len());
total = total.saturating_sub(first);
job.messages.remove(0);
}
}
pub fn agent_message_to_json(m: &AgentMessage) -> serde_json::Value {
match m {
AgentMessage::Llm(msg) => serde_json::to_value(msg).unwrap_or(serde_json::Value::Null),
AgentMessage::Custom(c) => match &c.payload {
serde_json::Value::Object(map) => {
let mut obj = map.clone();
obj.insert(
"role".to_string(),
serde_json::Value::String(c.role.clone()),
);
obj.insert(
"timestamp".to_string(),
serde_json::Value::from(c.timestamp),
);
serde_json::Value::Object(obj)
}
other => {
serde_json::json!({ "role": c.role, "timestamp": c.timestamp, "payload": other })
}
},
}
}
#[derive(Clone, Debug)]
pub struct JobTranscript<'a> {
pub job_id: &'a str,
pub run_id: Option<&'a str>,
pub node_id: Option<&'a str>,
pub messages: &'a [serde_json::Value],
}
pub trait JobTranscriptStore: Send + Sync {
fn save<'a>(&self, transcript: &JobTranscript<'a>);
fn load_node(&self, run_id: &str, node_id: &str) -> Option<Vec<serde_json::Value>>;
fn load_job(&self, job_id: &str) -> Option<Vec<serde_json::Value>>;
}
#[cfg(test)]
tests_bridge_macro::tests_bridge!("multiagent/job_transcript");