Something went wrong. Try again.
Personal fork of prime agent. Mirror of: https://mnemosyne.sigilzero.dev/sigilzero/prime-agent
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336//! The interactive client-settings seam implementation: every call opens the//! file-backed settings manager over the run's directories.
use anyhow::Result;use std::path::PathBuf;use std::sync::Arc;
use pa_tui::client_settings::ClientSettings;
/// The seam handle the interactive run carries.#[derive(Clone)]pub struct CliClientSettings { cwd: PathBuf, agent_dir: PathBuf,}
impl CliClientSettings { pub fn new(cwd: PathBuf, agent_dir: PathBuf) -> Arc<Self> { Arc::new(Self { cwd, agent_dir }) }
fn manager(&self) -> pa_core::settings::SettingsManager { pa_core::settings::SettingsManager::create(&self.cwd, &self.agent_dir) }}
macro_rules! setting { ($get:ident, $set:ident, $getter:ident, $setter:ident, $ty:ty) => { fn $get(&self) -> $ty { self.manager().$getter() }
fn $set(&self, value: $ty) -> Result<()> { self.manager().$setter(value) } };}
macro_rules! str_setting { ($get:ident, $set:ident, $getter:ident, $setter:ident) => { fn $get(&self) -> String { self.manager().$getter().to_string() }
fn $set(&self, value: &str) -> Result<()> { self.manager().$setter(value) } };}
impl ClientSettings for CliClientSettings { fn compaction_threshold_percent(&self) -> f64 { self.manager().get_compaction_threshold_percent() }
fn set_compaction_threshold_percent(&self, percent: f64) -> Result<()> { self.manager().set_compaction_threshold_percent(percent) }
fn theme(&self) -> Option<String> { self.manager().get_theme().map(str::to_string) }
fn set_theme(&self, theme: &str) -> Result<()> { self.manager().set_theme(theme.to_string()) }
fn default_service_tier(&self) -> String { // The wire name of the persisted default tier; "default" when // unset or unreadable. serde_json::to_value(self.manager().get_default_service_tier()) .ok() .and_then(|value| value.as_str().map(str::to_string)) .unwrap_or_else(|| "default".to_string()) }
fn set_default_service_tier(&self, tier: &str) -> Result<()> { let parsed: pa_types::ai::ServiceTier = serde_json::from_value(serde_json::Value::String(tier.to_string())) .map_err(|error| anyhow::anyhow!("Invalid service tier \"{tier}\": {error}"))?; self.manager().set_default_service_tier(parsed) }
setting!( show_images, set_show_images, get_show_images, set_show_images, bool ); setting!( clear_on_shrink, set_clear_on_shrink, get_clear_on_shrink, set_clear_on_shrink, bool ); setting!( show_terminal_progress, set_show_terminal_progress, get_show_terminal_progress, set_show_terminal_progress, bool ); setting!( image_auto_resize, set_image_auto_resize, get_image_auto_resize, set_image_auto_resize, bool ); setting!( block_images, set_block_images, get_block_images, set_block_images, bool );
fn image_model(&self) -> Option<String> { self.manager().get_image_model() } setting!( enable_skill_commands, set_enable_skill_commands, get_enable_skill_commands, set_enable_skill_commands, bool ); setting!( enable_builtin_skills, set_enable_builtin_skills, get_enable_builtin_skills, set_enable_builtin_skills, bool ); setting!( show_hardware_cursor, set_show_hardware_cursor, get_show_hardware_cursor, set_show_hardware_cursor, bool ); setting!( editor_padding_x, set_editor_padding_x, get_editor_padding_x, set_editor_padding_x, u64 ); setting!( autocomplete_max_visible, set_autocomplete_max_visible, get_autocomplete_max_visible, set_autocomplete_max_visible, u64 ); setting!( quiet_startup, set_quiet_startup, get_quiet_startup, set_quiet_startup, bool ); str_setting!( idle_eviction_minutes, set_idle_eviction_minutes, get_idle_eviction_minutes, set_idle_eviction_minutes ); str_setting!( mermaid_rendering_mode, set_mermaid_rendering_mode, get_mermaid_rendering_mode, set_mermaid_rendering_mode ); str_setting!( tree_filter_mode, set_tree_filter_mode, get_tree_filter_mode, set_tree_filter_mode ); str_setting!( chat_detail, set_chat_detail, get_chat_detail, set_chat_detail ); setting!( warnings_anthropic_extra_usage, set_warnings_anthropic_extra_usage, get_warnings_anthropic_extra_usage, set_warnings_anthropic_extra_usage, bool );
/// `updateChannel`: the settings enum's wire value; unset reads as /// `None`. fn update_channel(&self) -> Option<String> { let channel = self.manager().get_update_channel()?; Some( match channel { pa_core::settings::UpdateChannel::Stable => "stable", pa_core::settings::UpdateChannel::Nightly => "nightly", } .to_string(), ) }
fn set_update_channel(&self, channel: &str) -> Result<()> { let channel = match channel { "stable" => pa_core::settings::UpdateChannel::Stable, "nightly" => pa_core::settings::UpdateChannel::Nightly, _ => anyhow::bail!("unknown update channel: {channel}"), }; self.manager().set_update_channel(channel) }
fn effective_update_channel(&self, version: &str) -> String { let preferred = self .manager() .get_update_channel() .map(|channel| match channel { pa_core::settings::UpdateChannel::Stable => { pa_core::update::version::UpdateChannel::Stable } pa_core::settings::UpdateChannel::Nightly => { pa_core::update::version::UpdateChannel::Nightly } }); pa_core::update::version::resolve_update_channel(version, preferred) .wire_name() .to_string() }}
#[cfg(test)]mod tests { use super::*;
/// The seam over the real pa-core settings store. #[test] fn seam_round_trips_through_the_settings_store() { let dir = tempfile::TempDir::new().expect("temp dir"); let agent_dir = dir.path().join("agent"); std::fs::create_dir_all(&agent_dir).expect("agent dir"); let settings = CliClientSettings::new(dir.path().to_path_buf(), agent_dir.clone());
assert!(settings.show_images()); assert!(!settings.quiet_startup()); assert_eq!(settings.idle_eviction_minutes(), "90"); assert_eq!(settings.mermaid_rendering_mode(), "streaming"); // An unset store uses the core default; a user preference must be saved. assert_eq!(settings.tree_filter_mode(), "default"); settings .set_tree_filter_mode("user-only") .expect("user-only preference"); assert_eq!(settings.tree_filter_mode(), "user-only"); let saved: serde_json::Value = serde_json::from_str( &std::fs::read_to_string(agent_dir.join("settings.json")).unwrap(), ) .unwrap(); assert_eq!(saved["treeFilterMode"], "user-only"); assert_eq!( CliClientSettings::new(dir.path().to_path_buf(), agent_dir.clone()).tree_filter_mode(), "user-only" ); assert!(settings.warnings_anthropic_extra_usage());
settings.set_theme("dark").expect("theme"); settings.set_idle_eviction_minutes("off").expect("idle"); settings.set_tree_filter_mode("all").expect("tree filter"); settings.set_show_images(false).expect("show images");
assert_eq!(settings.theme().as_deref(), Some("dark")); assert_eq!(settings.idle_eviction_minutes(), "off"); assert_eq!(settings.tree_filter_mode(), "all"); assert!(!settings.show_images());
let content = std::fs::read_to_string(agent_dir.join("settings.json")).expect("settings file"); let value: serde_json::Value = serde_json::from_str(&content).expect("parse"); assert_eq!(value["theme"], "dark"); assert_eq!(value["idleEvictionMinutes"], "off"); assert_eq!(value["treeFilterMode"], "all");
// The update channel starts unset (the version infers it) and // pins through the /nightly off path's seam. assert_eq!(settings.update_channel(), None); settings.set_update_channel("stable").expect("channel"); assert_eq!(settings.update_channel().as_deref(), Some("stable")); let content = std::fs::read_to_string(agent_dir.join("settings.json")).expect("settings file"); let value: serde_json::Value = serde_json::from_str(&content).expect("parse"); assert_eq!(value["updateChannel"], "stable"); // ...and the /nightly on path's. settings.set_update_channel("nightly").expect("channel"); assert_eq!(settings.update_channel().as_deref(), Some("nightly")); }
#[test] fn tree_filter_global_writes_preserve_project_override_precedence() { let dir = tempfile::TempDir::new().unwrap(); let agent = dir.path().join("agent"); let project = dir.path().join(".prime/agent"); std::fs::create_dir_all(&agent).unwrap(); std::fs::create_dir_all(&project).unwrap(); std::fs::write( agent.join("settings.json"), r#"{"treeFilterMode":"user-only"}"#, ) .unwrap(); let project_content = r#"{"treeFilterMode":"all","quietStartup":true}"#; std::fs::write(project.join("settings.json"), project_content).unwrap(); let settings = CliClientSettings::new(dir.path().to_path_buf(), agent.clone()); assert_eq!(settings.tree_filter_mode(), "all"); settings.set_tree_filter_mode("no-tools").unwrap(); let global: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(agent.join("settings.json")).unwrap()) .unwrap(); assert_eq!(global["treeFilterMode"], "no-tools"); assert_eq!( std::fs::read_to_string(project.join("settings.json")).unwrap(), project_content ); assert_eq!( CliClientSettings::new(dir.path().to_path_buf(), agent.clone()).tree_filter_mode(), "all" ); std::fs::write(project.join("settings.json"), r#"{"quietStartup":true}"#).unwrap(); let reopened = CliClientSettings::new(dir.path().to_path_buf(), agent); assert_eq!(reopened.tree_filter_mode(), "no-tools"); assert!(reopened.quiet_startup()); }}