//! summary.json reasoning-effort persistence tests. //! //! Regression tests for "what effort was this session run on?": a fresh //! session must record its resolved `reasoning_effort` in `summary.json` at //! creation — not only after an explicit model/effort switch (the old //! behavior, which left the field absent for sessions that never switched). //! //! Each test spawns a real `kigi agent stdio` process against a mock //! inference server and asserts on the persisted `summary.json`. //! //! Run locally: //! ```bash //! cargo test -p kigi-shell --test test_summary_reasoning_effort -- --ignored //! ``` use std::future::Future; use kigi_test_support::*; async fn with_local_set(f: F) where F: FnOnce() -> Fut, Fut: Future, { tokio::task::LocalSet::new().run_until(f()).await; } /// Find `summary.json` for `session_id` under `/.kigi/sessions/` and /// parse it. The sessions tree is `//summary.json`; /// matching on the directory name avoids re-implementing the cwd encoding. fn read_summary(home: &std::path::Path, session_id: &str) -> serde_json::Value { let sessions_root = home.join(".kigi").join("sessions"); let cwd_dirs = std::fs::read_dir(&sessions_root) .unwrap_or_else(|e| panic!("no sessions dir at {}: {e}", sessions_root.display())); for cwd_dir in cwd_dirs.flatten() { let candidate = cwd_dir.path().join(session_id).join("summary.json"); if candidate.is_file() { let raw = std::fs::read_to_string(&candidate).expect("read summary.json"); return serde_json::from_str(&raw).expect("parse summary.json"); } } panic!( "summary.json for session {session_id} not found under {}", sessions_root.display() ); } /// A fresh session on a model with a configured reasoning effort must persist /// that effort in `summary.json` without any model/effort switch. // requires pre-built binary #[tokio::test] #[ignore] async fn test_fresh_session_persists_reasoning_effort() { with_local_set(|| async { let server = MockInferenceServer::start() .await .expect("start mock server"); let workdir = git_workdir(); // Configure the mock catalog's model with an explicit effort via the // user config override (the same path a remote settings catalog entry or // `--effort` would populate). let home = tempfile::TempDir::new().expect("create temp home"); let kigi_dir = home.path().join(".kigi"); std::fs::create_dir_all(&kigi_dir).expect("create .kigi dir"); std::fs::write( kigi_dir.join("config.toml"), r#" [model.test-model] supports_reasoning_effort = true reasoning_effort = "high" "#, ) .expect("write config.toml"); let client = KigiStdioClient::spawn_with_home(&server, workdir.path(), home).await; client.initialize_with_timeout().await; let session_id = client.create_session_with_timeout(workdir.path()).await; let result = client.prompt_with_timeout(&session_id, "say hello").await; assert!(result.is_ok(), "prompt failed: {:?}", result.err()); let summary = read_summary(client.home_path(), &session_id.0); assert_eq!( summary.get("reasoning_effort").and_then(|v| v.as_str()), Some("high"), "fresh session must record its effort in summary.json; got: {summary}" ); }) .await; } /// A fresh session on a model with no configured effort must not invent one: /// `summary.json` omits the field (the model uses its server-side default). // requires pre-built binary #[tokio::test] #[ignore] async fn test_fresh_session_without_effort_omits_field() { with_local_set(|| async { let server = MockInferenceServer::start() .await .expect("start mock server"); let workdir = git_workdir(); let client = KigiStdioClient::spawn(&server, workdir.path()).await; client.initialize_with_timeout().await; let session_id = client.create_session_with_timeout(workdir.path()).await; let result = client.prompt_with_timeout(&session_id, "say hello").await; assert!(result.is_ok(), "prompt failed: {:?}", result.err()); let summary = read_summary(client.home_path(), &session_id.0); assert_eq!( summary.get("reasoning_effort"), None, "session without a configured effort must omit the field; got: {summary}" ); }) .await; }