From 18c08dbcda7c1ba72d7961fa2aa5af898541cc6d Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Sat, 18 Oct 2025 19:36:09 -0400 Subject: [PATCH 1/4] Dont exit silently when storing api key fails --- crates/goose-cli/src/commands/configure.rs | 27 ++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 692a998d231a..046d48fecd97 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -414,7 +414,23 @@ fn select_model_from_list( } } -/// Dialog for configuring the A provider and model +fn try_store_secret( + config: &Config, + key_name: &str, + value: String, +) -> Result> { + match config.set_secret(key_name, Value::String(value)) { + Ok(_) => Ok(true), + Err(e) => { + cliclack::outro(style(format!( + "Failed to store {} securely: {}. Please ensure your system's secure storage is accessible. Alternatively you can run with GOOSE_DISABLE_KEYRING=true or set the key in your environment variables", + key_name, e + )).on_red().white())?; + Ok(false) + } + } +} + pub async fn configure_provider_dialog() -> Result> { // Get global config instance let config = Config::global(); @@ -465,7 +481,9 @@ pub async fn configure_provider_dialog() -> Result> { .interact()? { if key.secret { - config.set_secret(&key.name, Value::String(env_value))?; + if !try_store_secret(&config, &key.name, env_value)? { + return Ok(false); + } } else { config.set_param(&key.name, Value::String(env_value))?; } @@ -505,7 +523,9 @@ pub async fn configure_provider_dialog() -> Result> { }; if key.secret { - config.set_secret(&key.name, Value::String(value))?; + if !try_store_secret(&config, &key.name, value)? { + return Ok(false); + } } else { config.set_param(&key.name, Value::String(value))?; } @@ -513,7 +533,6 @@ pub async fn configure_provider_dialog() -> Result> { } } Err(_) => { - // Check if this key uses OAuth flow if key.oauth_flow { handle_oauth_configuration(provider_name, &key.name).await?; } else { From ac77fe01ffbda54ed16c6a6ed9e93b6e63bec4d3 Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Wed, 22 Oct 2025 14:54:54 -0400 Subject: [PATCH 2/4] Lint --- crates/goose-cli/src/commands/configure.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 046d48fecd97..2a74e870b878 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -481,7 +481,7 @@ pub async fn configure_provider_dialog() -> Result> { .interact()? { if key.secret { - if !try_store_secret(&config, &key.name, env_value)? { + if !try_store_secret(config, &key.name, env_value)? { return Ok(false); } } else { @@ -523,7 +523,7 @@ pub async fn configure_provider_dialog() -> Result> { }; if key.secret { - if !try_store_secret(&config, &key.name, value)? { + if !try_store_secret(config, &key.name, value)? { return Ok(false); } } else { From 1f54ca6977be7fddcb81fa61aa60c60e067ef8fc Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Tue, 28 Oct 2025 10:12:43 -0400 Subject: [PATCH 3/4] Bubble errors up --- crates/goose-cli/src/cli.rs | 17 +++++++---------- crates/goose-cli/src/main.rs | 1 - 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index 5688645e19bb..c428dda65063 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -1092,7 +1092,7 @@ pub async fn cli() -> Result<()> { .await; if interactive { - let _ = session.interactive(input_config.contents).await; + session.interactive(input_config.contents).await?; } else if let Some(contents) = input_config.contents { let session_start = std::time::Instant::now(); let session_type = if recipe_info.is_some() { @@ -1146,8 +1146,9 @@ pub async fn cli() -> Result<()> { result?; } else { - eprintln!("Error: no text provided for prompt in headless mode"); - std::process::exit(1); + return Err(anyhow::anyhow!( + "no text provided for prompt in headless mode" + )); } return Ok(()); @@ -1199,8 +1200,7 @@ pub async fn cli() -> Result<()> { BenchCommand::Selectors { config } => BenchRunner::list_selectors(config)?, BenchCommand::InitConfig { name } => { let mut config = BenchRunConfig::default(); - let cwd = - std::env::current_dir().expect("Failed to get current working directory"); + let cwd = std::env::current_dir()?; config.output_dir = Some(cwd); config.save(name); } @@ -1243,7 +1243,7 @@ pub async fn cli() -> Result<()> { } None => { return if !Config::global().exists() { - let _ = handle_configure().await; + handle_configure().await?; Ok(()) } else { // Run session command by default @@ -1271,10 +1271,7 @@ pub async fn cli() -> Result<()> { retry_config: None, }) .await; - if let Err(e) = session.interactive(None).await { - eprintln!("Session ended with error: {}", e); - std::process::exit(1); - } + session.interactive(None).await?; Ok(()) }; } diff --git a/crates/goose-cli/src/main.rs b/crates/goose-cli/src/main.rs index c301e30139a8..5215a3df679d 100644 --- a/crates/goose-cli/src/main.rs +++ b/crates/goose-cli/src/main.rs @@ -30,7 +30,6 @@ async fn main() -> Result<()> { } } - // Then shutdown the providers goose::tracing::shutdown_otlp(); } From 9b6c22f15b29901f32264dd2c52d87017d68faf7 Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Tue, 28 Oct 2025 13:35:15 -0400 Subject: [PATCH 4/4] More stuff --- crates/goose-cli/src/cli.rs | 27 +- crates/goose-cli/src/commands/configure.rs | 347 ++++++++++----------- 2 files changed, 167 insertions(+), 207 deletions(-) diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index cbcb58be8e21..5fbcd336aaab 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -77,19 +77,19 @@ async fn get_or_create_session_id( } let Some(id) = identifier else { - if resume { + return if resume { let sessions = SessionManager::list_sessions().await?; let session_id = sessions .first() .map(|s| s.id.clone()) .ok_or_else(|| anyhow::anyhow!("No session found to resume"))?; - return Ok(Some(session_id)); + Ok(Some(session_id)) } else { let session = SessionManager::create_session(std::env::current_dir()?, "CLI Session".to_string()) .await?; - return Ok(Some(session.id)); - } + Ok(Some(session.id)) + }; }; if let Some(session_id) = id.session_id { @@ -817,7 +817,7 @@ pub struct RecipeInfo { pub retry_config: Option, } -pub async fn cli() -> Result<()> { +pub async fn cli() -> anyhow::Result<()> { let cli = Cli::parse(); // Track the current directory in projects.json @@ -850,20 +850,17 @@ pub async fn cli() -> Result<()> { match cli.command { Some(Command::Configure {}) => { - let _ = handle_configure().await; - return Ok(()); + handle_configure().await?; } Some(Command::Info { verbose }) => { handle_info(verbose)?; - return Ok(()); } Some(Command::Mcp { name }) => { crate::logging::setup_logging(Some(&format!("mcp-{name}")), None)?; - let _ = goose_mcp::mcp_server_runner::run_mcp_server(&name).await; + goose_mcp::mcp_server_runner::run_mcp_server(&name).await?; } Some(Command::Acp {}) => { - let _ = run_acp_agent().await; - return Ok(()); + run_acp_agent().await?; } Some(Command::Session { command, @@ -884,13 +881,9 @@ pub async fn cli() -> Result<()> { ascending, working_dir, limit, - }) => { - handle_session_list(format, ascending, working_dir, limit).await?; - Ok(()) - } + }) => Ok(handle_session_list(format, ascending, working_dir, limit).await?), Some(SessionCommand::Remove { id, regex }) => { - handle_session_remove(id, regex).await?; - return Ok(()); + Ok(handle_session_remove(id, regex).await?) } Some(SessionCommand::Export { identifier, diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 127449c7f5c0..3c913e77c8ca 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -11,7 +11,10 @@ use goose::config::extensions::{ name_to_key, remove_extension, set_extension, set_extension_enabled, }; use goose::config::permission::PermissionLevel; -use goose::config::{Config, ConfigError, ExperimentManager, ExtensionEntry, PermissionManager}; +use goose::config::signup_tetrate::TetrateAuth; +use goose::config::{ + configure_tetrate, Config, ConfigError, ExperimentManager, ExtensionEntry, PermissionManager, +}; use goose::conversation::message::Message; use goose::model::ModelConfig; use goose::providers::{create, providers}; @@ -19,13 +22,12 @@ use rmcp::model::{Tool, ToolAnnotations}; use rmcp::object; use serde_json::Value; use std::collections::HashMap; -use std::error::Error; // useful for light themes where there is no dicernible colour contrast between // cursor-selected and cursor-unselected items. const MULTISELECT_VISIBILITY_HINT: &str = "<"; -pub async fn handle_configure() -> Result<(), Box> { +pub async fn handle_configure() -> anyhow::Result<()> { let config = Config::global(); if !config.exists() { @@ -235,8 +237,8 @@ pub async fn handle_configure() -> Result<(), Box> { "toggle" => toggle_extensions_dialog(), "add" => configure_extensions_dialog(), "remove" => remove_extension_dialog(), - "settings" => configure_settings_dialog().await.and(Ok(())), - "providers" => configure_provider_dialog().await.and(Ok(())), + "settings" => configure_settings_dialog().await, + "providers" => configure_provider_dialog().await.map(|_| ()), "custom_providers" => configure_custom_provider_dialog(), _ => unreachable!(), } @@ -244,10 +246,7 @@ pub async fn handle_configure() -> Result<(), Box> { } /// Helper function to handle OAuth configuration for a provider -async fn handle_oauth_configuration( - provider_name: &str, - key_name: &str, -) -> Result<(), Box> { +async fn handle_oauth_configuration(provider_name: &str, key_name: &str) -> anyhow::Result<()> { let _ = cliclack::log::info(format!( "Configuring {} using OAuth device code flow...", key_name @@ -263,17 +262,24 @@ async fn handle_oauth_configuration( } Err(e) => { let _ = cliclack::log::error(format!("Failed to authenticate: {}", e)); - Err(format!("OAuth authentication failed for {}: {}", key_name, e).into()) + Err(anyhow::anyhow!( + "OAuth authentication failed for {}: {}", + key_name, + e + )) } }, Err(e) => { let _ = cliclack::log::error(format!("Failed to create provider for OAuth: {}", e)); - Err(format!("Failed to create provider for OAuth: {}", e).into()) + Err(anyhow::anyhow!( + "Failed to create provider for OAuth: {}", + e + )) } } } -fn interactive_model_search(models: &[String]) -> Result> { +fn interactive_model_search(models: &[String]) -> anyhow::Result { const MAX_VISIBLE: usize = 30; let mut query = String::new(); @@ -355,7 +361,7 @@ fn interactive_model_search(models: &[String]) -> Result> fn select_model_from_list( models: &[String], provider_meta: &goose::providers::base::ProviderMetadata, -) -> Result> { +) -> anyhow::Result { const MAX_MODELS: usize = 10; // Smart model selection: // If we have more than MAX_MODELS models, show the recommended models with additional search option. @@ -411,11 +417,7 @@ fn select_model_from_list( } } -fn try_store_secret( - config: &Config, - key_name: &str, - value: String, -) -> Result> { +fn try_store_secret(config: &Config, key_name: &str, value: String) -> anyhow::Result { match config.set_secret(key_name, Value::String(value)) { Ok(_) => Ok(true), Err(e) => { @@ -428,7 +430,7 @@ fn try_store_secret( } } -pub async fn configure_provider_dialog() -> Result> { +pub async fn configure_provider_dialog() -> anyhow::Result { // Get global config instance let config = Config::global(); @@ -659,7 +661,7 @@ pub async fn configure_provider_dialog() -> Result> { /// Configure extensions that can be used with goose /// Dialog for toggling which extensions are enabled/disabled -pub fn toggle_extensions_dialog() -> Result<(), Box> { +pub fn toggle_extensions_dialog() -> anyhow::Result<()> { let extensions = get_all_extensions(); if extensions.is_empty() { @@ -711,7 +713,7 @@ pub fn toggle_extensions_dialog() -> Result<(), Box> { Ok(()) } -pub fn configure_extensions_dialog() -> Result<(), Box> { +pub fn configure_extensions_dialog() -> anyhow::Result<()> { let extension_type = cliclack::select("What type of extension would you like to add?") .item( "built-in", @@ -1124,7 +1126,7 @@ pub fn configure_extensions_dialog() -> Result<(), Box> { Ok(()) } -pub fn remove_extension_dialog() -> Result<(), Box> { +pub fn remove_extension_dialog() -> anyhow::Result<()> { let extensions = get_all_extensions(); // Create a list of extension names and their enabled status @@ -1179,7 +1181,7 @@ pub fn remove_extension_dialog() -> Result<(), Box> { Ok(()) } -pub async fn configure_settings_dialog() -> Result<(), Box> { +pub async fn configure_settings_dialog() -> anyhow::Result<()> { let setting_type = cliclack::select("What setting would you like to configure?") .item("goose_mode", "goose mode", "Configure goose mode") .item( @@ -1250,7 +1252,7 @@ pub async fn configure_settings_dialog() -> Result<(), Box> { Ok(()) } -pub fn configure_goose_mode_dialog() -> Result<(), Box> { +pub fn configure_goose_mode_dialog() -> anyhow::Result<()> { let config = Config::global(); // Check if GOOSE_MODE is set as an environment variable @@ -1303,7 +1305,7 @@ pub fn configure_goose_mode_dialog() -> Result<(), Box> { Ok(()) } -pub fn configure_goose_router_strategy_dialog() -> Result<(), Box> { +pub fn configure_goose_router_strategy_dialog() -> anyhow::Result<()> { let config = Config::global(); let enable_router = cliclack::select("Would you like to enable smart tool routing?") @@ -1333,7 +1335,7 @@ pub fn configure_goose_router_strategy_dialog() -> Result<(), Box> { Ok(()) } -pub fn configure_tool_output_dialog() -> Result<(), Box> { +pub fn configure_tool_output_dialog() -> anyhow::Result<()> { let config = Config::global(); // Check if GOOSE_CLI_MIN_PRIORITY is set as an environment variable if std::env::var("GOOSE_CLI_MIN_PRIORITY").is_ok() { @@ -1366,7 +1368,7 @@ pub fn configure_tool_output_dialog() -> Result<(), Box> { /// Configure experiment features that can be used with goose /// Dialog for toggling which experiments are enabled/disabled -pub fn toggle_experiments_dialog() -> Result<(), Box> { +pub fn toggle_experiments_dialog() -> anyhow::Result<()> { let experiments = ExperimentManager::get_all()?; if experiments.is_empty() { @@ -1404,7 +1406,7 @@ pub fn toggle_experiments_dialog() -> Result<(), Box> { Ok(()) } -pub async fn configure_tool_permissions_dialog() -> Result<(), Box> { +pub async fn configure_tool_permissions_dialog() -> anyhow::Result<()> { let mut extensions: Vec = get_enabled_extensions() .into_iter() .map(|ext| ext.name().clone()) @@ -1556,7 +1558,7 @@ pub async fn configure_tool_permissions_dialog() -> Result<(), Box> { Ok(()) } -fn configure_recipe_dialog() -> Result<(), Box> { +fn configure_recipe_dialog() -> anyhow::Result<()> { let key_name = GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY; let config = Config::global(); let default_recipe_repo = std::env::var(key_name) @@ -1578,7 +1580,7 @@ fn configure_recipe_dialog() -> Result<(), Box> { Ok(()) } -fn configure_scheduler_dialog() -> Result<(), Box> { +fn configure_scheduler_dialog() -> anyhow::Result<()> { let config = Config::global(); // Check if GOOSE_SCHEDULER_TYPE is set as an environment variable @@ -1632,7 +1634,7 @@ fn configure_scheduler_dialog() -> Result<(), Box> { Ok(()) } -pub fn configure_max_turns_dialog() -> Result<(), Box> { +pub fn configure_max_turns_dialog() -> anyhow::Result<()> { let config = Config::global(); let current_max_turns: u32 = config.get_param("GOOSE_MAX_TURNS").unwrap_or(1000); @@ -1665,205 +1667,170 @@ pub fn configure_max_turns_dialog() -> Result<(), Box> { } /// Handle OpenRouter authentication -pub async fn handle_openrouter_auth() -> Result<(), Box> { +pub async fn handle_openrouter_auth() -> anyhow::Result<()> { use goose::config::{configure_openrouter, signup_openrouter::OpenRouterAuth}; use goose::conversation::message::Message; use goose::providers::create; // Use the OpenRouter authentication flow let mut auth_flow = OpenRouterAuth::new()?; - match auth_flow.complete_flow().await { - Ok(api_key) => { - println!("\nAuthentication complete!"); + let api_key = auth_flow.complete_flow().await?; + println!("\nAuthentication complete!"); - // Get config instance - let config = Config::global(); + // Get config instance + let config = Config::global(); - // Use the existing configure_openrouter function to set everything up - println!("\nConfiguring OpenRouter..."); - if let Err(e) = configure_openrouter(config, api_key) { - eprintln!("Failed to configure OpenRouter: {}", e); - return Err(e.into()); - } + // Use the existing configure_openrouter function to set everything up + println!("\nConfiguring OpenRouter..."); + configure_openrouter(config, api_key)?; - println!("✓ OpenRouter configuration complete"); - println!("✓ Models configured successfully"); + println!("✓ OpenRouter configuration complete"); + println!("✓ Models configured successfully"); - // Test configuration - get the model that was configured - println!("\nTesting configuration..."); - let configured_model: String = config.get_param("GOOSE_MODEL")?; - let model_config = match goose::model::ModelConfig::new(&configured_model) { - Ok(config) => config, - Err(e) => { - eprintln!("⚠️ Invalid model configuration: {}", e); - eprintln!( - "Your settings have been saved. Please check your model configuration." - ); - return Ok(()); - } - }; - - match create("openrouter", model_config).await { - Ok(provider) => { - // Simple test request - let test_result = provider - .complete( - "You are goose, an AI assistant.", - &[Message::user().with_text("Say 'Configuration test successful!'")], - &[], - ) - .await; - - match test_result { - Ok(_) => { - println!("✓ Configuration test passed!"); - - // Enable the developer extension by default if not already enabled - let entries = get_all_extensions(); - let has_developer = entries - .iter() - .any(|e| e.config.name() == "developer" && e.enabled); - - if !has_developer { - set_extension(ExtensionEntry { - enabled: true, - config: ExtensionConfig::Builtin { - name: "developer".to_string(), - display_name: Some( - goose::config::DEFAULT_DISPLAY_NAME.to_string(), - ), - timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT), - bundled: Some(true), - description: "Developer extension".to_string(), - available_tools: Vec::new(), - }, - }); - println!("✓ Developer extension enabled"); - } + // Test configuration - get the model that was configured + println!("\nTesting configuration..."); + let configured_model: String = config.get_param("GOOSE_MODEL")?; + let model_config = match goose::model::ModelConfig::new(&configured_model) { + Ok(config) => config, + Err(e) => { + eprintln!("⚠️ Invalid model configuration: {}", e); + eprintln!("Your settings have been saved. Please check your model configuration."); + return Ok(()); + } + }; - cliclack::outro("OpenRouter setup complete! You can now use goose.")?; - } - Err(e) => { - eprintln!("⚠️ Configuration test failed: {}", e); - eprintln!("Your settings have been saved, but there may be an issue with the connection."); - } + match create("openrouter", model_config).await { + Ok(provider) => { + // Simple test request + let test_result = provider + .complete( + "You are goose, an AI assistant.", + &[Message::user().with_text("Say 'Configuration test successful!'")], + &[], + ) + .await; + + match test_result { + Ok(_) => { + println!("✓ Configuration test passed!"); + + // Enable the developer extension by default if not already enabled + let entries = get_all_extensions(); + let has_developer = entries + .iter() + .any(|e| e.config.name() == "developer" && e.enabled); + + if !has_developer { + set_extension(ExtensionEntry { + enabled: true, + config: ExtensionConfig::Builtin { + name: "developer".to_string(), + display_name: Some(goose::config::DEFAULT_DISPLAY_NAME.to_string()), + timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT), + bundled: Some(true), + description: "Developer extension".to_string(), + available_tools: Vec::new(), + }, + }); + println!("✓ Developer extension enabled"); } + + cliclack::outro("OpenRouter setup complete! You can now use goose.")?; } Err(e) => { - eprintln!("⚠️ Failed to create provider for testing: {}", e); - eprintln!("Your settings have been saved. Please check your configuration."); + eprintln!("⚠️ Configuration test failed: {}", e); + eprintln!("Your settings have been saved, but there may be an issue with the connection."); } } } Err(e) => { - eprintln!("Authentication failed: {}", e); - return Err(e.into()); + eprintln!("⚠️ Failed to create provider for testing: {}", e); + eprintln!("Your settings have been saved. Please check your configuration."); } } - Ok(()) } -/// Handle Tetrate Agent Router Service authentication -pub async fn handle_tetrate_auth() -> Result<(), Box> { - use goose::config::{configure_tetrate, signup_tetrate::TetrateAuth}; - use goose::conversation::message::Message; - use goose::providers::create; - - // Use the Tetrate Agent Router Service authentication flow +pub async fn handle_tetrate_auth() -> anyhow::Result<()> { let mut auth_flow = TetrateAuth::new()?; - match auth_flow.complete_flow().await { - Ok(api_key) => { - println!("\nAuthentication complete!"); + let api_key = auth_flow.complete_flow().await?; - let config = Config::global(); + println!("\nAuthentication complete!"); - // Use the existing configure_tetrate function to set everything up - println!("\nConfiguring Tetrate Agent Router Service..."); - if let Err(e) = configure_tetrate(config, api_key) { - eprintln!("Failed to configure Tetrate Agent Router Service: {}", e); - return Err(e.into()); - } + let config = Config::global(); - println!("✓ Tetrate Agent Router Service configuration complete"); - println!("✓ Models configured successfully"); + println!("\nConfiguring Tetrate Agent Router Service..."); + configure_tetrate(config, api_key)?; - // Test configuration - get the model that was configured - println!("\nTesting configuration..."); - let configured_model: String = config.get_param("GOOSE_MODEL")?; - let model_config = match goose::model::ModelConfig::new(&configured_model) { - Ok(config) => config, - Err(e) => { - eprintln!("⚠️ Invalid model configuration: {}", e); - eprintln!( - "Your settings have been saved. Please check your model configuration." - ); - return Ok(()); - } - }; - - match create("tetrate", model_config).await { - Ok(provider) => { - // Simple test request - let test_result = provider - .complete( - "You are goose, an AI assistant.", - &[Message::user().with_text("Say 'Configuration test successful!'")], - &[], - ) - .await; - - match test_result { - Ok(_) => { - println!("✓ Configuration test passed!"); - - // Enable the developer extension by default if not already enabled - let entries = get_all_extensions(); - let has_developer = entries - .iter() - .any(|e| e.config.name() == "developer" && e.enabled); - - if !has_developer { - set_extension(ExtensionEntry { - enabled: true, - config: ExtensionConfig::Builtin { - name: "developer".to_string(), - display_name: Some( - goose::config::DEFAULT_DISPLAY_NAME.to_string(), - ), - timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT), - bundled: Some(true), - description: "Developer extension".to_string(), - available_tools: Vec::new(), - }, - }); - println!("✓ Developer extension enabled"); - } + println!("✓ Tetrate Agent Router Service configuration complete"); + println!("✓ Models configured successfully"); - cliclack::outro("Tetrate Agent Router Service setup complete! You can now use goose.")?; - } - Err(e) => { - eprintln!("⚠️ Configuration test failed: {}", e); - eprintln!("Your settings have been saved, but there may be an issue with the connection."); - } + // Test configuration + println!("\nTesting configuration..."); + let configured_model: String = config.get_param("GOOSE_MODEL")?; + let model_config = match goose::model::ModelConfig::new(&configured_model) { + Ok(config) => config, + Err(e) => { + eprintln!("⚠️ Invalid model configuration: {}", e); + eprintln!("Your settings have been saved. Please check your model configuration."); + return Ok(()); + } + }; + + match create("tetrate", model_config).await { + Ok(provider) => { + let test_result = provider + .complete( + "You are goose, an AI assistant.", + &[Message::user().with_text("Say 'Configuration test successful!'")], + &[], + ) + .await; + + match test_result { + Ok(_) => { + println!("✓ Configuration test passed!"); + + let entries = get_all_extensions(); + let has_developer = entries + .iter() + .any(|e| e.config.name() == "developer" && e.enabled); + + if !has_developer { + set_extension(ExtensionEntry { + enabled: true, + config: ExtensionConfig::Builtin { + name: "developer".to_string(), + display_name: Some(goose::config::DEFAULT_DISPLAY_NAME.to_string()), + timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT), + bundled: Some(true), + description: "Developer extension".to_string(), + available_tools: Vec::new(), + }, + }); + println!("✓ Developer extension enabled"); } + + cliclack::outro( + "Tetrate Agent Router Service setup complete! You can now use goose.", + )?; } Err(e) => { - eprintln!("⚠️ Failed to create provider for testing: {}", e); - eprintln!("Your settings have been saved. Please check your configuration."); + eprintln!("⚠️ Configuration test failed: {}", e); + eprintln!("Your settings have been saved, but there may be an issue with the connection."); } } } Err(e) => { - eprintln!("Authentication failed: {}", e); - return Err(e.into()); + eprintln!("⚠️ Failed to create provider for testing: {}", e); + eprintln!("Your settings have been saved. Please check your configuration."); } } Ok(()) } -fn add_provider() -> Result<(), Box> { +fn add_provider() -> anyhow::Result<()> { let provider_type = cliclack::select("What type of API is this?") .item( "openai_compatible", @@ -1943,7 +1910,7 @@ fn add_provider() -> Result<(), Box> { Ok(()) } -fn remove_provider() -> Result<(), Box> { +fn remove_provider() -> anyhow::Result<()> { let custom_providers_dir = goose::config::declarative_providers::custom_providers_dir(); let custom_providers = if custom_providers_dir.exists() { goose::config::declarative_providers::load_custom_providers(&custom_providers_dir)? @@ -1970,7 +1937,7 @@ fn remove_provider() -> Result<(), Box> { Ok(()) } -pub fn configure_custom_provider_dialog() -> Result<(), Box> { +pub fn configure_custom_provider_dialog() -> anyhow::Result<()> { let action = cliclack::select("What would you like to do?") .item( "add",