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::model::ModelConfig;
|
||||||
use goose::posthog::{get_telemetry_choice, TELEMETRY_ENABLED_KEY};
|
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::provider_test::test_provider_configuration;
|
||||||
use goose::providers::{create, providers, retry_operation, RetryConfig};
|
use goose::providers::{create, providers, retry_operation, RetryConfig};
|
||||||
use goose::session::SessionType;
|
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> {
|
pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
|
||||||
// Get global config instance
|
// Get global config instance
|
||||||
let config = Config::global();
|
let config = Config::global();
|
||||||
@@ -574,107 +698,31 @@ pub async fn configure_provider_dialog() -> anyhow::Result<bool> {
|
|||||||
.find(|(p, _)| &p.name == provider_name)
|
.find(|(p, _)| &p.name == provider_name)
|
||||||
.expect("Selected provider must exist in metadata");
|
.expect("Selected provider must exist in metadata");
|
||||||
|
|
||||||
// Configure required provider keys
|
for key in provider_meta
|
||||||
for key in &provider_meta.config_keys {
|
.config_keys
|
||||||
if !key.required {
|
.iter()
|
||||||
continue;
|
.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 non_primary_keys: Vec<_> = provider_meta
|
||||||
let from_env = std::env::var(&key.name).ok();
|
.config_keys
|
||||||
|
.iter()
|
||||||
match from_env {
|
.filter(|k| !k.primary && !k.oauth_flow)
|
||||||
Some(env_value) => {
|
.collect();
|
||||||
let _ =
|
if !non_primary_keys.is_empty()
|
||||||
cliclack::log::info(format!("{} is set via environment variable", key.name));
|
&& cliclack::confirm("Would you like to configure advanced settings?")
|
||||||
if cliclack::confirm("Would you like to save this value to your keyring?")
|
.initial_value(false)
|
||||||
.initial_value(true)
|
.interact()?
|
||||||
.interact()?
|
{
|
||||||
{
|
for key in non_primary_keys {
|
||||||
if key.secret {
|
if !configure_single_key(config, provider_name, &provider_meta.display_name, key)
|
||||||
if !try_store_secret(config, &key.name, env_value)? {
|
.await?
|
||||||
return Ok(false);
|
{
|
||||||
}
|
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)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,12 +153,13 @@ impl ProviderDef for AnthropicProvider {
|
|||||||
models,
|
models,
|
||||||
ANTHROPIC_DOC_URL,
|
ANTHROPIC_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("ANTHROPIC_API_KEY", true, true, None),
|
ConfigKey::new("ANTHROPIC_API_KEY", true, true, None, true),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"ANTHROPIC_HOST",
|
"ANTHROPIC_HOST",
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
Some("https://api.anthropic.com"),
|
Some("https://api.anthropic.com"),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -55,10 +55,16 @@ impl ProviderDef for AzureProvider {
|
|||||||
AZURE_OPENAI_KNOWN_MODELS.to_vec(),
|
AZURE_OPENAI_KNOWN_MODELS.to_vec(),
|
||||||
AZURE_DOC_URL,
|
AZURE_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("AZURE_OPENAI_ENDPOINT", true, false, None),
|
ConfigKey::new("AZURE_OPENAI_ENDPOINT", true, false, None, true),
|
||||||
ConfigKey::new("AZURE_OPENAI_DEPLOYMENT_NAME", true, false, None),
|
ConfigKey::new("AZURE_OPENAI_DEPLOYMENT_NAME", true, false, None, true),
|
||||||
ConfigKey::new("AZURE_OPENAI_API_VERSION", true, false, Some("2024-10-21")),
|
ConfigKey::new(
|
||||||
ConfigKey::new("AZURE_OPENAI_API_KEY", false, true, Some("")),
|
"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
|
/// 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
|
/// When true, the provider's configure_oauth() method will be called instead of prompting for manual input
|
||||||
pub oauth_flow: bool,
|
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 {
|
impl ConfigKey {
|
||||||
/// Create a new 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 {
|
Self {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
required,
|
required,
|
||||||
secret,
|
secret,
|
||||||
default: default.map(|s| s.to_string()),
|
default: default.map(|s| s.to_string()),
|
||||||
oauth_flow: false,
|
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 {
|
Self {
|
||||||
name: T::KEY.to_string(),
|
name: T::KEY.to_string(),
|
||||||
required,
|
required,
|
||||||
secret,
|
secret,
|
||||||
default: Some(T::DEFAULT.to_string()),
|
default: Some(T::DEFAULT.to_string()),
|
||||||
oauth_flow: false,
|
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.
|
/// 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.
|
/// 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 {
|
Self {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
required,
|
required,
|
||||||
secret,
|
secret,
|
||||||
default: default.map(|s| s.to_string()),
|
default: default.map(|s| s.to_string()),
|
||||||
oauth_flow: true,
|
oauth_flow: true,
|
||||||
|
primary,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,9 +277,9 @@ impl ProviderDef for BedrockProvider {
|
|||||||
BEDROCK_KNOWN_MODELS.to_vec(),
|
BEDROCK_KNOWN_MODELS.to_vec(),
|
||||||
BEDROCK_DOC_LINK,
|
BEDROCK_DOC_LINK,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("AWS_PROFILE", false, false, Some("default")),
|
ConfigKey::new("AWS_PROFILE", false, false, Some("default"), true),
|
||||||
ConfigKey::new("AWS_REGION", false, false, None),
|
ConfigKey::new("AWS_REGION", false, false, None, true),
|
||||||
ConfigKey::new("AWS_BEARER_TOKEN_BEDROCK", false, true, None),
|
ConfigKey::new("AWS_BEARER_TOKEN_BEDROCK", false, true, None, true),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -861,6 +861,7 @@ impl ProviderDef for ChatGptCodexProvider {
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
None,
|
None,
|
||||||
|
false,
|
||||||
)],
|
)],
|
||||||
)
|
)
|
||||||
.with_unlisted_models()
|
.with_unlisted_models()
|
||||||
|
|||||||
@@ -483,7 +483,9 @@ impl ProviderDef for ClaudeCodeProvider {
|
|||||||
// Only a few agentic choices; fetched dynamically via fetch_supported_models.
|
// Only a few agentic choices; fetched dynamically via fetch_supported_models.
|
||||||
vec![],
|
vec![],
|
||||||
CLAUDE_CODE_DOC_URL,
|
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"
|
// The model list only returns aliases the `claude` CLI uses, such as "default"
|
||||||
// and "haiku". There is no listing that includes full names like
|
// 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_KNOWN_MODELS.to_vec(),
|
||||||
CODEX_DOC_URL,
|
CODEX_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::from_value_type::<CodexCommand>(true, false),
|
ConfigKey::from_value_type::<CodexCommand>(true, false, true),
|
||||||
ConfigKey::from_value_type::<CodexReasoningEffort>(false, false),
|
ConfigKey::from_value_type::<CodexReasoningEffort>(false, false, true),
|
||||||
ConfigKey::from_value_type::<CodexSkipGitCheck>(false, false),
|
ConfigKey::from_value_type::<CodexSkipGitCheck>(false, false, true),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.with_unlisted_models()
|
.with_unlisted_models()
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ impl ProviderDef for CursorAgentProvider {
|
|||||||
CURSOR_AGENT_KNOWN_MODELS.to_vec(),
|
CURSOR_AGENT_KNOWN_MODELS.to_vec(),
|
||||||
CURSOR_AGENT_DOC_URL,
|
CURSOR_AGENT_DOC_URL,
|
||||||
vec![ConfigKey::from_value_type::<CursorAgentCommand>(
|
vec![ConfigKey::from_value_type::<CursorAgentCommand>(
|
||||||
true, false,
|
true, false, true,
|
||||||
)],
|
)],
|
||||||
)
|
)
|
||||||
.with_unlisted_models()
|
.with_unlisted_models()
|
||||||
|
|||||||
@@ -246,8 +246,8 @@ impl ProviderDef for DatabricksProvider {
|
|||||||
DATABRICKS_KNOWN_MODELS.to_vec(),
|
DATABRICKS_KNOWN_MODELS.to_vec(),
|
||||||
DATABRICKS_DOC_URL,
|
DATABRICKS_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("DATABRICKS_HOST", true, false, None),
|
ConfigKey::new("DATABRICKS_HOST", true, false, None, true),
|
||||||
ConfigKey::new("DATABRICKS_TOKEN", false, true, None),
|
ConfigKey::new("DATABRICKS_TOKEN", false, true, None, true),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -503,36 +503,41 @@ impl ProviderDef for GcpVertexAIProvider {
|
|||||||
KNOWN_MODELS.to_vec(),
|
KNOWN_MODELS.to_vec(),
|
||||||
GCP_VERTEX_AI_DOC_URL,
|
GCP_VERTEX_AI_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("GCP_PROJECT_ID", true, false, None),
|
ConfigKey::new("GCP_PROJECT_ID", true, false, None, true),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"GCP_LOCATION",
|
"GCP_LOCATION",
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
Some(&GcpLocation::Iowa.to_string()),
|
Some(&GcpLocation::Iowa.to_string()),
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"GCP_MAX_RETRIES",
|
"GCP_MAX_RETRIES",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
Some(&DEFAULT_MAX_RETRIES.to_string()),
|
Some(&DEFAULT_MAX_RETRIES.to_string()),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"GCP_INITIAL_RETRY_INTERVAL_MS",
|
"GCP_INITIAL_RETRY_INTERVAL_MS",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
Some(&DEFAULT_INITIAL_RETRY_INTERVAL_MS.to_string()),
|
Some(&DEFAULT_INITIAL_RETRY_INTERVAL_MS.to_string()),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"GCP_BACKOFF_MULTIPLIER",
|
"GCP_BACKOFF_MULTIPLIER",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
Some(&DEFAULT_BACKOFF_MULTIPLIER.to_string()),
|
Some(&DEFAULT_BACKOFF_MULTIPLIER.to_string()),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"GCP_MAX_RETRY_INTERVAL_MS",
|
"GCP_MAX_RETRY_INTERVAL_MS",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
Some(&DEFAULT_MAX_RETRY_INTERVAL_MS.to_string()),
|
Some(&DEFAULT_MAX_RETRY_INTERVAL_MS.to_string()),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -306,7 +306,9 @@ impl ProviderDef for GeminiCliProvider {
|
|||||||
GEMINI_CLI_DEFAULT_MODEL,
|
GEMINI_CLI_DEFAULT_MODEL,
|
||||||
GEMINI_CLI_KNOWN_MODELS.to_vec(),
|
GEMINI_CLI_KNOWN_MODELS.to_vec(),
|
||||||
GEMINI_CLI_DOC_URL,
|
GEMINI_CLI_DOC_URL,
|
||||||
vec![ConfigKey::from_value_type::<GeminiCliCommand>(true, false)],
|
vec![ConfigKey::from_value_type::<GeminiCliCommand>(
|
||||||
|
true, false, true,
|
||||||
|
)],
|
||||||
)
|
)
|
||||||
.with_unlisted_models()
|
.with_unlisted_models()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -395,6 +395,7 @@ impl ProviderDef for GithubCopilotProvider {
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
None,
|
None,
|
||||||
|
false,
|
||||||
)],
|
)],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ impl ProviderDef for GoogleProvider {
|
|||||||
GOOGLE_KNOWN_MODELS.to_vec(),
|
GOOGLE_KNOWN_MODELS.to_vec(),
|
||||||
GOOGLE_DOC_URL,
|
GOOGLE_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("GOOGLE_API_KEY", true, true, None),
|
ConfigKey::new("GOOGLE_API_KEY", true, true, None, true),
|
||||||
ConfigKey::new("GOOGLE_HOST", false, false, Some(GOOGLE_API_HOST)),
|
ConfigKey::new("GOOGLE_HOST", false, false, Some(GOOGLE_API_HOST), false),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,16 +145,23 @@ impl ProviderDef for LiteLLMProvider {
|
|||||||
vec![],
|
vec![],
|
||||||
LITELLM_DOC_URL,
|
LITELLM_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("LITELLM_API_KEY", true, true, None),
|
ConfigKey::new("LITELLM_API_KEY", true, true, None, true),
|
||||||
ConfigKey::new("LITELLM_HOST", true, false, Some("http://localhost:4000")),
|
ConfigKey::new(
|
||||||
|
"LITELLM_HOST",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
Some("http://localhost:4000"),
|
||||||
|
true,
|
||||||
|
),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"LITELLM_BASE_PATH",
|
"LITELLM_BASE_PATH",
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
Some("v1/chat/completions"),
|
Some("v1/chat/completions"),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
ConfigKey::new("LITELLM_CUSTOM_HEADERS", false, true, None),
|
ConfigKey::new("LITELLM_CUSTOM_HEADERS", false, true, None, false),
|
||||||
ConfigKey::new("LITELLM_TIMEOUT", false, false, Some("600")),
|
ConfigKey::new("LITELLM_TIMEOUT", false, false, Some("600"), false),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,12 +158,13 @@ impl ProviderDef for OllamaProvider {
|
|||||||
OLLAMA_KNOWN_MODELS.to_vec(),
|
OLLAMA_KNOWN_MODELS.to_vec(),
|
||||||
OLLAMA_DOC_URL,
|
OLLAMA_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("OLLAMA_HOST", true, false, Some(OLLAMA_HOST)),
|
ConfigKey::new("OLLAMA_HOST", true, false, Some(OLLAMA_HOST), true),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"OLLAMA_TIMEOUT",
|
"OLLAMA_TIMEOUT",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
Some(&(OLLAMA_TIMEOUT.to_string())),
|
Some(&(OLLAMA_TIMEOUT.to_string())),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -285,13 +285,25 @@ impl ProviderDef for OpenAiProvider {
|
|||||||
models,
|
models,
|
||||||
OPEN_AI_DOC_URL,
|
OPEN_AI_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("OPENAI_API_KEY", false, true, None),
|
ConfigKey::new("OPENAI_API_KEY", false, true, None, true),
|
||||||
ConfigKey::new("OPENAI_HOST", true, false, Some("https://api.openai.com")),
|
ConfigKey::new(
|
||||||
ConfigKey::new("OPENAI_BASE_PATH", true, false, Some("v1/chat/completions")),
|
"OPENAI_HOST",
|
||||||
ConfigKey::new("OPENAI_ORGANIZATION", false, false, None),
|
true,
|
||||||
ConfigKey::new("OPENAI_PROJECT", false, false, None),
|
false,
|
||||||
ConfigKey::new("OPENAI_CUSTOM_HEADERS", false, true, None),
|
Some("https://api.openai.com"),
|
||||||
ConfigKey::new("OPENAI_TIMEOUT", false, false, Some("600")),
|
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_KNOWN_MODELS.to_vec(),
|
||||||
OPENROUTER_DOC_URL,
|
OPENROUTER_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("OPENROUTER_API_KEY", true, true, None),
|
ConfigKey::new("OPENROUTER_API_KEY", true, true, None, true),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"OPENROUTER_HOST",
|
"OPENROUTER_HOST",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
Some("https://openrouter.ai"),
|
Some("https://openrouter.ai"),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -109,8 +109,13 @@ impl ProviderRegistry {
|
|||||||
config_keys.remove(api_key_index);
|
config_keys.remove(api_key_index);
|
||||||
} else if !config.api_key_env.is_empty() {
|
} else if !config.api_key_env.is_empty() {
|
||||||
let api_key_required = provider_type == ProviderType::Declarative;
|
let api_key_required = provider_type == ProviderType::Declarative;
|
||||||
config_keys[api_key_index] =
|
config_keys[api_key_index] = super::base::ConfigKey::new(
|
||||||
super::base::ConfigKey::new(&config.api_key_env, api_key_required, true, None);
|
&config.api_key_env,
|
||||||
|
api_key_required,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -284,9 +284,9 @@ impl ProviderDef for SageMakerTgiProvider {
|
|||||||
vec![SAGEMAKER_TGI_DEFAULT_MODEL],
|
vec![SAGEMAKER_TGI_DEFAULT_MODEL],
|
||||||
SAGEMAKER_TGI_DOC_LINK,
|
SAGEMAKER_TGI_DOC_LINK,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("SAGEMAKER_ENDPOINT_NAME", false, false, None),
|
ConfigKey::new("SAGEMAKER_ENDPOINT_NAME", true, false, None, true),
|
||||||
ConfigKey::new("AWS_REGION", true, false, Some("us-east-1")),
|
ConfigKey::new("AWS_REGION", true, false, Some("us-east-1"), true),
|
||||||
ConfigKey::new("AWS_PROFILE", true, false, Some("default")),
|
ConfigKey::new("AWS_PROFILE", true, false, Some("default"), true),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -309,8 +309,8 @@ impl ProviderDef for SnowflakeProvider {
|
|||||||
SNOWFLAKE_KNOWN_MODELS.to_vec(),
|
SNOWFLAKE_KNOWN_MODELS.to_vec(),
|
||||||
SNOWFLAKE_DOC_URL,
|
SNOWFLAKE_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("SNOWFLAKE_HOST", true, false, None),
|
ConfigKey::new("SNOWFLAKE_HOST", true, false, None, true),
|
||||||
ConfigKey::new("SNOWFLAKE_TOKEN", true, true, None),
|
ConfigKey::new("SNOWFLAKE_TOKEN", true, true, None, true),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,12 +75,13 @@ impl ProviderDef for TetrateProvider {
|
|||||||
TETRATE_KNOWN_MODELS.to_vec(),
|
TETRATE_KNOWN_MODELS.to_vec(),
|
||||||
TETRATE_DOC_URL,
|
TETRATE_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("TETRATE_API_KEY", true, true, None),
|
ConfigKey::new("TETRATE_API_KEY", true, true, None, true),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"TETRATE_HOST",
|
"TETRATE_HOST",
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
Some("https://api.router.tetrate.ai"),
|
Some("https://api.router.tetrate.ai"),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -205,19 +205,21 @@ impl ProviderDef for VeniceProvider {
|
|||||||
FALLBACK_MODELS.to_vec(),
|
FALLBACK_MODELS.to_vec(),
|
||||||
VENICE_DOC_URL,
|
VENICE_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("VENICE_API_KEY", true, true, None),
|
ConfigKey::new("VENICE_API_KEY", true, true, None, true),
|
||||||
ConfigKey::new("VENICE_HOST", true, false, Some(VENICE_DEFAULT_HOST)),
|
ConfigKey::new("VENICE_HOST", true, false, Some(VENICE_DEFAULT_HOST), false),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"VENICE_BASE_PATH",
|
"VENICE_BASE_PATH",
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
Some(VENICE_DEFAULT_BASE_PATH),
|
Some(VENICE_DEFAULT_BASE_PATH),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
ConfigKey::new(
|
ConfigKey::new(
|
||||||
"VENICE_MODELS_PATH",
|
"VENICE_MODELS_PATH",
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
Some(VENICE_DEFAULT_MODELS_PATH),
|
Some(VENICE_DEFAULT_MODELS_PATH),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ impl ProviderDef for XaiProvider {
|
|||||||
XAI_KNOWN_MODELS.to_vec(),
|
XAI_KNOWN_MODELS.to_vec(),
|
||||||
XAI_DOC_URL,
|
XAI_DOC_URL,
|
||||||
vec![
|
vec![
|
||||||
ConfigKey::new("XAI_API_KEY", true, true, None),
|
ConfigKey::new("XAI_API_KEY", true, true, None, true),
|
||||||
ConfigKey::new("XAI_HOST", false, false, Some(XAI_API_HOST)),
|
ConfigKey::new("XAI_HOST", false, false, Some(XAI_API_HOST), false),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3588,6 +3588,10 @@
|
|||||||
"type": "boolean",
|
"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"
|
"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": {
|
"required": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether this key is required for the provider to function"
|
"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
|
* When true, the provider's configure_oauth() method will be called instead of prompting for manual input
|
||||||
*/
|
*/
|
||||||
oauth_flow: boolean;
|
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
|
* 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 [error, setError] = useState<string | null>(null);
|
||||||
const [isOAuthLoading, setIsOAuthLoading] = useState(false);
|
const [isOAuthLoading, setIsOAuthLoading] = useState(false);
|
||||||
|
|
||||||
const requiredParameters = provider.metadata.config_keys.filter(
|
let primaryParameters = provider.metadata.config_keys.filter((param) => param.primary);
|
||||||
(param) => param.required === true
|
if (primaryParameters.length === 0) {
|
||||||
);
|
primaryParameters = provider.metadata.config_keys;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this provider uses OAuth for configuration
|
// Check if this provider uses OAuth for configuration
|
||||||
const isOAuthProvider = provider.metadata.config_keys.some((key) => key.oauth_flow);
|
const isOAuthProvider = provider.metadata.config_keys.some((key) => key.oauth_flow);
|
||||||
@@ -238,7 +239,7 @@ export default function ProviderConfigurationModal({
|
|||||||
validationErrors={validationErrors}
|
validationErrors={validationErrors}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{requiredParameters.length > 0 &&
|
{primaryParameters.length > 0 &&
|
||||||
provider.metadata.config_keys &&
|
provider.metadata.config_keys &&
|
||||||
provider.metadata.config_keys.length > 0 && <SecureStorageNotice />}
|
provider.metadata.config_keys.length > 0 && <SecureStorageNotice />}
|
||||||
</>
|
</>
|
||||||
@@ -260,7 +261,7 @@ export default function ProviderConfigurationModal({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ProviderSetupActions
|
<ProviderSetupActions
|
||||||
requiredParameters={requiredParameters}
|
primaryParameters={primaryParameters}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSubmit={handleSubmitForm}
|
onSubmit={handleSubmitForm}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
|
|||||||
+3
-3
@@ -12,7 +12,7 @@ interface ProviderSetupActionsProps {
|
|||||||
onCancelDelete?: () => void;
|
onCancelDelete?: () => void;
|
||||||
canDelete?: boolean;
|
canDelete?: boolean;
|
||||||
providerName?: string;
|
providerName?: string;
|
||||||
requiredParameters?: ConfigKey[];
|
primaryParameters?: ConfigKey[];
|
||||||
isActiveProvider?: boolean; // Made optional with default false
|
isActiveProvider?: boolean; // Made optional with default false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ export default function ProviderSetupActions({
|
|||||||
onCancelDelete,
|
onCancelDelete,
|
||||||
canDelete,
|
canDelete,
|
||||||
providerName,
|
providerName,
|
||||||
requiredParameters,
|
primaryParameters,
|
||||||
isActiveProvider = false, // Default value provided
|
isActiveProvider = false, // Default value provided
|
||||||
}: ProviderSetupActionsProps) {
|
}: ProviderSetupActionsProps) {
|
||||||
// If we're showing delete confirmation, render the delete confirmation buttons
|
// 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
|
<Trash2 className="h-4 w-4 mr-2" /> Delete Provider
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{requiredParameters && requiredParameters.length > 0 ? (
|
{primaryParameters && primaryParameters.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
+5
-4
@@ -168,10 +168,11 @@ export default function DefaultProviderSetupForm({
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let aboveFoldParameters = parameters.filter((p) => p.required);
|
let aboveFoldParameters = parameters.filter((p) => p.primary);
|
||||||
let belowFoldParameters = parameters.filter((p) => !p.required);
|
let belowFoldParameters = parameters.filter((p) => !p.primary);
|
||||||
if (aboveFoldParameters.length === 0) {
|
|
||||||
aboveFoldParameters = belowFoldParameters;
|
if (aboveFoldParameters.length === 0 && parameters.length > 0) {
|
||||||
|
aboveFoldParameters = parameters;
|
||||||
belowFoldParameters = [];
|
belowFoldParameters = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user