diff --git a/crates/goose-cli/src/commands/acp.rs b/crates/goose-cli/src/commands/acp.rs index 8ee9acfe09d4..95094ce7e908 100644 --- a/crates/goose-cli/src/commands/acp.rs +++ b/crates/goose-cli/src/commands/acp.rs @@ -101,11 +101,11 @@ impl GooseAcpAgent { let config = Config::global(); let provider_name: String = config - .get_param("GOOSE_PROVIDER") + .get_goose_provider() .map_err(|e| anyhow::anyhow!("No provider configured: {}", e))?; let model_name: String = config - .get_param("GOOSE_MODEL") + .get_goose_model() .map_err(|e| anyhow::anyhow!("No model configured: {}", e))?; let model_config = goose::model::ModelConfig { diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index bf0ef6a0e614..82653c08e9e1 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -14,7 +14,8 @@ use goose::config::paths::Paths; use goose::config::permission::PermissionLevel; use goose::config::signup_tetrate::TetrateAuth; use goose::config::{ - configure_tetrate, Config, ConfigError, ExperimentManager, ExtensionEntry, PermissionManager, + configure_tetrate, Config, ConfigError, ExperimentManager, ExtensionEntry, GooseMode, + PermissionManager, }; use goose::conversation::message::Message; use goose::model::ModelConfig; @@ -421,7 +422,7 @@ fn select_model_from_list( } fn try_store_secret(config: &Config, key_name: &str, value: String) -> anyhow::Result { - match config.set_secret(key_name, Value::String(value)) { + match config.set_secret(key_name, &value) { Ok(_) => Ok(true), Err(e) => { cliclack::outro(style(format!( @@ -450,7 +451,7 @@ pub async fn configure_provider_dialog() -> anyhow::Result { .collect(); // Get current default provider if it exists - let current_provider: Option = config.get_param("GOOSE_PROVIDER").ok(); + let current_provider: Option = config.get_goose_provider().ok(); let default_provider = current_provider.unwrap_or_default(); // Select provider @@ -487,7 +488,7 @@ pub async fn configure_provider_dialog() -> anyhow::Result { return Ok(false); } } else { - config.set_param(&key.name, Value::String(env_value))?; + config.set_param(&key.name, &env_value)?; } let _ = cliclack::log::info(format!("Saved {} to {}", key.name, config.path())); } @@ -529,7 +530,7 @@ pub async fn configure_provider_dialog() -> anyhow::Result { return Ok(false); } } else { - config.set_param(&key.name, Value::String(value))?; + config.set_param(&key.name, &value)?; } } } @@ -558,9 +559,9 @@ pub async fn configure_provider_dialog() -> anyhow::Result { }; if key.secret { - config.set_secret(&key.name, Value::String(value))?; + config.set_secret(&key.name, &value)?; } else { - config.set_param(&key.name, Value::String(value))?; + config.set_param(&key.name, &value)?; } } } @@ -648,9 +649,8 @@ pub async fn configure_provider_dialog() -> anyhow::Result { match result { Ok((_message, _usage)) => { - // Update config with new values only if the test succeeds - config.set_param("GOOSE_PROVIDER", Value::String(provider_name.to_string()))?; - config.set_param("GOOSE_MODEL", Value::String(model.clone()))?; + config.set_goose_provider(provider_name)?; + config.set_goose_model(&model)?; print_config_file_saved()?; Ok(true) } @@ -877,7 +877,7 @@ pub fn configure_extensions_dialog() -> anyhow::Result<()> { // Try to store in keychain let keychain_key = key.to_string(); - match config.set_secret(&keychain_key, Value::String(value.clone())) { + match config.set_secret(&keychain_key, &value) { Ok(_) => { // Successfully stored in keychain, add to env_keys env_keys.push(keychain_key); @@ -973,7 +973,7 @@ pub fn configure_extensions_dialog() -> anyhow::Result<()> { // Try to store in keychain let keychain_key = key.to_string(); - match config.set_secret(&keychain_key, Value::String(value.clone())) { + match config.set_secret(&keychain_key, &value) { Ok(_) => { // Successfully stored in keychain, add to env_keys env_keys.push(keychain_key); @@ -1093,7 +1093,7 @@ pub fn configure_extensions_dialog() -> anyhow::Result<()> { // Try to store in keychain let keychain_key = key.to_string(); - match config.set_secret(&keychain_key, Value::String(value.clone())) { + match config.set_secret(&keychain_key, &Value::String(value.clone())) { Ok(_) => { // Successfully stored in keychain, add to env_keys env_keys.push(keychain_key); @@ -1273,46 +1273,35 @@ pub fn configure_goose_mode_dialog() -> anyhow::Result<()> { let mode = cliclack::select("Which goose mode would you like to configure?") .item( - "auto", + GooseMode::Auto, "Auto Mode", "Full file modification, extension usage, edit, create and delete files freely" ) .item( - "approve", + GooseMode::Approve, "Approve Mode", "All tools, extensions and file modifications will require human approval" ) .item( - "smart_approve", + GooseMode::SmartApprove, "Smart Approve Mode", "Editing, creating, deleting files and using extensions will require human approval" ) .item( - "chat", + GooseMode::Chat, "Chat Mode", "Engage with the selected provider without using tools, extensions, or file modification" ) .interact()?; - match mode { - "auto" => { - config.set_param("GOOSE_MODE", Value::String("auto".to_string()))?; - cliclack::outro("Set to Auto Mode - full file modification enabled")?; - } - "approve" => { - config.set_param("GOOSE_MODE", Value::String("approve".to_string()))?; - cliclack::outro("Set to Approve Mode - all tools and modifications require approval")?; - } - "smart_approve" => { - config.set_param("GOOSE_MODE", Value::String("smart_approve".to_string()))?; - cliclack::outro("Set to Smart Approve Mode - modifications require approval")?; - } - "chat" => { - config.set_param("GOOSE_MODE", Value::String("chat".to_string()))?; - cliclack::outro("Set to Chat Mode - no tools or modifications enabled")?; - } - _ => unreachable!(), + config.set_goose_mode(mode)?; + let msg = match mode { + GooseMode::Auto => "Set to Auto Mode - full file modification enabled", + GooseMode::Approve => "Set to Approve Mode - all tools and modifications require approval", + GooseMode::SmartApprove => "Set to Smart Approve Mode - modifications require approval", + GooseMode::Chat => "Set to Chat Mode - no tools or modifications enabled", }; + cliclack::outro(msg)?; Ok(()) } @@ -1321,28 +1310,25 @@ pub fn configure_goose_router_strategy_dialog() -> anyhow::Result<()> { let enable_router = cliclack::select("Would you like to enable smart tool routing?") .item( - "true", + true, "Enable Router", "Use LLM-based intelligence to select tools", ) .item( - "false", + false, "Disable Router", "Use the default tool selection strategy", ) .interact()?; - match enable_router { - "true" => { - config.set_param("GOOSE_ENABLE_ROUTER", Value::String("true".to_string()))?; - cliclack::outro("Router enabled - using LLM-based intelligence for tool selection")?; - } - "false" => { - config.set_param("GOOSE_ENABLE_ROUTER", Value::String("false".to_string()))?; - cliclack::outro("Router disabled - using default tool selection")?; - } - _ => unreachable!(), + config.set_param("GOOSE_ENABLE_ROUTER", enable_router)?; + let msg = if enable_router { + "Router enabled - using LLM-based intelligence for tool selection" + } else { + "Router disabled - using default tool selection" }; + cliclack::outro(msg)?; + Ok(()) } @@ -1360,15 +1346,15 @@ pub fn configure_tool_output_dialog() -> anyhow::Result<()> { match tool_log_level { "high" => { - config.set_param("GOOSE_CLI_MIN_PRIORITY", Value::from(0.8))?; + config.set_param("GOOSE_CLI_MIN_PRIORITY", 0.8)?; cliclack::outro("Showing tool output of high importance only.")?; } "medium" => { - config.set_param("GOOSE_CLI_MIN_PRIORITY", Value::from(0.2))?; + config.set_param("GOOSE_CLI_MIN_PRIORITY", 0.2)?; cliclack::outro("Showing tool output of medium importance.")?; } "all" => { - config.set_param("GOOSE_CLI_MIN_PRIORITY", Value::from(0.0))?; + config.set_param("GOOSE_CLI_MIN_PRIORITY", 0.0)?; cliclack::outro("Showing all tool output.")?; } _ => unreachable!(), @@ -1441,11 +1427,11 @@ pub async fn configure_tool_permissions_dialog() -> anyhow::Result<()> { let config = Config::global(); let provider_name: String = config - .get_param("GOOSE_PROVIDER") + .get_goose_provider() .expect("No provider configured. Please set model provider first"); let model: String = config - .get_param("GOOSE_MODEL") + .get_goose_model() .expect("No model configured. Please set model first"); let model_config = ModelConfig::new(&model)?; @@ -1591,7 +1577,7 @@ fn configure_recipe_dialog() -> anyhow::Result<()> { if input_value.clone().trim().is_empty() { config.delete(key_name)?; } else { - config.set_param(key_name, Value::String(input_value))?; + config.set_param(key_name, &input_value)?; } Ok(()) } @@ -1618,7 +1604,7 @@ pub fn configure_max_turns_dialog() -> anyhow::Result<()> { .interact()?; let max_turns: u32 = max_turns_input.parse()?; - config.set_param("GOOSE_MAX_TURNS", Value::from(max_turns))?; + config.set_param("GOOSE_MAX_TURNS", max_turns)?; cliclack::outro(format!( "Set maximum turns to {} - goose will ask for input after {} consecutive actions", @@ -1651,7 +1637,7 @@ pub async fn handle_openrouter_auth() -> anyhow::Result<()> { // Test configuration - get the model that was configured println!("\nTesting configuration..."); - let configured_model: String = config.get_param("GOOSE_MODEL")?; + let configured_model: String = config.get_goose_model()?; let model_config = match goose::model::ModelConfig::new(&configured_model) { Ok(config) => config, Err(e) => { @@ -1729,7 +1715,7 @@ pub async fn handle_tetrate_auth() -> anyhow::Result<()> { // Test configuration println!("\nTesting configuration..."); - let configured_model: String = config.get_param("GOOSE_MODEL")?; + let configured_model: String = config.get_goose_model()?; let model_config = match goose::model::ModelConfig::new(&configured_model) { Ok(config) => config, Err(e) => { diff --git a/crates/goose-cli/src/commands/web.rs b/crates/goose-cli/src/commands/web.rs index 636b2590ebbb..f49e44fa919c 100644 --- a/crates/goose-cli/src/commands/web.rs +++ b/crates/goose-cli/src/commands/web.rs @@ -139,7 +139,7 @@ pub async fn handle_web( let config = goose::config::Config::global(); - let provider_name: String = match config.get_param("GOOSE_PROVIDER") { + let provider_name: String = match config.get_goose_provider() { Ok(p) => p, Err(_) => { eprintln!("No provider configured. Run 'goose configure' first"); @@ -147,7 +147,7 @@ pub async fn handle_web( } }; - let model: String = match config.get_param("GOOSE_MODEL") { + let model: String = match config.get_goose_model() { Ok(m) => m, Err(_) => { eprintln!("No model configured. Run 'goose configure' first"); diff --git a/crates/goose-cli/src/recipes/recipe.rs b/crates/goose-cli/src/recipes/recipe.rs index 987b7d295b6e..ff722657289a 100644 --- a/crates/goose-cli/src/recipes/recipe.rs +++ b/crates/goose-cli/src/recipes/recipe.rs @@ -11,7 +11,6 @@ use goose::recipe::build_recipe::{ }; use goose::recipe::validate_recipe::parse_and_validate_parameters; use goose::recipe::Recipe; -use serde_json::Value; fn create_user_prompt_callback() -> impl Fn(&str, &str) -> Result { |key: &str, description: &str| -> Result { @@ -98,7 +97,7 @@ pub fn collect_missing_secrets(requirements: &[SecretRequirement]) -> Result<()> .unwrap_or_else(|_| String::new()); if !value.trim().is_empty() { - config.set_secret(&req.key, Value::String(value))?; + config.set_secret(&req.key, &value)?; println!("✅ Secret stored securely for {}", req.extension_name); } else { println!("⏭️ Skipped {} for {}", req.key, req.extension_name); diff --git a/crates/goose-cli/src/session/builder.rs b/crates/goose-cli/src/session/builder.rs index 7e0f0fce7df1..8c0811a4b415 100644 --- a/crates/goose-cli/src/session/builder.rs +++ b/crates/goose-cli/src/session/builder.rs @@ -208,7 +208,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession { .as_ref() .and_then(|s| s.goose_provider.clone()) }) - .or_else(|| config.get_param("GOOSE_PROVIDER").ok()) + .or_else(|| config.get_goose_provider().ok()) .expect("No provider configured. Run 'goose configure' first"); let model_name = session_config @@ -219,7 +219,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession { .as_ref() .and_then(|s| s.goose_model.clone()) }) - .or_else(|| config.get_param("GOOSE_MODEL").ok()) + .or_else(|| config.get_goose_model().ok()) .expect("No model configured. Run 'goose configure' first"); let temperature = session_config.settings.as_ref().and_then(|s| s.temperature); diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index f4712ecb3351..529fff3a1fa7 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -12,6 +12,7 @@ use crate::session::task_execution_display::{ }; use goose::conversation::Conversation; use std::io::Write; +use std::str::FromStr; pub use self::export::message_to_markdown; pub use builder::{build_session, SessionBuilderConfig, SessionSettings}; @@ -28,7 +29,7 @@ use completion::GooseCompleter; use goose::agents::extension::{Envs, ExtensionConfig}; use goose::agents::types::RetryConfig; use goose::agents::{Agent, SessionConfig}; -use goose::config::Config; +use goose::config::{Config, GooseMode}; use goose::providers::pricing::initialize_pricing_cache; use goose::session::SessionManager; use input::InputResult; @@ -545,21 +546,18 @@ impl CliSession { save_history(&mut editor); let config = Config::global(); - let mode = mode.to_lowercase(); - - // Check if mode is valid - if !["auto", "approve", "chat", "smart_approve"].contains(&mode.as_str()) { - output::render_error(&format!( - "Invalid mode '{}'. Mode must be one of: auto, approve, chat", - mode - )); - continue; - } - - config - .set_param("GOOSE_MODE", Value::String(mode.to_string())) - .unwrap(); - output::goose_mode_message(&format!("Goose mode set to '{}'", mode)); + let mode = match GooseMode::from_str(&mode.to_lowercase()) { + Ok(mode) => mode, + Err(_) => { + output::render_error(&format!( + "Invalid mode '{}'. Mode must be one of: auto, approve, chat, smart_approve", + mode + )); + continue; + } + }; + config.set_goose_mode(mode)?; + output::goose_mode_message(&format!("Goose mode set to '{:?}'", mode)); continue; } input::InputResult::Plan(options) => { @@ -787,12 +785,9 @@ impl CliSession { self.run_mode = RunMode::Normal; // set goose mode: auto if that isn't already the case let config = Config::global(); - let curr_goose_mode = - config.get_param("GOOSE_MODE").unwrap_or("auto".to_string()); - if curr_goose_mode != "auto" { - config - .set_param("GOOSE_MODE", Value::String("auto".to_string())) - .unwrap(); + let curr_goose_mode = config.get_goose_mode().unwrap_or(GooseMode::Auto); + if curr_goose_mode != GooseMode::Auto { + config.set_goose_mode(GooseMode::Auto).unwrap(); } // clear the messages before acting on the plan @@ -807,10 +802,8 @@ impl CliSession { output::hide_thinking(); // Reset run & goose mode - if curr_goose_mode != "auto" { - config - .set_param("GOOSE_MODE", Value::String(curr_goose_mode.to_string())) - .unwrap(); + if curr_goose_mode != GooseMode::Auto { + config.set_goose_mode(curr_goose_mode)?; } } else { // add the plan response (assistant message) & carry the conversation forward @@ -1324,7 +1317,7 @@ impl CliSession { .unwrap_or(false); let provider_name = config - .get_param::("GOOSE_PROVIDER") + .get_goose_provider() .unwrap_or_else(|_| "unknown".to_string()); // Do not get costing information if show cost is disabled @@ -1488,7 +1481,7 @@ async fn get_reasoner() -> Result, anyhow::Error> { } else { println!("WARNING: GOOSE_PLANNER_PROVIDER not found. Using default provider..."); config - .get_param::("GOOSE_PROVIDER") + .get_goose_provider() .expect("No provider configured. Run 'goose configure' first") }; @@ -1498,7 +1491,7 @@ async fn get_reasoner() -> Result, anyhow::Error> { } else { println!("WARNING: GOOSE_PLANNER_MODEL not found. Using default model..."); config - .get_param::("GOOSE_MODEL") + .get_goose_model() .expect("No model configured. Run 'goose configure' first") }; diff --git a/crates/goose-cli/src/session/output.rs b/crates/goose-cli/src/session/output.rs index b1e0066da556..7b115ff90a2c 100644 --- a/crates/goose-cli/src/session/output.rs +++ b/crates/goose-cli/src/session/output.rs @@ -68,7 +68,7 @@ thread_local! { pub fn set_theme(theme: Theme) { let config = Config::global(); config - .set_param("GOOSE_CLI_THEME", Value::String(theme.as_config_string())) + .set_param("GOOSE_CLI_THEME", theme.as_config_string()) .expect("Failed to set theme"); CURRENT_THEME.with(|t| *t.borrow_mut() = theme); @@ -79,7 +79,7 @@ pub fn set_theme(theme: Theme) { Theme::Ansi => "ansi", }; - if let Err(e) = config.set_param("GOOSE_CLI_THEME", Value::String(theme_str.to_string())) { + if let Err(e) = config.set_param("GOOSE_CLI_THEME", theme_str) { eprintln!("Failed to save theme setting to config: {}", e); } } diff --git a/crates/goose-server/src/routes/agent.rs b/crates/goose-server/src/routes/agent.rs index b16cea136058..764e20b34f18 100644 --- a/crates/goose-server/src/routes/agent.rs +++ b/crates/goose-server/src/routes/agent.rs @@ -12,7 +12,7 @@ use axum::{ use goose::config::PermissionManager; use goose::agents::ExtensionConfig; -use goose::config::Config; +use goose::config::{Config, GooseMode}; use goose::model::ModelConfig; use goose::prompt_template::render_global_file; use goose::providers::{create, create_with_named_model}; @@ -211,15 +211,12 @@ async fn resume_agent( let config = Config::global(); let provider_result = async { - let provider_name: String = - config - .get_param("GOOSE_PROVIDER") - .map_err(|_| ErrorResponse { - message: "Could not configure agent: missing provider".into(), - status: StatusCode::INTERNAL_SERVER_ERROR, - })?; - - let model: String = config.get_param("GOOSE_MODEL").map_err(|_| ErrorResponse { + let provider_name: String = config.get_goose_provider().map_err(|_| ErrorResponse { + message: "Could not configure agent: missing provider".into(), + status: StatusCode::INTERNAL_SERVER_ERROR, + })?; + + let model: String = config.get_goose_model().map_err(|_| ErrorResponse { message: "Could not configure agent: missing model".into(), status: StatusCode::INTERNAL_SERVER_ERROR, })?; @@ -348,7 +345,7 @@ async fn get_tools( Query(query): Query, ) -> Result>, StatusCode> { let config = Config::global(); - let goose_mode = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string()); + let goose_mode = config.get_goose_mode().unwrap_or(GooseMode::Auto); let agent = state.get_agent_for_route(query.session_id).await?; let permission_manager = PermissionManager::default(); @@ -360,9 +357,9 @@ async fn get_tools( let permission = permission_manager .get_user_permission(&tool.name) .or_else(|| { - if goose_mode == "smart_approve" { + if goose_mode == GooseMode::SmartApprove { permission_manager.get_smart_approve_permission(&tool.name) - } else if goose_mode == "approve" { + } else if goose_mode == GooseMode::Approve { Some(PermissionLevel::AskBefore) } else { None @@ -406,10 +403,7 @@ async fn update_agent_provider( .await?; let config = Config::global(); - let model = match payload - .model - .or_else(|| config.get_param("GOOSE_MODEL").ok()) - { + let model = match payload.model.or_else(|| config.get_goose_model().ok()) { Some(m) => m, None => { tracing::error!("No model specified"); diff --git a/crates/goose-server/src/routes/audio.rs b/crates/goose-server/src/routes/audio.rs index 6985d96d1ce0..707f1b5b2742 100644 --- a/crates/goose-server/src/routes/audio.rs +++ b/crates/goose-server/src/routes/audio.rs @@ -251,7 +251,7 @@ async fn transcribe_elevenlabs_handler( // Migrate to secret storage if let Err(e) = config.set( "ELEVENLABS_API_KEY", - serde_json::Value::String(key.clone()), + &serde_json::Value::String(key.clone()), true, ) { tracing::error!("Failed to migrate ElevenLabs API key: {:?}", e); diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index c1b1de5d2777..8965b4e783e4 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -101,7 +101,7 @@ pub async fn upsert_config( Json(query): Json, ) -> Result, StatusCode> { let config = Config::global(); - let result = config.set(&query.key, query.value, query.is_secret); + let result = config.set(&query.key, &query.value, query.is_secret); match result { Ok(_) => Ok(Json(Value::String(format!("Upserted key {}", query.key)))), diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index 461843f4447f..0134f9867fa2 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -29,7 +29,7 @@ use crate::agents::tool_route_manager::ToolRouteManager; use crate::agents::tool_router_index_manager::ToolRouterIndexManager; use crate::agents::types::SessionConfig; use crate::agents::types::{FrontendTool, SharedProvider, ToolResultReceiver}; -use crate::config::{get_enabled_extensions, Config}; +use crate::config::{get_enabled_extensions, Config, GooseMode}; use crate::context_mgmt::DEFAULT_COMPACTION_THRESHOLD; use crate::conversation::{debug_conversation_fix, fix_conversation, Conversation}; use crate::mcp_utils::ToolResult; @@ -73,7 +73,7 @@ pub struct ReplyContext { pub tools: Vec, pub toolshim_tools: Vec, pub system_prompt: String, - pub goose_mode: String, + pub goose_mode: GooseMode, pub initial_messages: Vec, pub config: &'static Config, } @@ -193,7 +193,7 @@ impl Agent { // Add permission inspector (medium-high priority) // Note: mode will be updated dynamically based on session config tool_inspection_manager.add_inspector(Box::new(PermissionInspector::new( - "smart_approve".to_string(), + GooseMode::SmartApprove, std::collections::HashSet::new(), // readonly tools - will be populated from extension manager std::collections::HashSet::new(), // regular tools - will be populated from extension manager ))); @@ -264,7 +264,7 @@ impl Agent { // Update permission inspector mode to match the session mode self.tool_inspection_manager - .update_permission_inspector_mode(goose_mode.clone()) + .update_permission_inspector_mode(goose_mode) .await; Ok(ReplyContext { @@ -1026,8 +1026,7 @@ impl Agent { yield AgentEvent::Message(msg); } - let mode = goose_mode.clone(); - if mode.as_str() == "chat" { + if goose_mode == GooseMode::Chat { // Skip all tool calls in chat mode for request in remaining_requests { let mut response = message_tool_response.lock().await; @@ -1248,15 +1247,13 @@ impl Agent { })) } - fn determine_goose_mode(session: Option<&SessionConfig>, config: &Config) -> String { + fn determine_goose_mode(session: Option<&SessionConfig>, config: &Config) -> GooseMode { let mode = session.and_then(|s| s.execution_mode.as_deref()); match mode { - Some("foreground") => "chat".to_string(), - Some("background") => "auto".to_string(), - _ => config - .get_param("GOOSE_MODE") - .unwrap_or_else(|_| "auto".to_string()), + Some("foreground") => GooseMode::Chat, + Some("background") => GooseMode::Auto, + _ => config.get_goose_mode().unwrap_or(GooseMode::Auto), } } @@ -1515,7 +1512,7 @@ impl Agent { // but it doesn't know and the plumbing looks complicated. let config = Config::global(); let provider_name: String = config - .get_param("GOOSE_PROVIDER") + .get_goose_provider() .expect("No provider configured. Run 'goose configure' first"); let settings = Settings { diff --git a/crates/goose/src/agents/prompt_manager.rs b/crates/goose/src/agents/prompt_manager.rs index 133624d12ad9..8f8ca85f2c09 100644 --- a/crates/goose/src/agents/prompt_manager.rs +++ b/crates/goose/src/agents/prompt_manager.rs @@ -3,13 +3,16 @@ use chrono::DateTime; use chrono::Utc; use serde::Serialize; use serde_json::Value; -use std::borrow::Cow; use std::collections::HashMap; use crate::agents::extension::ExtensionInfo; use crate::agents::recipe_tools::dynamic_task_tools::should_enabled_subagents; use crate::agents::router_tools::llm_search_tool_prompt; -use crate::{config::Config, prompt_template, utils::sanitize_unicode_tags}; +use crate::{ + config::{Config, GooseMode}, + prompt_template, + utils::sanitize_unicode_tags, +}; const MAX_EXTENSIONS: usize = 5; const MAX_TOOLS: usize = 50; @@ -34,7 +37,7 @@ struct SystemPromptContext { current_date_time: String, #[serde(skip_serializing_if = "Option::is_none")] extension_tool_limits: Option<(usize, usize)>, - goose_mode: String, + goose_mode: GooseMode, is_autonomous: bool, enable_subagents: bool, max_extensions: usize, @@ -106,9 +109,7 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> { .collect(); let config = Config::global(); - let goose_mode = config - .get_param("GOOSE_MODE") - .unwrap_or_else(|_| Cow::from("auto")); + let goose_mode = config.get_goose_mode().unwrap_or(GooseMode::Auto); let extension_tool_limits = self .extension_tool_count @@ -119,8 +120,8 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> { tool_selection_strategy: self.router_enabled.then(llm_search_tool_prompt), current_date_time: self.manager.current_date_timestamp.clone(), extension_tool_limits, - goose_mode: goose_mode.to_string(), - is_autonomous: goose_mode == "auto", + goose_mode, + is_autonomous: goose_mode == GooseMode::Auto, enable_subagents: should_enabled_subagents(self.model_name.as_str()), max_extensions: MAX_EXTENSIONS, max_tools: MAX_TOOLS, @@ -137,7 +138,7 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> { }); let mut system_prompt_extras = self.manager.system_prompt_extras.clone(); - if goose_mode == "chat" { + if goose_mode == GooseMode::Chat { system_prompt_extras.push( "Right now you are in the chat only mode, no access to any tool use and system." .to_string(), diff --git a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs index f87cda290337..8f6b2881b249 100644 --- a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs +++ b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs @@ -9,6 +9,7 @@ use crate::agents::subagent_execution_tool::{ task_types::{Task, TaskType}, }; use crate::agents::tool_execution::ToolCallResult; +use crate::config::GooseMode; use crate::recipe::{Recipe, RecipeBuilder}; use anyhow::{anyhow, Result}; use rmcp::model::{Content, ErrorCode, ErrorData, Tool, ToolAnnotations}; @@ -93,7 +94,7 @@ pub struct TaskParameter { pub fn should_enabled_subagents(model_name: &str) -> bool { let config = crate::config::Config::global(); - let is_autonomous = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string()) == "auto"; + let is_autonomous = config.get_goose_mode().unwrap_or(GooseMode::Auto) == GooseMode::Auto; if !is_autonomous { return false; } diff --git a/crates/goose/src/config/base.rs b/crates/goose/src/config/base.rs index 3974a4b9a6cc..550695120b92 100644 --- a/crates/goose/src/config/base.rs +++ b/crates/goose/src/config/base.rs @@ -1,8 +1,9 @@ use crate::config::paths::Paths; +use crate::config::GooseMode; use fs2::FileExt; use keyring::Entry; use once_cell::sync::OnceCell; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; use std::env; @@ -143,6 +144,11 @@ macro_rules! declare_param { self.get_param(stringify!($param_name)) } } + paste::paste! { + pub fn [](&self, v: impl Into<$param_type>) -> Result<(), ConfigError> { + self.set_param(stringify!($param_name), &v.into()) + } + } }; } @@ -551,7 +557,10 @@ impl Config { } // save a parameter in the appropriate location based on if it's secret or not - pub fn set(&self, key: &str, value: Value, is_secret: bool) -> Result<(), ConfigError> { + pub fn set(&self, key: &str, value: &V, is_secret: bool) -> Result<(), ConfigError> + where + V: Serialize, + { if is_secret { self.set_secret(key, value) } else { @@ -606,17 +615,10 @@ impl Config { /// Returns a ConfigError if: /// - There is an error reading or writing the config file /// - There is an error serializing the value - pub fn set_param(&self, key: &str, value: Value) -> Result<(), ConfigError> { - // Lock before reading to prevent race condition. + pub fn set_param(&self, key: &str, value: V) -> Result<(), ConfigError> { let _guard = self.guard.lock().unwrap(); - - // Load current values with recovery if needed let mut values = self.load_values()?; - - // Modify values - values.insert(key.to_string(), value); - - // Save all values using the atomic write approach + values.insert(key.to_string(), serde_json::to_value(&value)?); self.save_values(values) } @@ -689,12 +691,15 @@ impl Config { /// Returns a ConfigError if: /// - There is an error accessing the keyring /// - There is an error serializing the value - pub fn set_secret(&self, key: &str, value: Value) -> Result<(), ConfigError> { + pub fn set_secret(&self, key: &str, value: &V) -> Result<(), ConfigError> + where + V: Serialize, + { // Lock before reading to prevent race condition. let _guard = self.guard.lock().unwrap(); let mut values = self.load_secrets()?; - values.insert(key.to_string(), value); + values.insert(key.to_string(), serde_json::to_value(value)?); match &self.secrets { SecretStorage::Keyring { service } => { @@ -742,6 +747,9 @@ impl Config { } declare_param!(GOOSE_SEARCH_PATHS, Vec); + declare_param!(GOOSE_MODE, GooseMode); + declare_param!(GOOSE_PROVIDER, String); + declare_param!(GOOSE_MODEL, String); } /// Load init-config.yaml from workspace root if it exists. @@ -819,7 +827,7 @@ mod tests { let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?; // Set a simple string value - config.set_param("test_key", Value::String("test_value".to_string()))?; + config.set_param("test_key", "test_value")?; // Test simple string retrieval let value: String = config.get_param("test_key")?; @@ -874,8 +882,8 @@ mod tests { let temp_file = NamedTempFile::new().unwrap(); let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?; - config.set_param("key1", Value::String("value1".to_string()))?; - config.set_param("key2", Value::Number(42.into()))?; + config.set_param("key1", "value1")?; + config.set_param("key2", 42)?; // Read the file directly to check YAML formatting let content = std::fs::read_to_string(temp_file.path())?; @@ -890,12 +898,11 @@ mod tests { let temp_file = NamedTempFile::new().unwrap(); let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?; - config.set_param("key", Value::String("value".to_string()))?; - - let value: String = config.get_param("key")?; - assert_eq!(value, "value"); + config.set_param("test_key", "test_value")?; + config.set_param("another_key", 42)?; + config.set_param("third_key", true)?; - config.delete("key")?; + let _values = config.load_values()?; let result: Result = config.get_param("key"); assert!(matches!(result, Err(ConfigError::NotFound(_)))); @@ -909,7 +916,7 @@ mod tests { let secrets_file = NamedTempFile::new().unwrap(); let config = Config::new_with_file_secrets(config_file.path(), secrets_file.path())?; - config.set_secret("key", Value::String("value".to_string()))?; + config.set_secret("key", &"value")?; let value: String = config.get_secret("key")?; assert_eq!(value, "value"); @@ -930,7 +937,7 @@ mod tests { let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?; // Test setting and getting a simple secret - config.set_secret("api_key", Value::String("secret123".to_string()))?; + config.set_secret("api_key", &Value::String("secret123".to_string()))?; let value: String = config.get_secret("api_key")?; assert_eq!(value, "secret123"); @@ -957,8 +964,8 @@ mod tests { let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?; // Set multiple secrets - config.set_secret("key1", Value::String("secret1".to_string()))?; - config.set_secret("key2", Value::String("secret2".to_string()))?; + config.set_secret("key1", &Value::String("secret1".to_string()))?; + config.set_secret("key2", &Value::String("secret2".to_string()))?; // Verify both exist let value1: String = config.get_secret("key1")?; @@ -1056,7 +1063,7 @@ mod tests { let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?; // Create a valid config first - config.set_param("key1", Value::String("value1".to_string()))?; + config.set_param("key1", "value1")?; // Verify the backup was created by the first write let backup_paths = config.get_backup_paths(); @@ -1066,7 +1073,7 @@ mod tests { } // Make another write to ensure backup is created - config.set_param("key2", Value::Number(42.into()))?; + config.set_param("key2", 42)?; // Check again for (i, path) in backup_paths.iter().enumerate() { @@ -1160,15 +1167,15 @@ mod tests { let config = Config::new(config_path, TEST_KEYRING_SERVICE)?; // First, create a config with some data - config.set_param("test_key_backup", Value::String("backup_value".to_string()))?; - config.set_param("another_key", Value::Number(42.into()))?; + config.set_param("test_key_backup", "backup_value")?; + config.set_param("another_key", 42)?; // Verify the backup was created let backup_paths = config.get_backup_paths(); let primary_backup = &backup_paths[0]; // .bak file // Make sure we have a backup by doing another write - config.set_param("third_key", Value::Bool(true))?; + config.set_param("third_key", true)?; assert!(primary_backup.exists(), "Backup should exist after writes"); // Now delete the main config file to simulate it being lost @@ -1204,7 +1211,7 @@ mod tests { let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?; // Set initial values - config.set_param("key1", Value::String("value1".to_string()))?; + config.set_param("key1", "value1")?; // Verify the config file exists and is valid assert!(temp_file.path().exists()); @@ -1225,7 +1232,7 @@ mod tests { // Create multiple versions to test rotation for i in 1..=7 { - config.set_param("version", Value::Number(i.into()))?; + config.set_param("version", i)?; } let backup_paths = config.get_backup_paths(); @@ -1460,7 +1467,7 @@ mod tests { let config = Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?; // Set value in config file - config.set_param("test_precedence", Value::String("file_value".to_string()))?; + config.set_param("test_precedence", "file_value")?; // Verify file value is returned when no env var let value: String = config.get_param("test_precedence")?; diff --git a/crates/goose/src/config/declarative_providers.rs b/crates/goose/src/config/declarative_providers.rs index 10df3ff1021b..a063ec475696 100644 --- a/crates/goose/src/config/declarative_providers.rs +++ b/crates/goose/src/config/declarative_providers.rs @@ -97,7 +97,7 @@ pub fn create_custom_provider( let api_key_name = generate_api_key_name(&id); let config = Config::global(); - config.set_secret(&api_key_name, serde_json::Value::String(api_key))?; + config.set_secret(&api_key_name, &api_key)?; let model_infos: Vec = models .into_iter() @@ -147,10 +147,7 @@ pub fn update_custom_provider( let config = Config::global(); if !api_key.is_empty() { - config.set_secret( - &existing_config.api_key_env, - serde_json::Value::String(api_key), - )?; + config.set_secret(&existing_config.api_key_env, &api_key)?; } if editable { diff --git a/crates/goose/src/config/experiments.rs b/crates/goose/src/config/experiments.rs index 80135adb19a7..c60802e2bc04 100644 --- a/crates/goose/src/config/experiments.rs +++ b/crates/goose/src/config/experiments.rs @@ -35,7 +35,7 @@ impl ExperimentManager { Self::refresh_experiments(&mut experiments); experiments.insert(name.to_string(), enabled); - config.set_param("experiments", serde_json::to_value(experiments)?)?; + config.set_param("experiments", experiments)?; Ok(()) } diff --git a/crates/goose/src/config/extensions.rs b/crates/goose/src/config/extensions.rs index 4241e1b42245..fac3d6f23c67 100644 --- a/crates/goose/src/config/extensions.rs +++ b/crates/goose/src/config/extensions.rs @@ -101,15 +101,9 @@ fn get_extensions_map() -> HashMap { fn save_extensions_map(extensions: HashMap) { let config = Config::global(); - match serde_json::to_value(extensions) { - Ok(value) => { - if let Err(e) = config.set_param(EXTENSIONS_CONFIG_KEY, value) { - tracing::debug!("Failed to save extensions config: {}", e); - } - } - Err(e) => { - tracing::debug!("Failed to serialize extensions: {}", e); - } + if let Err(e) = config.set_param(EXTENSIONS_CONFIG_KEY, &extensions) { + // TODO(jack) why is this just a debug statement? + tracing::debug!("Failed to save extensions config: {}", e); } } diff --git a/crates/goose/src/config/goose_mode.rs b/crates/goose/src/config/goose_mode.rs new file mode 100644 index 000000000000..e778aa8df729 --- /dev/null +++ b/crates/goose/src/config/goose_mode.rs @@ -0,0 +1,26 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum GooseMode { + Auto, + Approve, + SmartApprove, + Chat, +} + +impl FromStr for GooseMode { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "auto" => Ok(GooseMode::Auto), + "approve" => Ok(GooseMode::Approve), + "smart_approve" => Ok(GooseMode::SmartApprove), + "chat" => Ok(GooseMode::Chat), + _ => Err(format!("invalid mode: {}", s)), + } + } +} diff --git a/crates/goose/src/config/mod.rs b/crates/goose/src/config/mod.rs index c37a49e66b89..ffae26d460aa 100644 --- a/crates/goose/src/config/mod.rs +++ b/crates/goose/src/config/mod.rs @@ -2,6 +2,7 @@ pub mod base; pub mod declarative_providers; mod experiments; pub mod extensions; +pub mod goose_mode; pub mod paths; pub mod permission; pub mod search_path; @@ -16,6 +17,7 @@ pub use extensions::{ get_all_extension_names, get_all_extensions, get_enabled_extensions, get_extension_by_name, is_extension_enabled, remove_extension, set_extension, set_extension_enabled, ExtensionEntry, }; +pub use goose_mode::GooseMode; pub use permission::PermissionManager; pub use signup_openrouter::configure_openrouter; pub use signup_tetrate::configure_tetrate; diff --git a/crates/goose/src/config/signup_openrouter/mod.rs b/crates/goose/src/config/signup_openrouter/mod.rs index cc5c8b079c06..0423727079ec 100644 --- a/crates/goose/src/config/signup_openrouter/mod.rs +++ b/crates/goose/src/config/signup_openrouter/mod.rs @@ -162,14 +162,10 @@ impl PkceAuthFlow { pub use self::PkceAuthFlow as OpenRouterAuth; use crate::config::Config; -use serde_json::Value; pub fn configure_openrouter(config: &Config, api_key: String) -> Result<()> { - config.set_secret("OPENROUTER_API_KEY", Value::String(api_key))?; - config.set_param("GOOSE_PROVIDER", Value::String("openrouter".to_string()))?; - config.set_param( - "GOOSE_MODEL", - Value::String(OPENROUTER_DEFAULT_MODEL.to_string()), - )?; + config.set_secret("OPENROUTER_API_KEY", &api_key)?; + config.set_goose_provider("openrouter")?; + config.set_goose_model(OPENROUTER_DEFAULT_MODEL)?; Ok(()) } diff --git a/crates/goose/src/config/signup_tetrate/mod.rs b/crates/goose/src/config/signup_tetrate/mod.rs index b8b97c967a6c..b868983a8344 100644 --- a/crates/goose/src/config/signup_tetrate/mod.rs +++ b/crates/goose/src/config/signup_tetrate/mod.rs @@ -163,14 +163,10 @@ impl PkceAuthFlow { pub use self::PkceAuthFlow as TetrateAuth; use crate::config::Config; -use serde_json::Value; pub fn configure_tetrate(config: &Config, api_key: String) -> Result<()> { - config.set_secret("TETRATE_API_KEY", Value::String(api_key))?; - config.set_param("GOOSE_PROVIDER", Value::String("tetrate".to_string()))?; - config.set_param( - "GOOSE_MODEL", - Value::String(TETRATE_DEFAULT_MODEL.to_string()), - )?; + config.set_secret("TETRATE_API_KEY", &api_key)?; + config.set_goose_provider("tetrate")?; + config.set_goose_model(TETRATE_DEFAULT_MODEL)?; Ok(()) } diff --git a/crates/goose/src/config/signup_tetrate/tests.rs b/crates/goose/src/config/signup_tetrate/tests.rs index 8335337c2fd3..64f9136beb90 100644 --- a/crates/goose/src/config/signup_tetrate/tests.rs +++ b/crates/goose/src/config/signup_tetrate/tests.rs @@ -76,12 +76,9 @@ fn test_configure_tetrate() { config.get_secret::("TETRATE_API_KEY").unwrap(), test_key ); + assert_eq!(config.get_goose_provider().unwrap(), "tetrate"); assert_eq!( - config.get_param::("GOOSE_PROVIDER").unwrap(), - "tetrate" - ); - assert_eq!( - config.get_param::("GOOSE_MODEL").unwrap(), + config.get_goose_model().unwrap(), TETRATE_DEFAULT_MODEL.to_string() ); } diff --git a/crates/goose/src/oauth/persist.rs b/crates/goose/src/oauth/persist.rs index cf6fccaa1481..1600b8424862 100644 --- a/crates/goose/src/oauth/persist.rs +++ b/crates/goose/src/oauth/persist.rs @@ -27,9 +27,8 @@ pub async fn save_credentials( token_response, }; - let value = serde_json::to_value(&credentials)?; let key = secret_key(name); - config.set_secret(&key, value)?; + config.set_secret(&key, &credentials)?; Ok(()) } diff --git a/crates/goose/src/permission/permission_inspector.rs b/crates/goose/src/permission/permission_inspector.rs index fe4a347d4b43..88b99a6b05d9 100644 --- a/crates/goose/src/permission/permission_inspector.rs +++ b/crates/goose/src/permission/permission_inspector.rs @@ -1,6 +1,6 @@ use crate::agents::extension_manager_extension::MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE; use crate::config::permission::PermissionLevel; -use crate::config::PermissionManager; +use crate::config::{GooseMode, PermissionManager}; use crate::conversation::message::{Message, ToolRequest}; use crate::permission::permission_judge::PermissionCheckResult; use crate::tool_inspection::{InspectionAction, InspectionResult, ToolInspector}; @@ -12,7 +12,7 @@ use tokio::sync::Mutex; /// Permission Inspector that handles tool permission checking pub struct PermissionInspector { - mode: Arc>, + mode: Arc>, readonly_tools: HashSet, regular_tools: HashSet, pub permission_manager: Arc>, @@ -20,7 +20,7 @@ pub struct PermissionInspector { impl PermissionInspector { pub fn new( - mode: String, + mode: GooseMode, readonly_tools: HashSet, regular_tools: HashSet, ) -> Self { @@ -33,7 +33,7 @@ impl PermissionInspector { } pub fn with_permission_manager( - mode: String, + mode: GooseMode, readonly_tools: HashSet, regular_tools: HashSet, permission_manager: Arc>, @@ -47,7 +47,7 @@ impl PermissionInspector { } /// Update the mode of this permission inspector - pub async fn update_mode(&self, new_mode: String) { + pub async fn update_mode(&self, new_mode: GooseMode) { let mut mode = self.mode.lock().await; *mode = new_mode; } @@ -139,45 +139,42 @@ impl ToolInspector for PermissionInspector { if let Ok(tool_call) = &request.tool_call { let tool_name = &tool_call.name; - // Handle different modes - let action = if *mode == "chat" { - // In chat mode, all tools are skipped (handled elsewhere) - continue; - } else if *mode == "auto" { - // In auto mode, all tools are approved - InspectionAction::Allow - } else { - // Smart mode - check permissions - - // 1. Check user-defined permission first - if let Some(level) = permission_manager.get_user_permission(tool_name) { - match level { - PermissionLevel::AlwaysAllow => InspectionAction::Allow, - PermissionLevel::NeverAllow => InspectionAction::Deny, - PermissionLevel::AskBefore => InspectionAction::RequireApproval(None), + let action = match *mode { + GooseMode::Chat => continue, + GooseMode::Auto => InspectionAction::Allow, + GooseMode::Approve | GooseMode::SmartApprove => { + // 1. Check user-defined permission first + if let Some(level) = permission_manager.get_user_permission(tool_name) { + match level { + PermissionLevel::AlwaysAllow => InspectionAction::Allow, + PermissionLevel::NeverAllow => InspectionAction::Deny, + PermissionLevel::AskBefore => { + InspectionAction::RequireApproval(None) + } + } + } + // 2. Check if it's a readonly or regular tool (both pre-approved) + else if self.readonly_tools.contains(tool_name.as_ref()) + || self.regular_tools.contains(tool_name.as_ref()) + { + InspectionAction::Allow + } + // 4. Special case for extension management + else if tool_name == MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE { + InspectionAction::RequireApproval(Some( + "Extension management requires approval for security".to_string(), + )) + } + // 5. Default: require approval for unknown tools + else { + InspectionAction::RequireApproval(None) } - } - // 2. Check if it's a readonly or regular tool (both pre-approved) - else if self.readonly_tools.contains(tool_name.as_ref()) - || self.regular_tools.contains(tool_name.as_ref()) - { - InspectionAction::Allow - } - // 4. Special case for extension management - else if tool_name == MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE { - InspectionAction::RequireApproval(Some( - "Extension management requires approval for security".to_string(), - )) - } - // 5. Default: require approval for unknown tools - else { - InspectionAction::RequireApproval(None) } }; let reason = match &action { InspectionAction::Allow => { - if *mode == "auto" { + if *mode == GooseMode::Auto { "Auto mode - all tools approved".to_string() } else if self.readonly_tools.contains(tool_name.as_ref()) { "Tool marked as read-only".to_string() diff --git a/crates/goose/src/providers/claude_code.rs b/crates/goose/src/providers/claude_code.rs index 9683c2ee03d4..e64a212bd2d9 100644 --- a/crates/goose/src/providers/claude_code.rs +++ b/crates/goose/src/providers/claude_code.rs @@ -10,7 +10,7 @@ use tokio::process::Command; use super::base::{ConfigKey, Provider, ProviderMetadata, ProviderUsage, Usage}; use super::errors::ProviderError; use super::utils::RequestLog; -use crate::config::Config; +use crate::config::{Config, GooseMode}; use crate::conversation::message::{Message, MessageContent}; use crate::model::ModelConfig; use rmcp::model::Tool; @@ -338,10 +338,8 @@ impl ClaudeCodeProvider { // Add permission mode based on GOOSE_MODE setting let config = Config::global(); - if let Ok(goose_mode) = config.get_param::("GOOSE_MODE") { - if goose_mode.as_str() == "auto" { - cmd.arg("--permission-mode").arg("acceptEdits"); - } + if let Ok(GooseMode::Auto) = config.get_goose_mode() { + cmd.arg("--permission-mode").arg("acceptEdits"); } cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); @@ -523,18 +521,6 @@ mod tests { use super::ModelConfig; use super::*; - #[test] - fn test_permission_mode_flag_construction() { - // Test that in auto mode, the --permission-mode acceptEdits flag is added - std::env::set_var("GOOSE_MODE", "auto"); - - let config = Config::global(); - let goose_mode: String = config.get_param("GOOSE_MODE").unwrap(); - assert_eq!(goose_mode, "auto"); - - std::env::remove_var("GOOSE_MODE"); - } - #[tokio::test] async fn test_claude_code_invalid_model_no_fallback() { // Test that an invalid model is kept as-is (no fallback) diff --git a/crates/goose/src/providers/githubcopilot.rs b/crates/goose/src/providers/githubcopilot.rs index 08a7074c48ba..1b503aa50a38 100644 --- a/crates/goose/src/providers/githubcopilot.rs +++ b/crates/goose/src/providers/githubcopilot.rs @@ -235,7 +235,7 @@ impl GithubCopilotProvider { .get_access_token() .await .context("unable to login into github")?; - config.set_secret("GITHUB_COPILOT_TOKEN", Value::String(token.clone()))?; + config.set_secret("GITHUB_COPILOT_TOKEN", &token)?; token } _ => return Err(err.into()), @@ -500,7 +500,7 @@ impl Provider for GithubCopilotProvider { // Save the token config - .set_secret("GITHUB_COPILOT_TOKEN", Value::String(token)) + .set_secret("GITHUB_COPILOT_TOKEN", &token) .map_err(|e| ProviderError::ExecutionError(format!("Failed to save token: {}", e)))?; Ok(()) diff --git a/crates/goose/src/providers/ollama.rs b/crates/goose/src/providers/ollama.rs index 3a180084634b..87f17aabd9ab 100644 --- a/crates/goose/src/providers/ollama.rs +++ b/crates/goose/src/providers/ollama.rs @@ -6,6 +6,7 @@ use super::utils::{ get_model, handle_response_openai_compat, handle_status_openai_compat, RequestLog, }; use crate::config::declarative_providers::DeclarativeProviderConfig; +use crate::config::GooseMode; use crate::conversation::message::Message; use crate::conversation::Conversation; @@ -199,8 +200,12 @@ impl Provider for OllamaProvider { tools: &[Tool], ) -> Result<(Message, ProviderUsage), ProviderError> { let config = crate::config::Config::global(); - let goose_mode = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string()); - let filtered_tools = if goose_mode == "chat" { &[] } else { tools }; + let goose_mode = config.get_goose_mode().unwrap_or(GooseMode::Auto); + let filtered_tools = if goose_mode == GooseMode::Chat { + &[] + } else { + tools + }; let payload = create_request( &self.model, diff --git a/crates/goose/src/scheduler.rs b/crates/goose/src/scheduler.rs index 85269ae2fa37..0fd040817e1b 100644 --- a/crates/goose/src/scheduler.rs +++ b/crates/goose/src/scheduler.rs @@ -1104,7 +1104,7 @@ async fn run_scheduled_job_internal( agent_provider = provider; } else { let global_config = Config::global(); - let provider_name: String = match global_config.get_param("GOOSE_PROVIDER") { + let provider_name: String = match global_config.get_goose_provider() { Ok(name) => name, Err(_) => return Err(JobExecutionError { job_id: job.id.clone(), @@ -1114,7 +1114,7 @@ async fn run_scheduled_job_internal( }), }; let model_name: String = - match global_config.get_param("GOOSE_MODEL") { + match global_config.get_goose_model() { Ok(name) => name, Err(_) => return Err(JobExecutionError { job_id: job.id.clone(), diff --git a/crates/goose/src/tool_inspection.rs b/crates/goose/src/tool_inspection.rs index 56f1cdce66ac..2e372c91ae96 100644 --- a/crates/goose/src/tool_inspection.rs +++ b/crates/goose/src/tool_inspection.rs @@ -2,6 +2,7 @@ use anyhow::Result; use async_trait::async_trait; use std::collections::HashMap; +use crate::config::GooseMode; use crate::conversation::message::{Message, ToolRequest}; use crate::permission::permission_inspector::PermissionInspector; use crate::permission::permission_judge::PermissionCheckResult; @@ -116,7 +117,7 @@ impl ToolInspectionManager { } /// Update the permission inspector's mode - pub async fn update_permission_inspector_mode(&self, mode: String) { + pub async fn update_permission_inspector_mode(&self, mode: GooseMode) { for inspector in &self.inspectors { if inspector.name() == "permission" { // Downcast to PermissionInspector to access update_mode method diff --git a/crates/goose/src/tracing/otlp_layer.rs b/crates/goose/src/tracing/otlp_layer.rs index 958e188f31a1..b8edd745e125 100644 --- a/crates/goose/src/tracing/otlp_layer.rs +++ b/crates/goose/src/tracing/otlp_layer.rs @@ -301,16 +301,10 @@ mod tests { // Set values in config test_config - .set_param( - "otel_exporter_otlp_endpoint", - serde_json::Value::String("http://config:4318".to_string()), - ) + .set_param("otel_exporter_otlp_endpoint", "http://config:4318") .unwrap(); test_config - .set_param( - "otel_exporter_otlp_timeout", - serde_json::Value::Number(3000.into()), - ) + .set_param("otel_exporter_otlp_timeout", 3000) .unwrap(); // Test that from_config reads from the config file