use zeph_llm::provider::MessagePart;
use super::tier_loop::skipped_output;
use crate::agent::Agent;
use crate::channel::Channel;
impl<C: Channel> Agent<C> {
#[tracing::instrument(
name = "core.tool.preprocess_focus_compress",
skip_all,
level = "debug"
)]
pub(super) async fn preprocess_focus_compress_calls(
&mut self,
tool_calls: &[zeph_llm::provider::ToolUseRequest],
tool_results: &mut [Result<Option<zeph_tools::ToolOutput>, zeph_tools::ToolError>],
) -> Option<zeph_llm::provider::Message> {
let mut pending_focus_checkpoint: Option<zeph_llm::provider::Message> = None;
for (idx, tc) in tool_calls.iter().enumerate() {
let is_focus_tool = self.services.focus.config.enabled
&& (tc.name == "start_focus" || tc.name == "complete_focus");
let is_compress = tc.name == "compress_context";
let is_request_compaction = tc.name == "request_compaction"
&& self
.services
.memory
.subsystems
.arc_config
.allow_agent_compaction;
if is_focus_tool || is_compress || is_request_compaction {
let result = if is_compress {
self.handle_compress_context().await
} else if is_request_compaction {
self.handle_request_compaction(&tc.input).await
} else {
let (text, maybe_checkpoint) =
self.handle_focus_tool(tc.name.as_str(), &tc.input);
if let Some(cp) = maybe_checkpoint {
pending_focus_checkpoint = Some(cp);
}
text
};
tool_results[idx] = Ok(Some(skipped_output(tc.name.clone(), result)));
}
}
pending_focus_checkpoint
}
#[tracing::instrument(name = "context.tool_result_compress", skip_all, level = "debug")]
pub(super) fn apply_acon_compression(
&mut self,
tool_calls: &[zeph_llm::provider::ToolUseRequest],
result_parts: &mut [MessagePart],
) {
use zeph_context::tool_result_compress::{
CompressionMethod, ToolResultCompressionConfig, ToolResultCompressor, ToolResultEntry,
};
let acon = &self.services.memory.subsystems.acon_config;
if !acon.enabled {
return;
}
let cfg = ToolResultCompressionConfig::from(acon);
let tc = std::sync::Arc::clone(&self.runtime.metrics.token_counter);
let id_to_name: std::collections::HashMap<&str, &str> = tool_calls
.iter()
.map(|tc| (tc.id.as_str(), tc.name.as_str()))
.collect();
let indexed_texts: Vec<(usize, String, String)> = result_parts
.iter()
.enumerate()
.filter_map(|(i, part)| {
if let MessagePart::ToolResult {
content,
tool_use_id,
..
} = part
{
let name = id_to_name
.get(tool_use_id.as_str())
.copied()
.unwrap_or("")
.to_owned();
Some((i, name, content.clone()))
} else {
None
}
})
.collect();
if indexed_texts.is_empty() {
return;
}
let entries: Vec<ToolResultEntry<'_>> = indexed_texts
.iter()
.map(|(part_idx, name, text)| ToolResultEntry {
tool_name: name.as_str(),
text: text.as_str(),
index: *part_idx,
})
.collect();
let compressed = ToolResultCompressor::compress_batch(&entries, tc.as_ref(), &cfg);
let mut tokens_saved: usize = 0;
let mut results_compressed: u32 = 0;
for (result, (part_idx, _, _)) in compressed.iter().zip(indexed_texts.iter()) {
if result.method != CompressionMethod::PassThrough
&& let MessagePart::ToolResult { content, .. } = &mut result_parts[*part_idx]
{
content.clone_from(&result.text);
tokens_saved = tokens_saved.saturating_add(
result
.original_tokens
.saturating_sub(result.compressed_tokens),
);
results_compressed += 1;
}
}
if results_compressed > 0 {
tracing::debug!(
tokens_saved,
results_compressed,
"acon: tool result compression applied"
);
self.update_metrics(|m| {
m.acon_tokens_saved = m
.acon_tokens_saved
.saturating_add(u64::try_from(tokens_saved).unwrap_or(u64::MAX));
m.acon_results_compressed = m
.acon_results_compressed
.saturating_add(u64::from(results_compressed));
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_tool_req(name: &str) -> zeph_llm::provider::ToolUseRequest {
zeph_llm::provider::ToolUseRequest {
id: format!("id_{name}"),
name: name.into(),
input: serde_json::Value::Null,
}
}
#[test]
fn apply_acon_compression_noop_when_disabled() {
use crate::testing::{MockChannel, MockToolExecutor, mock_provider};
use zeph_llm::provider::MessagePart;
use zeph_skills::registry::SkillRegistry;
let mut agent = Agent::new(
mock_provider(vec![]),
MockChannel::new(vec![] as Vec<String>),
SkillRegistry::empty(),
None,
5,
MockToolExecutor::no_tools(),
);
agent.services.memory.subsystems.acon_config.enabled = false;
let big_content = "word ".repeat(5000);
let mut parts = vec![MessagePart::ToolResult {
tool_use_id: "id_shell".to_owned(),
content: big_content.clone(),
is_error: false,
}];
let calls = vec![make_tool_req("shell")];
agent.apply_acon_compression(&calls, &mut parts);
if let MessagePart::ToolResult { content, .. } = &parts[0] {
assert_eq!(
content.len(),
big_content.len(),
"content must not be modified when acon is disabled"
);
} else {
panic!("expected ToolResult part");
}
}
#[test]
#[allow(clippy::too_many_lines)] fn pre_assembly_passes_preserve_image_sibling() {
use crate::testing::{MockChannel, MockToolExecutor, mock_provider};
use zeph_llm::provider::{ImageData, ToolUseRequest};
use zeph_skills::registry::SkillRegistry;
fn make_agent() -> Agent<MockChannel> {
let mut agent = Agent::new(
mock_provider(vec![]),
MockChannel::new(vec![] as Vec<String>),
SkillRegistry::empty(),
None,
5,
MockToolExecutor::no_tools(),
);
agent.services.memory.subsystems.acon_config.enabled = true;
agent
}
fn tool_result(id: &str, content: String) -> MessagePart {
MessagePart::ToolResult {
tool_use_id: id.to_owned(),
content,
is_error: false,
}
}
let big_a = "alpha ".repeat(3000);
let big_b = "bravo ".repeat(3000);
let calls = vec![
ToolUseRequest {
id: "id_a".to_owned(),
name: "read".into(),
input: serde_json::Value::Null,
},
ToolUseRequest {
id: "id_b".to_owned(),
name: "read".into(),
input: serde_json::Value::Null,
},
];
let image_bytes = vec![1u8, 2, 3, 4, 5];
let image_mime = "image/png".to_owned();
let image = MessagePart::Image(Box::new(ImageData {
data: image_bytes.clone(),
mime_type: image_mime.clone(),
}));
let big_a_original_len = big_a.len();
let mut control_parts = vec![
tool_result("id_a", big_a.clone()),
tool_result("id_b", big_b.clone()),
];
let mut control_agent = make_agent();
control_agent.apply_acon_compression(&calls, &mut control_parts);
let mut interleaved_parts = vec![
tool_result("id_a", big_a),
image,
tool_result("id_b", big_b),
];
let mut agent = make_agent();
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
rt.block_on(async {
agent
.run_causal_ipi_post_probe(None, &interleaved_parts)
.await;
});
agent.record_shadow_event(&calls, "goal summary".into());
agent.apply_acon_compression(&calls, &mut interleaved_parts);
let MessagePart::ToolResult {
content: control_a, ..
} = &control_parts[0]
else {
panic!("expected ToolResult part in control run");
};
let MessagePart::ToolResult {
content: control_b, ..
} = &control_parts[1]
else {
panic!("expected ToolResult part in control run");
};
let MessagePart::ToolResult {
content: interleaved_a,
..
} = &interleaved_parts[0]
else {
panic!("expected ToolResult part at index 0");
};
let MessagePart::ToolResult {
content: interleaved_b,
..
} = &interleaved_parts[2]
else {
panic!("expected ToolResult part at index 2");
};
assert!(
control_a.len() < big_a_original_len,
"sanity: compression must actually run (control_a shorter than original)"
);
assert_eq!(
control_a, interleaved_a,
"id_a compression must be identical with/without the interleaved Image sibling"
);
assert_eq!(
control_b, interleaved_b,
"id_b compression must be identical with/without the interleaved Image sibling"
);
match &interleaved_parts[1] {
MessagePart::Image(img) => {
assert_eq!(img.data, image_bytes, "Image bytes must be unchanged");
assert_eq!(
img.mime_type, image_mime,
"Image mime_type must be unchanged"
);
}
other => panic!("expected Image part at index 1, got {other:?}"),
}
}
}