//! Tests for the opted-in tool-output summary. Do not edit. mod support; use bxcore::{CallId, DataClass, LogRecord, SessionId, ToolCall}; use loopd::baseline::Baseline; use loopd::compact; use loopd::llama::Client; use loopd::memory::NoMemory; use loopd::session::Session; use loopd::summarise; use loopd::tools::Registry; use loopd::turn::{Runtime, run_turn}; use support::{FakeServer, Home, Reply, ScriptedPort}; const CHAT: &str = "/v1/chat/completions"; const SUMMARISE: &str = "Summarise this tool result.\n"; fn sse(content: &str) -> Reply { let content_json = serde_json::to_string(content).unwrap(); let data1 = format!( r#"data: {{"choices":[{{"finish_reason":null,"index":0,"delta":{{"content":{content_json}}}}}],"id":"s","timings":{{"cache_n":0,"prompt_n":10,"predicted_n":1}}}}"# ); let data2 = r#"data: {"choices":[{"finish_reason":"stop","index":0,"delta":{}}],"id":"s","timings":{"cache_n":0,"prompt_n":10,"predicted_n":1}}"#; let body = format!("{data1}\n\n{data2}\n\ndata: [DONE]\n\n"); Reply::raw(format!( "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len() )) } fn ts() -> bxcore::Timestamp { bxcore::Timestamp::parse("2026-09-18T08:00:00.000Z").unwrap() } fn assistant(name: &str) -> LogRecord { LogRecord::Assistant { time: ts(), content: None, reasoning_content: None, tool_calls: vec![ToolCall { id: "call_f".to_string(), name: name.to_string(), arguments: "{}".to_string(), }], } } fn result(untrusted: bool) -> LogRecord { LogRecord::ToolResult { time: ts(), call: CallId(1), tool_call_id: "call_f".to_string(), content: "the fetched page is about tea".to_string(), class: DataClass::Public, untrusted, truncated: false, } } fn session(home: &Home, cfg: &loopd::config::Config) -> Session { let registry = Registry::m2b(); Session::create( &home.dir, SessionId::new("a").unwrap(), Baseline::assemble(cfg, ®istry).unwrap(), cfg.slots.main, ) .unwrap() } #[test] fn an_opted_in_result_gets_a_summary_on_the_background_slot() { let home = Home::new(); home.write("summarise.md", SUMMARISE); let server = FakeServer::start(); let mut cfg = home.config(&server.socket); cfg.compact.summarise = vec!["http_fetch".to_string()]; server.route(CHAT, vec![sse("tea page")]); let client = Client::new(cfg.clone()); let mut s = session(&home, &cfg); s.append(assistant("http_fetch")).unwrap(); s.append(result(false)).unwrap(); summarise::missing(&cfg, &client, &mut s).unwrap(); let sent = server.requests_to(CHAT); assert_eq!(sent.len(), 1); assert_eq!(sent[0].json()["id_slot"], 1); assert!(sent[0].json().get("tools").is_none()); assert!( s.records().iter().any(|r| matches!(r, LogRecord::ToolSummary { content, call, untrusted, .. } if content == "tea page" && *call == CallId(1) && !*untrusted)), "{:?}", s.records() ); } #[test] fn the_summary_copies_the_result_labels() { let home = Home::new(); home.write("summarise.md", SUMMARISE); let server = FakeServer::start(); let mut cfg = home.config(&server.socket); cfg.compact.summarise = vec!["http_fetch".to_string()]; server.route(CHAT, vec![sse("x")]); let client = Client::new(cfg.clone()); let mut s = session(&home, &cfg); s.append(assistant("http_fetch")).unwrap(); s.append(result(true)).unwrap(); summarise::missing(&cfg, &client, &mut s).unwrap(); match s.records().last() { Some(LogRecord::ToolSummary { class, untrusted, tool_call_id, .. }) => { assert_eq!(*class, DataClass::Public); assert!(*untrusted); assert_eq!(tool_call_id, "call_f"); } other => panic!("{other:?}"), } } #[test] fn a_tool_not_in_summarise_is_left_alone() { let home = Home::new(); home.write("summarise.md", SUMMARISE); let server = FakeServer::start(); let mut cfg = home.config(&server.socket); cfg.compact.summarise = vec!["http_fetch".to_string()]; let client = Client::new(cfg.clone()); let mut s = session(&home, &cfg); s.append(assistant("clock")).unwrap(); s.append(result(false)).unwrap(); summarise::missing(&cfg, &client, &mut s).unwrap(); assert!(server.requests_to(CHAT).is_empty()); assert!( !s.records() .iter() .any(|r| matches!(r, LogRecord::ToolSummary { .. })) ); } #[test] fn a_result_that_already_has_a_summary_is_not_summarised_again() { let home = Home::new(); home.write("summarise.md", SUMMARISE); let server = FakeServer::start(); let mut cfg = home.config(&server.socket); cfg.compact.summarise = vec!["http_fetch".to_string()]; server.route(CHAT, vec![sse("once")]); let client = Client::new(cfg.clone()); let mut s = session(&home, &cfg); s.append(assistant("http_fetch")).unwrap(); s.append(result(false)).unwrap(); summarise::missing(&cfg, &client, &mut s).unwrap(); summarise::missing(&cfg, &client, &mut s).unwrap(); assert_eq!(server.requests_to(CHAT).len(), 1); assert_eq!( s.records() .iter() .filter(|r| matches!(r, LogRecord::ToolSummary { .. })) .count(), 1 ); } #[test] fn messages_stay_the_same_after_a_summary() { let home = Home::new(); home.write("summarise.md", SUMMARISE); let server = FakeServer::start(); let mut cfg = home.config(&server.socket); cfg.compact.summarise = vec!["http_fetch".to_string()]; server.route(CHAT, vec![sse("tea page")]); let client = Client::new(cfg.clone()); let mut s = session(&home, &cfg); s.append(assistant("http_fetch")).unwrap(); s.append(result(false)).unwrap(); let before = loopd::baseline::messages(s.baseline(), s.records()); summarise::missing(&cfg, &client, &mut s).unwrap(); let after = loopd::baseline::messages(s.baseline(), s.records()); assert_eq!(before, after); } #[test] fn after_a_turn_an_opted_in_call_is_summarised() { let home = Home::new(); home.write("summarise.md", SUMMARISE); home.write("compact.md", "Summarise.\n"); let server = FakeServer::start(); let mut cfg = home.config(&server.socket); cfg.compact.summarise = vec!["read_file".to_string()]; server.route( CHAT, vec![ Reply::fixture("tool_call"), Reply::fixture("plain"), sse("hosts file"), ], ); let client = Client::new(cfg.clone()); let port = ScriptedPort::new(vec![support::ok_result("127.0.0.1 localhost\n")]); let registry = Registry::m2b(); let mut s = Session::create( &home.dir, SessionId::new("a").unwrap(), Baseline::assemble(&cfg, ®istry).unwrap(), cfg.slots.main, ) .unwrap(); let rt = Runtime { cfg: &cfg, client: &client, port: &port, registry: ®istry, ctx: None, }; let mut events = Vec::new(); let outcome = run_turn(&mut s, &rt, "hosts?", None, &mut |e| events.push(e.clone())); compact::finish_turn( &mut s, &rt, &NoMemory, "hosts?", None, &mut |e| events.push(e.clone()), outcome, ) .unwrap(); assert!( s.records() .iter() .any(|r| matches!(r, LogRecord::ToolSummary { .. })), "{:?}", s.records() ); assert_eq!( server.requests_to(CHAT).last().unwrap().json()["id_slot"], 1 ); }