synaptic_deep/middleware/
filesystem.rs1use async_trait::async_trait;
2use serde_json::Value;
3use std::sync::Arc;
4use synaptic_core::SynapticError;
5use synaptic_middleware::{AgentMiddleware, ToolCallRequest, ToolCaller};
6
7use crate::backend::Backend;
8
9pub struct FilesystemMiddleware {
15 backend: Arc<dyn Backend>,
16 eviction_threshold_chars: usize,
17}
18
19impl FilesystemMiddleware {
20 pub fn new(backend: Arc<dyn Backend>, eviction_threshold_tokens: usize) -> Self {
21 Self {
22 backend,
23 eviction_threshold_chars: eviction_threshold_tokens * 4,
24 }
25 }
26}
27
28#[async_trait]
29impl AgentMiddleware for FilesystemMiddleware {
30 async fn wrap_tool_call(
31 &self,
32 request: ToolCallRequest,
33 next: &dyn ToolCaller,
34 ) -> Result<Value, SynapticError> {
35 let tool_call_id = request.call.id.clone();
36 let result = next.call(request).await?;
37
38 let result_str = match &result {
39 Value::String(s) => s.clone(),
40 other => serde_json::to_string(other).unwrap_or_default(),
41 };
42
43 if result_str.len() > self.eviction_threshold_chars {
44 let eviction_path = format!(".evicted/{}.txt", tool_call_id);
45 let _ = self.backend.write_file(&eviction_path, &result_str).await;
46
47 let lines: Vec<&str> = result_str.lines().collect();
48 let preview = if lines.len() <= 10 {
49 result_str
50 } else {
51 let first = &lines[..5];
52 let last = &lines[lines.len() - 5..];
53 format!(
54 "{}\n\n... ({} lines omitted, full result in {}) ...\n\n{}",
55 first.join("\n"),
56 lines.len() - 10,
57 eviction_path,
58 last.join("\n")
59 )
60 };
61 Ok(Value::String(preview))
62 } else {
63 Ok(result)
64 }
65 }
66}