mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
chore: show important keys for provider configuration (#7265)
This commit is contained in:
@@ -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<bool> {
|
||||
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<String, _> = 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<bool> {
|
||||
// Get global config instance
|
||||
let config = Config::global();
|
||||
@@ -574,107 +698,31 @@ pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
|
||||
.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<String, _> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<T: ConfigValue>(required: bool, secret: bool) -> Self {
|
||||
pub fn from_value_type<T: ConfigValue>(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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -861,6 +861,7 @@ impl ProviderDef for ChatGptCodexProvider {
|
||||
true,
|
||||
true,
|
||||
None,
|
||||
false,
|
||||
)],
|
||||
)
|
||||
.with_unlisted_models()
|
||||
|
||||
@@ -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::<ClaudeCodeCommand>(true, false)],
|
||||
vec![ConfigKey::from_value_type::<ClaudeCodeCommand>(
|
||||
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
|
||||
|
||||
@@ -600,9 +600,9 @@ impl ProviderDef for CodexProvider {
|
||||
CODEX_KNOWN_MODELS.to_vec(),
|
||||
CODEX_DOC_URL,
|
||||
vec![
|
||||
ConfigKey::from_value_type::<CodexCommand>(true, false),
|
||||
ConfigKey::from_value_type::<CodexReasoningEffort>(false, false),
|
||||
ConfigKey::from_value_type::<CodexSkipGitCheck>(false, false),
|
||||
ConfigKey::from_value_type::<CodexCommand>(true, false, true),
|
||||
ConfigKey::from_value_type::<CodexReasoningEffort>(false, false, true),
|
||||
ConfigKey::from_value_type::<CodexSkipGitCheck>(false, false, true),
|
||||
],
|
||||
)
|
||||
.with_unlisted_models()
|
||||
|
||||
@@ -291,7 +291,7 @@ impl ProviderDef for CursorAgentProvider {
|
||||
CURSOR_AGENT_KNOWN_MODELS.to_vec(),
|
||||
CURSOR_AGENT_DOC_URL,
|
||||
vec![ConfigKey::from_value_type::<CursorAgentCommand>(
|
||||
true, false,
|
||||
true, false, true,
|
||||
)],
|
||||
)
|
||||
.with_unlisted_models()
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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::<GeminiCliCommand>(true, false)],
|
||||
vec![ConfigKey::from_value_type::<GeminiCliCommand>(
|
||||
true, false, true,
|
||||
)],
|
||||
)
|
||||
.with_unlisted_models()
|
||||
}
|
||||
|
||||
@@ -395,6 +395,7 @@ impl ProviderDef for GithubCopilotProvider {
|
||||
true,
|
||||
true,
|
||||
None,
|
||||
false,
|
||||
)],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -41,9 +41,10 @@ export default function ProviderConfigurationModal({
|
||||
const [error, setError] = useState<string | null>(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 && <SecureStorageNotice />}
|
||||
</>
|
||||
@@ -260,7 +261,7 @@ export default function ProviderConfigurationModal({
|
||||
</div>
|
||||
) : (
|
||||
<ProviderSetupActions
|
||||
requiredParameters={requiredParameters}
|
||||
primaryParameters={primaryParameters}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSubmitForm}
|
||||
onDelete={handleDelete}
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ interface ProviderSetupActionsProps {
|
||||
onCancelDelete?: () => 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({
|
||||
<Trash2 className="h-4 w-4 mr-2" /> Delete Provider
|
||||
</Button>
|
||||
)}
|
||||
{requiredParameters && requiredParameters.length > 0 ? (
|
||||
{primaryParameters && primaryParameters.length > 0 ? (
|
||||
<>
|
||||
<Button
|
||||
type="submit"
|
||||
|
||||
+5
-4
@@ -168,10 +168,11 @@ export default function DefaultProviderSetupForm({
|
||||
));
|
||||
};
|
||||
|
||||
let aboveFoldParameters = parameters.filter((p) => p.required);
|
||||
let belowFoldParameters = parameters.filter((p) => !p.required);
|
||||
if (aboveFoldParameters.length === 0) {
|
||||
aboveFoldParameters = belowFoldParameters;
|
||||
let aboveFoldParameters = parameters.filter((p) => p.primary);
|
||||
let belowFoldParameters = parameters.filter((p) => !p.primary);
|
||||
|
||||
if (aboveFoldParameters.length === 0 && parameters.length > 0) {
|
||||
aboveFoldParameters = parameters;
|
||||
belowFoldParameters = [];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user