diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index f6056fcbcb..02e977506b 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -21,6 +21,7 @@ use goose::config::{ }; use goose::model::ModelConfig; use goose::posthog::{get_telemetry_choice, TELEMETRY_ENABLED_KEY}; +use goose::providers::base::ConfigKey; use goose::providers::provider_test::test_provider_configuration; use goose::providers::{create, providers, retry_operation, RetryConfig}; use goose::session::SessionType; @@ -541,6 +542,129 @@ fn try_store_secret(config: &Config, key_name: &str, value: String) -> anyhow::R } } +async fn configure_single_key( + config: &Config, + provider_name: &str, + display_name: &str, + key: &ConfigKey, +) -> anyhow::Result { + let from_env = std::env::var(&key.name).ok(); + + match from_env { + Some(env_value) => { + let _ = cliclack::log::info(format!("{} is set via environment variable", key.name)); + if cliclack::confirm("Would you like to save this value to your keyring?") + .initial_value(true) + .interact()? + { + if key.secret { + if !try_store_secret(config, &key.name, env_value)? { + return Ok(false); + } + } else { + config.set_param(&key.name, &env_value)?; + } + let _ = cliclack::log::info(format!("Saved {} to {}", key.name, config.path())); + } + } + None => { + let existing: Result = if key.secret { + config.get_secret(&key.name) + } else { + config.get_param(&key.name) + }; + + match existing { + Ok(_) => { + let _ = cliclack::log::info(format!("{} is already configured", key.name)); + if cliclack::confirm("Would you like to update this value?").interact()? { + if key.oauth_flow { + handle_oauth_configuration(provider_name, &key.name).await?; + } else { + let value: String = if key.secret { + cliclack::password(format!("Enter new value for {}", key.name)) + .mask('▪') + .interact()? + } else { + let mut input = + cliclack::input(format!("Enter new value for {}", key.name)); + if key.default.is_some() { + input = input.default_input(&key.default.clone().unwrap()); + } + input.interact()? + }; + + if key.secret { + if !try_store_secret(config, &key.name, value)? { + return Ok(false); + } + } else { + config.set_param(&key.name, &value)?; + } + } + } + } + Err(_) => { + if key.oauth_flow { + handle_oauth_configuration(provider_name, &key.name).await?; + } else if !key.required && key.secret { + if cliclack::confirm(format!( + "Would you like to set {}? (optional)", + key.name + )) + .initial_value(true) + .interact()? + { + let value: String = + cliclack::password(format!("Enter value for {}", key.name)) + .mask('▪') + .interact()?; + if !try_store_secret(config, &key.name, value)? { + return Ok(false); + } + } + } else { + let prompt = if key.required { + format!( + "Provider {} requires {}, please enter a value", + display_name, key.name + ) + } else { + format!("Enter {} (optional, press Enter to skip)", key.name) + }; + + let value: String = if key.secret { + cliclack::password(&prompt).mask('▪').interact()? + } else { + let mut input = cliclack::input(&prompt); + if key.default.is_some() { + input = input.default_input(&key.default.clone().unwrap()); + } + if !key.required { + input = input.required(false); + } + input.interact()? + }; + + if value.is_empty() { + return Ok(true); + } + + if key.secret { + if !try_store_secret(config, &key.name, value)? { + return Ok(false); + } + } else { + config.set_param(&key.name, &value)?; + } + } + } + } + } + } + Ok(true) +} + pub async fn configure_provider_dialog() -> anyhow::Result { // Get global config instance let config = Config::global(); @@ -574,107 +698,31 @@ pub async fn configure_provider_dialog() -> anyhow::Result { .find(|(p, _)| &p.name == provider_name) .expect("Selected provider must exist in metadata"); - // Configure required provider keys - for key in &provider_meta.config_keys { - if !key.required { - continue; + for key in provider_meta + .config_keys + .iter() + .filter(|k| k.primary || k.oauth_flow) + { + if !configure_single_key(config, provider_name, &provider_meta.display_name, key).await? { + return Ok(false); } + } - // First check if the value is set via environment variable - let from_env = std::env::var(&key.name).ok(); - - match from_env { - Some(env_value) => { - let _ = - cliclack::log::info(format!("{} is set via environment variable", key.name)); - if cliclack::confirm("Would you like to save this value to your keyring?") - .initial_value(true) - .interact()? - { - if key.secret { - if !try_store_secret(config, &key.name, env_value)? { - return Ok(false); - } - } else { - config.set_param(&key.name, &env_value)?; - } - let _ = cliclack::log::info(format!("Saved {} to {}", key.name, config.path())); - } - } - None => { - let existing: Result = if key.secret { - config.get_secret(&key.name) - } else { - config.get_param(&key.name) - }; - - match existing { - Ok(_) => { - let _ = cliclack::log::info(format!("{} is already configured", key.name)); - if cliclack::confirm("Would you like to update this value?").interact()? { - // Check if this key uses OAuth flow - if key.oauth_flow { - handle_oauth_configuration(provider_name, &key.name).await?; - } else { - // Non-OAuth key, use manual entry - let value: String = if key.secret { - cliclack::password(format!("Enter new value for {}", key.name)) - .mask('▪') - .interact()? - } else { - let mut input = cliclack::input(format!( - "Enter new value for {}", - key.name - )); - if key.default.is_some() { - input = input.default_input(&key.default.clone().unwrap()); - } - input.interact()? - }; - - if key.secret { - if !try_store_secret(config, &key.name, value)? { - return Ok(false); - } - } else { - config.set_param(&key.name, &value)?; - } - } - } - } - Err(_) => { - if key.oauth_flow { - handle_oauth_configuration(provider_name, &key.name).await?; - } else { - // Non-OAuth key, use manual entry - let value: String = if key.secret { - cliclack::password(format!( - "Provider {} requires {}, please enter a value", - provider_meta.display_name, key.name - )) - .mask('▪') - .interact()? - } else { - let mut input = cliclack::input(format!( - "Provider {} requires {}, please enter a value", - provider_meta.display_name, key.name - )); - if key.default.is_some() { - input = input.default_input(&key.default.clone().unwrap()); - } - input.interact()? - }; - - if key.secret { - if !try_store_secret(config, &key.name, value)? { - return Ok(false); - } - } else { - config.set_param(&key.name, &value)?; - } - } - } - } + let non_primary_keys: Vec<_> = provider_meta + .config_keys + .iter() + .filter(|k| !k.primary && !k.oauth_flow) + .collect(); + if !non_primary_keys.is_empty() + && cliclack::confirm("Would you like to configure advanced settings?") + .initial_value(false) + .interact()? + { + for key in non_primary_keys { + if !configure_single_key(config, provider_name, &provider_meta.display_name, key) + .await? + { + return Ok(false); } } } diff --git a/crates/goose/src/providers/anthropic.rs b/crates/goose/src/providers/anthropic.rs index fd2cd814b8..24eb5fcab0 100644 --- a/crates/goose/src/providers/anthropic.rs +++ b/crates/goose/src/providers/anthropic.rs @@ -153,12 +153,13 @@ impl ProviderDef for AnthropicProvider { models, ANTHROPIC_DOC_URL, vec![ - ConfigKey::new("ANTHROPIC_API_KEY", true, true, None), + ConfigKey::new("ANTHROPIC_API_KEY", true, true, None, true), ConfigKey::new( "ANTHROPIC_HOST", true, false, Some("https://api.anthropic.com"), + false, ), ], ) diff --git a/crates/goose/src/providers/azure.rs b/crates/goose/src/providers/azure.rs index e9fdc2a105..4072ae6823 100644 --- a/crates/goose/src/providers/azure.rs +++ b/crates/goose/src/providers/azure.rs @@ -55,10 +55,16 @@ impl ProviderDef for AzureProvider { AZURE_OPENAI_KNOWN_MODELS.to_vec(), AZURE_DOC_URL, vec![ - ConfigKey::new("AZURE_OPENAI_ENDPOINT", true, false, None), - ConfigKey::new("AZURE_OPENAI_DEPLOYMENT_NAME", true, false, None), - ConfigKey::new("AZURE_OPENAI_API_VERSION", true, false, Some("2024-10-21")), - ConfigKey::new("AZURE_OPENAI_API_KEY", false, true, Some("")), + ConfigKey::new("AZURE_OPENAI_ENDPOINT", true, false, None, true), + ConfigKey::new("AZURE_OPENAI_DEPLOYMENT_NAME", true, false, None, true), + ConfigKey::new( + "AZURE_OPENAI_API_VERSION", + true, + false, + Some("2024-10-21"), + false, + ), + ConfigKey::new("AZURE_OPENAI_API_KEY", false, true, Some(""), true), ], ) } diff --git a/crates/goose/src/providers/base.rs b/crates/goose/src/providers/base.rs index bc3033373b..99559dc11f 100644 --- a/crates/goose/src/providers/base.rs +++ b/crates/goose/src/providers/base.rs @@ -205,27 +205,39 @@ pub struct ConfigKey { /// Whether this key should be configured using OAuth device code flow /// When true, the provider's configure_oauth() method will be called instead of prompting for manual input pub oauth_flow: bool, + /// Whether this key should be shown prominently during provider setup + /// (onboarding, settings modal, CLI configure) + #[serde(default)] + pub primary: bool, } impl ConfigKey { /// Create a new ConfigKey - pub fn new(name: &str, required: bool, secret: bool, default: Option<&str>) -> Self { + pub fn new( + name: &str, + required: bool, + secret: bool, + default: Option<&str>, + primary: bool, + ) -> Self { Self { name: name.to_string(), required, secret, default: default.map(|s| s.to_string()), oauth_flow: false, + primary, } } - pub fn from_value_type(required: bool, secret: bool) -> Self { + pub fn from_value_type(required: bool, secret: bool, primary: bool) -> Self { Self { name: T::KEY.to_string(), required, secret, default: Some(T::DEFAULT.to_string()), oauth_flow: false, + primary, } } @@ -233,13 +245,20 @@ impl ConfigKey { /// /// This is used for providers that support OAuth authentication instead of manual API key entry. /// When oauth_flow is true, the configuration system will call the provider's configure_oauth() method. - pub fn new_oauth(name: &str, required: bool, secret: bool, default: Option<&str>) -> Self { + pub fn new_oauth( + name: &str, + required: bool, + secret: bool, + default: Option<&str>, + primary: bool, + ) -> Self { Self { name: name.to_string(), required, secret, default: default.map(|s| s.to_string()), oauth_flow: true, + primary, } } } diff --git a/crates/goose/src/providers/bedrock.rs b/crates/goose/src/providers/bedrock.rs index 07f390ee76..24ffd18afa 100644 --- a/crates/goose/src/providers/bedrock.rs +++ b/crates/goose/src/providers/bedrock.rs @@ -277,9 +277,9 @@ impl ProviderDef for BedrockProvider { BEDROCK_KNOWN_MODELS.to_vec(), BEDROCK_DOC_LINK, vec![ - ConfigKey::new("AWS_PROFILE", false, false, Some("default")), - ConfigKey::new("AWS_REGION", false, false, None), - ConfigKey::new("AWS_BEARER_TOKEN_BEDROCK", false, true, None), + ConfigKey::new("AWS_PROFILE", false, false, Some("default"), true), + ConfigKey::new("AWS_REGION", false, false, None, true), + ConfigKey::new("AWS_BEARER_TOKEN_BEDROCK", false, true, None, true), ], ) } diff --git a/crates/goose/src/providers/chatgpt_codex.rs b/crates/goose/src/providers/chatgpt_codex.rs index b636c35e66..40f15d1b16 100644 --- a/crates/goose/src/providers/chatgpt_codex.rs +++ b/crates/goose/src/providers/chatgpt_codex.rs @@ -861,6 +861,7 @@ impl ProviderDef for ChatGptCodexProvider { true, true, None, + false, )], ) .with_unlisted_models() diff --git a/crates/goose/src/providers/claude_code.rs b/crates/goose/src/providers/claude_code.rs index d7ca8179d0..7a8bd02734 100644 --- a/crates/goose/src/providers/claude_code.rs +++ b/crates/goose/src/providers/claude_code.rs @@ -483,7 +483,9 @@ impl ProviderDef for ClaudeCodeProvider { // Only a few agentic choices; fetched dynamically via fetch_supported_models. vec![], CLAUDE_CODE_DOC_URL, - vec![ConfigKey::from_value_type::(true, false)], + vec![ConfigKey::from_value_type::( + true, false, true, + )], ) // The model list only returns aliases the `claude` CLI uses, such as "default" // and "haiku". There is no listing that includes full names like diff --git a/crates/goose/src/providers/codex.rs b/crates/goose/src/providers/codex.rs index 7da73c4c0a..3a31e9f621 100644 --- a/crates/goose/src/providers/codex.rs +++ b/crates/goose/src/providers/codex.rs @@ -600,9 +600,9 @@ impl ProviderDef for CodexProvider { CODEX_KNOWN_MODELS.to_vec(), CODEX_DOC_URL, vec![ - ConfigKey::from_value_type::(true, false), - ConfigKey::from_value_type::(false, false), - ConfigKey::from_value_type::(false, false), + ConfigKey::from_value_type::(true, false, true), + ConfigKey::from_value_type::(false, false, true), + ConfigKey::from_value_type::(false, false, true), ], ) .with_unlisted_models() diff --git a/crates/goose/src/providers/cursor_agent.rs b/crates/goose/src/providers/cursor_agent.rs index aeab6136cd..ad05cae1d5 100644 --- a/crates/goose/src/providers/cursor_agent.rs +++ b/crates/goose/src/providers/cursor_agent.rs @@ -291,7 +291,7 @@ impl ProviderDef for CursorAgentProvider { CURSOR_AGENT_KNOWN_MODELS.to_vec(), CURSOR_AGENT_DOC_URL, vec![ConfigKey::from_value_type::( - true, false, + true, false, true, )], ) .with_unlisted_models() diff --git a/crates/goose/src/providers/databricks.rs b/crates/goose/src/providers/databricks.rs index 34f4239e3f..a215ec4dd9 100644 --- a/crates/goose/src/providers/databricks.rs +++ b/crates/goose/src/providers/databricks.rs @@ -246,8 +246,8 @@ impl ProviderDef for DatabricksProvider { DATABRICKS_KNOWN_MODELS.to_vec(), DATABRICKS_DOC_URL, vec![ - ConfigKey::new("DATABRICKS_HOST", true, false, None), - ConfigKey::new("DATABRICKS_TOKEN", false, true, None), + ConfigKey::new("DATABRICKS_HOST", true, false, None, true), + ConfigKey::new("DATABRICKS_TOKEN", false, true, None, true), ], ) } diff --git a/crates/goose/src/providers/gcpvertexai.rs b/crates/goose/src/providers/gcpvertexai.rs index a089e65bdd..559c0a46d2 100644 --- a/crates/goose/src/providers/gcpvertexai.rs +++ b/crates/goose/src/providers/gcpvertexai.rs @@ -503,36 +503,41 @@ impl ProviderDef for GcpVertexAIProvider { KNOWN_MODELS.to_vec(), GCP_VERTEX_AI_DOC_URL, vec![ - ConfigKey::new("GCP_PROJECT_ID", true, false, None), + ConfigKey::new("GCP_PROJECT_ID", true, false, None, true), ConfigKey::new( "GCP_LOCATION", true, false, Some(&GcpLocation::Iowa.to_string()), + true, ), ConfigKey::new( "GCP_MAX_RETRIES", false, false, Some(&DEFAULT_MAX_RETRIES.to_string()), + false, ), ConfigKey::new( "GCP_INITIAL_RETRY_INTERVAL_MS", false, false, Some(&DEFAULT_INITIAL_RETRY_INTERVAL_MS.to_string()), + false, ), ConfigKey::new( "GCP_BACKOFF_MULTIPLIER", false, false, Some(&DEFAULT_BACKOFF_MULTIPLIER.to_string()), + false, ), ConfigKey::new( "GCP_MAX_RETRY_INTERVAL_MS", false, false, Some(&DEFAULT_MAX_RETRY_INTERVAL_MS.to_string()), + false, ), ], ) diff --git a/crates/goose/src/providers/gemini_cli.rs b/crates/goose/src/providers/gemini_cli.rs index 746e1d6b73..f458d9deb1 100644 --- a/crates/goose/src/providers/gemini_cli.rs +++ b/crates/goose/src/providers/gemini_cli.rs @@ -306,7 +306,9 @@ impl ProviderDef for GeminiCliProvider { GEMINI_CLI_DEFAULT_MODEL, GEMINI_CLI_KNOWN_MODELS.to_vec(), GEMINI_CLI_DOC_URL, - vec![ConfigKey::from_value_type::(true, false)], + vec![ConfigKey::from_value_type::( + true, false, true, + )], ) .with_unlisted_models() } diff --git a/crates/goose/src/providers/githubcopilot.rs b/crates/goose/src/providers/githubcopilot.rs index e7f767e4ba..e4fadb9d8f 100644 --- a/crates/goose/src/providers/githubcopilot.rs +++ b/crates/goose/src/providers/githubcopilot.rs @@ -395,6 +395,7 @@ impl ProviderDef for GithubCopilotProvider { true, true, None, + false, )], ) } diff --git a/crates/goose/src/providers/google.rs b/crates/goose/src/providers/google.rs index 769cc54200..fdd884ab49 100644 --- a/crates/goose/src/providers/google.rs +++ b/crates/goose/src/providers/google.rs @@ -117,8 +117,8 @@ impl ProviderDef for GoogleProvider { GOOGLE_KNOWN_MODELS.to_vec(), GOOGLE_DOC_URL, vec![ - ConfigKey::new("GOOGLE_API_KEY", true, true, None), - ConfigKey::new("GOOGLE_HOST", false, false, Some(GOOGLE_API_HOST)), + ConfigKey::new("GOOGLE_API_KEY", true, true, None, true), + ConfigKey::new("GOOGLE_HOST", false, false, Some(GOOGLE_API_HOST), false), ], ) } diff --git a/crates/goose/src/providers/litellm.rs b/crates/goose/src/providers/litellm.rs index 9f6c8306d9..78149bce57 100644 --- a/crates/goose/src/providers/litellm.rs +++ b/crates/goose/src/providers/litellm.rs @@ -145,16 +145,23 @@ impl ProviderDef for LiteLLMProvider { vec![], LITELLM_DOC_URL, vec![ - ConfigKey::new("LITELLM_API_KEY", true, true, None), - ConfigKey::new("LITELLM_HOST", true, false, Some("http://localhost:4000")), + ConfigKey::new("LITELLM_API_KEY", true, true, None, true), + ConfigKey::new( + "LITELLM_HOST", + true, + false, + Some("http://localhost:4000"), + true, + ), ConfigKey::new( "LITELLM_BASE_PATH", true, false, Some("v1/chat/completions"), + false, ), - ConfigKey::new("LITELLM_CUSTOM_HEADERS", false, true, None), - ConfigKey::new("LITELLM_TIMEOUT", false, false, Some("600")), + ConfigKey::new("LITELLM_CUSTOM_HEADERS", false, true, None, false), + ConfigKey::new("LITELLM_TIMEOUT", false, false, Some("600"), false), ], ) } diff --git a/crates/goose/src/providers/ollama.rs b/crates/goose/src/providers/ollama.rs index 2e18ea8238..b1c0e3ec2a 100644 --- a/crates/goose/src/providers/ollama.rs +++ b/crates/goose/src/providers/ollama.rs @@ -158,12 +158,13 @@ impl ProviderDef for OllamaProvider { OLLAMA_KNOWN_MODELS.to_vec(), OLLAMA_DOC_URL, vec![ - ConfigKey::new("OLLAMA_HOST", true, false, Some(OLLAMA_HOST)), + ConfigKey::new("OLLAMA_HOST", true, false, Some(OLLAMA_HOST), true), ConfigKey::new( "OLLAMA_TIMEOUT", false, false, Some(&(OLLAMA_TIMEOUT.to_string())), + false, ), ], ) diff --git a/crates/goose/src/providers/openai.rs b/crates/goose/src/providers/openai.rs index 41ab2fa405..480fd6cf60 100644 --- a/crates/goose/src/providers/openai.rs +++ b/crates/goose/src/providers/openai.rs @@ -285,13 +285,25 @@ impl ProviderDef for OpenAiProvider { models, OPEN_AI_DOC_URL, vec![ - ConfigKey::new("OPENAI_API_KEY", false, true, None), - ConfigKey::new("OPENAI_HOST", true, false, Some("https://api.openai.com")), - ConfigKey::new("OPENAI_BASE_PATH", true, false, Some("v1/chat/completions")), - ConfigKey::new("OPENAI_ORGANIZATION", false, false, None), - ConfigKey::new("OPENAI_PROJECT", false, false, None), - ConfigKey::new("OPENAI_CUSTOM_HEADERS", false, true, None), - ConfigKey::new("OPENAI_TIMEOUT", false, false, Some("600")), + ConfigKey::new("OPENAI_API_KEY", false, true, None, true), + ConfigKey::new( + "OPENAI_HOST", + true, + false, + Some("https://api.openai.com"), + false, + ), + ConfigKey::new( + "OPENAI_BASE_PATH", + true, + false, + Some("v1/chat/completions"), + false, + ), + ConfigKey::new("OPENAI_ORGANIZATION", false, false, None, false), + ConfigKey::new("OPENAI_PROJECT", false, false, None, false), + ConfigKey::new("OPENAI_CUSTOM_HEADERS", false, true, None, false), + ConfigKey::new("OPENAI_TIMEOUT", false, false, Some("600"), false), ], ) } diff --git a/crates/goose/src/providers/openrouter.rs b/crates/goose/src/providers/openrouter.rs index 39fd3149ff..05a4b6da04 100644 --- a/crates/goose/src/providers/openrouter.rs +++ b/crates/goose/src/providers/openrouter.rs @@ -158,12 +158,13 @@ impl ProviderDef for OpenRouterProvider { OPENROUTER_KNOWN_MODELS.to_vec(), OPENROUTER_DOC_URL, vec![ - ConfigKey::new("OPENROUTER_API_KEY", true, true, None), + ConfigKey::new("OPENROUTER_API_KEY", true, true, None, true), ConfigKey::new( "OPENROUTER_HOST", false, false, Some("https://openrouter.ai"), + false, ), ], ) diff --git a/crates/goose/src/providers/provider_registry.rs b/crates/goose/src/providers/provider_registry.rs index 8248a7b501..4c142becc1 100644 --- a/crates/goose/src/providers/provider_registry.rs +++ b/crates/goose/src/providers/provider_registry.rs @@ -109,8 +109,13 @@ impl ProviderRegistry { config_keys.remove(api_key_index); } else if !config.api_key_env.is_empty() { let api_key_required = provider_type == ProviderType::Declarative; - config_keys[api_key_index] = - super::base::ConfigKey::new(&config.api_key_env, api_key_required, true, None); + config_keys[api_key_index] = super::base::ConfigKey::new( + &config.api_key_env, + api_key_required, + true, + None, + true, + ); } } diff --git a/crates/goose/src/providers/sagemaker_tgi.rs b/crates/goose/src/providers/sagemaker_tgi.rs index 06b0fdc24e..4c2200014c 100644 --- a/crates/goose/src/providers/sagemaker_tgi.rs +++ b/crates/goose/src/providers/sagemaker_tgi.rs @@ -284,9 +284,9 @@ impl ProviderDef for SageMakerTgiProvider { vec![SAGEMAKER_TGI_DEFAULT_MODEL], SAGEMAKER_TGI_DOC_LINK, vec![ - ConfigKey::new("SAGEMAKER_ENDPOINT_NAME", false, false, None), - ConfigKey::new("AWS_REGION", true, false, Some("us-east-1")), - ConfigKey::new("AWS_PROFILE", true, false, Some("default")), + ConfigKey::new("SAGEMAKER_ENDPOINT_NAME", true, false, None, true), + ConfigKey::new("AWS_REGION", true, false, Some("us-east-1"), true), + ConfigKey::new("AWS_PROFILE", true, false, Some("default"), true), ], ) } diff --git a/crates/goose/src/providers/snowflake.rs b/crates/goose/src/providers/snowflake.rs index 1153fa8539..7cb6cd574e 100644 --- a/crates/goose/src/providers/snowflake.rs +++ b/crates/goose/src/providers/snowflake.rs @@ -309,8 +309,8 @@ impl ProviderDef for SnowflakeProvider { SNOWFLAKE_KNOWN_MODELS.to_vec(), SNOWFLAKE_DOC_URL, vec![ - ConfigKey::new("SNOWFLAKE_HOST", true, false, None), - ConfigKey::new("SNOWFLAKE_TOKEN", true, true, None), + ConfigKey::new("SNOWFLAKE_HOST", true, false, None, true), + ConfigKey::new("SNOWFLAKE_TOKEN", true, true, None, true), ], ) } diff --git a/crates/goose/src/providers/tetrate.rs b/crates/goose/src/providers/tetrate.rs index 1eff1cd3ef..f906aa597c 100644 --- a/crates/goose/src/providers/tetrate.rs +++ b/crates/goose/src/providers/tetrate.rs @@ -75,12 +75,13 @@ impl ProviderDef for TetrateProvider { TETRATE_KNOWN_MODELS.to_vec(), TETRATE_DOC_URL, vec![ - ConfigKey::new("TETRATE_API_KEY", true, true, None), + ConfigKey::new("TETRATE_API_KEY", true, true, None, true), ConfigKey::new( "TETRATE_HOST", false, false, Some("https://api.router.tetrate.ai"), + false, ), ], ) diff --git a/crates/goose/src/providers/venice.rs b/crates/goose/src/providers/venice.rs index 8ab86cb72e..0722e6ec4d 100644 --- a/crates/goose/src/providers/venice.rs +++ b/crates/goose/src/providers/venice.rs @@ -205,19 +205,21 @@ impl ProviderDef for VeniceProvider { FALLBACK_MODELS.to_vec(), VENICE_DOC_URL, vec![ - ConfigKey::new("VENICE_API_KEY", true, true, None), - ConfigKey::new("VENICE_HOST", true, false, Some(VENICE_DEFAULT_HOST)), + ConfigKey::new("VENICE_API_KEY", true, true, None, true), + ConfigKey::new("VENICE_HOST", true, false, Some(VENICE_DEFAULT_HOST), false), ConfigKey::new( "VENICE_BASE_PATH", true, false, Some(VENICE_DEFAULT_BASE_PATH), + false, ), ConfigKey::new( "VENICE_MODELS_PATH", true, false, Some(VENICE_DEFAULT_MODELS_PATH), + false, ), ], ) diff --git a/crates/goose/src/providers/xai.rs b/crates/goose/src/providers/xai.rs index 011cedaac9..7006ad3906 100644 --- a/crates/goose/src/providers/xai.rs +++ b/crates/goose/src/providers/xai.rs @@ -45,8 +45,8 @@ impl ProviderDef for XaiProvider { XAI_KNOWN_MODELS.to_vec(), XAI_DOC_URL, vec![ - ConfigKey::new("XAI_API_KEY", true, true, None), - ConfigKey::new("XAI_HOST", false, false, Some(XAI_API_HOST)), + ConfigKey::new("XAI_API_KEY", true, true, None, true), + ConfigKey::new("XAI_HOST", false, false, Some(XAI_API_HOST), false), ], ) } diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 5798424460..33106d8fb2 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -3588,6 +3588,10 @@ "type": "boolean", "description": "Whether this key should be configured using OAuth device code flow\nWhen true, the provider's configure_oauth() method will be called instead of prompting for manual input" }, + "primary": { + "type": "boolean", + "description": "Whether this key should be shown prominently during provider setup\n(onboarding, settings modal, CLI configure)" + }, "required": { "type": "boolean", "description": "Whether this key is required for the provider to function" diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 8371c16f6f..1dfb9b6d4d 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -90,6 +90,11 @@ export type ConfigKey = { * When true, the provider's configure_oauth() method will be called instead of prompting for manual input */ oauth_flow: boolean; + /** + * Whether this key should be shown prominently during provider setup + * (onboarding, settings modal, CLI configure) + */ + primary?: boolean; /** * Whether this key is required for the provider to function */ diff --git a/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx b/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx index a2f1fc6892..547fee53a6 100644 --- a/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx +++ b/ui/desktop/src/components/settings/providers/modal/ProviderConfiguationModal.tsx @@ -41,9 +41,10 @@ export default function ProviderConfigurationModal({ const [error, setError] = useState(null); const [isOAuthLoading, setIsOAuthLoading] = useState(false); - const requiredParameters = provider.metadata.config_keys.filter( - (param) => param.required === true - ); + let primaryParameters = provider.metadata.config_keys.filter((param) => param.primary); + if (primaryParameters.length === 0) { + primaryParameters = provider.metadata.config_keys; + } // Check if this provider uses OAuth for configuration const isOAuthProvider = provider.metadata.config_keys.some((key) => key.oauth_flow); @@ -238,7 +239,7 @@ export default function ProviderConfigurationModal({ validationErrors={validationErrors} /> - {requiredParameters.length > 0 && + {primaryParameters.length > 0 && provider.metadata.config_keys && provider.metadata.config_keys.length > 0 && } @@ -260,7 +261,7 @@ export default function ProviderConfigurationModal({ ) : ( void; canDelete?: boolean; providerName?: string; - requiredParameters?: ConfigKey[]; + primaryParameters?: ConfigKey[]; isActiveProvider?: boolean; // Made optional with default false } @@ -29,7 +29,7 @@ export default function ProviderSetupActions({ onCancelDelete, canDelete, providerName, - requiredParameters, + primaryParameters, isActiveProvider = false, // Default value provided }: ProviderSetupActionsProps) { // If we're showing delete confirmation, render the delete confirmation buttons @@ -96,7 +96,7 @@ export default function ProviderSetupActions({ Delete Provider )} - {requiredParameters && requiredParameters.length > 0 ? ( + {primaryParameters && primaryParameters.length > 0 ? ( <>