feat: add requires_auth flag for custom providers without authentication (#6705)
Signed-off-by: rabi <ramishra@redhat.com>
This commit is contained in:
@@ -5,7 +5,9 @@ use goose::agents::extension::ToolInfo;
|
||||
use goose::agents::extension_manager::get_parameter_names;
|
||||
use goose::agents::Agent;
|
||||
use goose::agents::{extension::Envs, ExtensionConfig};
|
||||
use goose::config::declarative_providers::{create_custom_provider, remove_custom_provider};
|
||||
use goose::config::declarative_providers::{
|
||||
create_custom_provider, remove_custom_provider, CreateCustomProviderParams,
|
||||
};
|
||||
use goose::config::extensions::{
|
||||
get_all_extension_names, get_all_extensions, get_enabled_extensions, get_extension_by_name,
|
||||
name_to_key, remove_extension, set_extension, set_extension_enabled,
|
||||
@@ -1864,11 +1866,16 @@ fn add_provider() -> anyhow::Result<()> {
|
||||
})
|
||||
.interact()?;
|
||||
|
||||
let api_key: String = cliclack::password("API key:")
|
||||
.allow_empty()
|
||||
.mask('▪')
|
||||
let requires_auth = cliclack::confirm("Does this provider require authentication?")
|
||||
.initial_value(true)
|
||||
.interact()?;
|
||||
|
||||
let api_key: String = if requires_auth {
|
||||
cliclack::password("API key:").mask('▪').interact()?
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let models_input: String = cliclack::input("Available models (separate with commas):")
|
||||
.placeholder("model-a, model-b, model-c")
|
||||
.validate(|input: &String| {
|
||||
@@ -1897,15 +1904,16 @@ fn add_provider() -> anyhow::Result<()> {
|
||||
None
|
||||
};
|
||||
|
||||
create_custom_provider(
|
||||
provider_type,
|
||||
display_name.clone(),
|
||||
create_custom_provider(CreateCustomProviderParams {
|
||||
engine: provider_type.to_string(),
|
||||
display_name: display_name.clone(),
|
||||
api_url,
|
||||
api_key,
|
||||
models,
|
||||
Some(supports_streaming),
|
||||
supports_streaming: Some(supports_streaming),
|
||||
headers,
|
||||
)?;
|
||||
requires_auth,
|
||||
})?;
|
||||
|
||||
cliclack::outro(format!("Custom provider added: {}", display_name))?;
|
||||
Ok(())
|
||||
|
||||
@@ -93,6 +93,12 @@ pub struct UpdateCustomProviderRequest {
|
||||
pub models: Vec<String>,
|
||||
pub supports_streaming: Option<bool>,
|
||||
pub headers: Option<std::collections::HashMap<String, String>>,
|
||||
#[serde(default = "default_requires_auth")]
|
||||
pub requires_auth: bool,
|
||||
}
|
||||
|
||||
fn default_requires_auth() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
@@ -699,13 +705,16 @@ pub async fn create_custom_provider(
|
||||
Json(request): Json<UpdateCustomProviderRequest>,
|
||||
) -> Result<Json<String>, StatusCode> {
|
||||
let config = goose::config::declarative_providers::create_custom_provider(
|
||||
&request.engine,
|
||||
request.display_name,
|
||||
request.api_url,
|
||||
request.api_key,
|
||||
request.models,
|
||||
request.supports_streaming,
|
||||
request.headers,
|
||||
goose::config::declarative_providers::CreateCustomProviderParams {
|
||||
engine: request.engine,
|
||||
display_name: request.display_name,
|
||||
api_url: request.api_url,
|
||||
api_key: request.api_key,
|
||||
models: request.models,
|
||||
supports_streaming: request.supports_streaming,
|
||||
headers: request.headers,
|
||||
requires_auth: request.requires_auth,
|
||||
},
|
||||
)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
@@ -769,13 +778,17 @@ pub async fn update_custom_provider(
|
||||
Json(request): Json<UpdateCustomProviderRequest>,
|
||||
) -> Result<Json<String>, StatusCode> {
|
||||
goose::config::declarative_providers::update_custom_provider(
|
||||
&id,
|
||||
&request.engine,
|
||||
request.display_name,
|
||||
request.api_url,
|
||||
request.api_key,
|
||||
request.models,
|
||||
request.supports_streaming,
|
||||
goose::config::declarative_providers::UpdateCustomProviderParams {
|
||||
id: id.clone(),
|
||||
engine: request.engine,
|
||||
display_name: request.display_name,
|
||||
api_url: request.api_url,
|
||||
api_key: request.api_key,
|
||||
models: request.models,
|
||||
supports_streaming: request.supports_streaming,
|
||||
headers: request.headers,
|
||||
requires_auth: request.requires_auth,
|
||||
},
|
||||
)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
|
||||
@@ -96,9 +96,20 @@ pub fn check_provider_configured(metadata: &ProviderMetadata, provider_type: Pro
|
||||
|
||||
if provider_type == ProviderType::Custom || provider_type == ProviderType::Declarative {
|
||||
if let Ok(loaded_provider) = load_provider(metadata.name.as_str()) {
|
||||
return config
|
||||
.get_secret::<String>(&loaded_provider.config.api_key_env)
|
||||
.is_ok();
|
||||
if !loaded_provider.config.requires_auth {
|
||||
return true;
|
||||
}
|
||||
|
||||
if !loaded_provider.config.api_key_env.is_empty() {
|
||||
let api_key_result =
|
||||
config.get_secret::<String>(&loaded_provider.config.api_key_env);
|
||||
if api_key_result.is_ok() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom providers with config files are intentionally created
|
||||
return provider_type == ProviderType::Custom;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,12 +33,19 @@ pub struct DeclarativeProviderConfig {
|
||||
pub engine: ProviderEngine,
|
||||
pub display_name: String,
|
||||
pub description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub api_key_env: String,
|
||||
pub base_url: String,
|
||||
pub models: Vec<ModelInfo>,
|
||||
pub headers: Option<HashMap<String, String>>,
|
||||
pub timeout_seconds: Option<u64>,
|
||||
pub supports_streaming: Option<bool>,
|
||||
#[serde(default = "default_requires_auth")]
|
||||
pub requires_auth: bool,
|
||||
}
|
||||
|
||||
fn default_requires_auth() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
impl DeclarativeProviderConfig {
|
||||
@@ -85,42 +92,68 @@ pub fn generate_api_key_name(id: &str) -> String {
|
||||
format!("{}_API_KEY", id.to_uppercase())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CreateCustomProviderParams {
|
||||
pub engine: String,
|
||||
pub display_name: String,
|
||||
pub api_url: String,
|
||||
pub api_key: String,
|
||||
pub models: Vec<String>,
|
||||
pub supports_streaming: Option<bool>,
|
||||
pub headers: Option<HashMap<String, String>>,
|
||||
pub requires_auth: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UpdateCustomProviderParams {
|
||||
pub id: String,
|
||||
pub engine: String,
|
||||
pub display_name: String,
|
||||
pub api_url: String,
|
||||
pub api_key: String,
|
||||
pub models: Vec<String>,
|
||||
pub supports_streaming: Option<bool>,
|
||||
pub headers: Option<HashMap<String, String>>,
|
||||
pub requires_auth: bool,
|
||||
}
|
||||
|
||||
pub fn create_custom_provider(
|
||||
engine: &str,
|
||||
display_name: String,
|
||||
api_url: String,
|
||||
api_key: String,
|
||||
models: Vec<String>,
|
||||
supports_streaming: Option<bool>,
|
||||
headers: Option<HashMap<String, String>>,
|
||||
params: CreateCustomProviderParams,
|
||||
) -> Result<DeclarativeProviderConfig> {
|
||||
let id = generate_id(&display_name);
|
||||
let api_key_name = generate_api_key_name(&id);
|
||||
let id = generate_id(¶ms.display_name);
|
||||
|
||||
let config = Config::global();
|
||||
config.set_secret(&api_key_name, &api_key)?;
|
||||
let api_key_env = if params.requires_auth {
|
||||
let api_key_name = generate_api_key_name(&id);
|
||||
let config = Config::global();
|
||||
config.set_secret(&api_key_name, ¶ms.api_key)?;
|
||||
api_key_name
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let model_infos: Vec<ModelInfo> = models
|
||||
let model_infos: Vec<ModelInfo> = params
|
||||
.models
|
||||
.into_iter()
|
||||
.map(|name| ModelInfo::new(name, 128000))
|
||||
.collect();
|
||||
|
||||
let provider_config = DeclarativeProviderConfig {
|
||||
name: id.clone(),
|
||||
engine: match engine {
|
||||
engine: match params.engine.as_str() {
|
||||
"openai_compatible" => ProviderEngine::OpenAI,
|
||||
"anthropic_compatible" => ProviderEngine::Anthropic,
|
||||
"ollama_compatible" => ProviderEngine::Ollama,
|
||||
_ => return Err(anyhow::anyhow!("Invalid provider type: {}", engine)),
|
||||
_ => return Err(anyhow::anyhow!("Invalid provider type: {}", params.engine)),
|
||||
},
|
||||
display_name: display_name.clone(),
|
||||
description: Some(format!("Custom {} provider", display_name)),
|
||||
api_key_env: api_key_name,
|
||||
base_url: api_url,
|
||||
display_name: params.display_name.clone(),
|
||||
description: Some(format!("Custom {} provider", params.display_name)),
|
||||
api_key_env,
|
||||
base_url: params.api_url,
|
||||
models: model_infos,
|
||||
headers,
|
||||
headers: params.headers,
|
||||
timeout_seconds: None,
|
||||
supports_streaming,
|
||||
supports_streaming: params.supports_streaming,
|
||||
requires_auth: params.requires_auth,
|
||||
};
|
||||
|
||||
let custom_providers_dir = custom_providers_dir();
|
||||
@@ -133,49 +166,54 @@ pub fn create_custom_provider(
|
||||
Ok(provider_config)
|
||||
}
|
||||
|
||||
pub fn update_custom_provider(
|
||||
id: &str,
|
||||
provider_type: &str,
|
||||
display_name: String,
|
||||
api_url: String,
|
||||
api_key: String,
|
||||
models: Vec<String>,
|
||||
supports_streaming: Option<bool>,
|
||||
) -> Result<()> {
|
||||
let loaded_provider = load_provider(id)?;
|
||||
pub fn update_custom_provider(params: UpdateCustomProviderParams) -> Result<()> {
|
||||
let loaded_provider = load_provider(¶ms.id)?;
|
||||
let existing_config = loaded_provider.config;
|
||||
let editable = loaded_provider.is_editable;
|
||||
|
||||
let config = Config::global();
|
||||
if !api_key.is_empty() {
|
||||
config.set_secret(&existing_config.api_key_env, &api_key)?;
|
||||
}
|
||||
|
||||
let api_key_env = if params.requires_auth {
|
||||
let api_key_name = if existing_config.api_key_env.is_empty() {
|
||||
generate_api_key_name(¶ms.id)
|
||||
} else {
|
||||
existing_config.api_key_env.clone()
|
||||
};
|
||||
if !params.api_key.is_empty() {
|
||||
config.set_secret(&api_key_name, ¶ms.api_key)?;
|
||||
}
|
||||
api_key_name
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
if editable {
|
||||
let model_infos: Vec<ModelInfo> = models
|
||||
let model_infos: Vec<ModelInfo> = params
|
||||
.models
|
||||
.into_iter()
|
||||
.map(|name| ModelInfo::new(name, 128000))
|
||||
.collect();
|
||||
|
||||
let updated_config = DeclarativeProviderConfig {
|
||||
name: id.to_string(),
|
||||
engine: match provider_type {
|
||||
name: params.id.clone(),
|
||||
engine: match params.engine.as_str() {
|
||||
"openai_compatible" => ProviderEngine::OpenAI,
|
||||
"anthropic_compatible" => ProviderEngine::Anthropic,
|
||||
"ollama_compatible" => ProviderEngine::Ollama,
|
||||
_ => return Err(anyhow::anyhow!("Invalid provider type: {}", provider_type)),
|
||||
_ => return Err(anyhow::anyhow!("Invalid provider type: {}", params.engine)),
|
||||
},
|
||||
display_name,
|
||||
display_name: params.display_name,
|
||||
description: existing_config.description,
|
||||
api_key_env: existing_config.api_key_env,
|
||||
base_url: api_url,
|
||||
api_key_env,
|
||||
base_url: params.api_url,
|
||||
models: model_infos,
|
||||
headers: existing_config.headers,
|
||||
headers: params.headers.or(existing_config.headers),
|
||||
timeout_seconds: existing_config.timeout_seconds,
|
||||
supports_streaming,
|
||||
supports_streaming: params.supports_streaming,
|
||||
requires_auth: params.requires_auth,
|
||||
};
|
||||
|
||||
let file_path = custom_providers_dir().join(format!("{}.json", id));
|
||||
let file_path = custom_providers_dir().join(format!("{}.json", updated_config.name));
|
||||
let json_content = serde_json::to_string_pretty(&updated_config)?;
|
||||
std::fs::write(file_path, json_content)?;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ pub struct ApiClient {
|
||||
}
|
||||
|
||||
pub enum AuthMethod {
|
||||
NoAuth,
|
||||
BearerToken(String),
|
||||
ApiKey {
|
||||
header_name: String,
|
||||
@@ -172,6 +173,7 @@ pub struct ApiResponse {
|
||||
impl fmt::Debug for AuthMethod {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
AuthMethod::NoAuth => f.debug_tuple("NoAuth").finish(),
|
||||
AuthMethod::BearerToken(_) => f.debug_tuple("BearerToken").field(&"[hidden]").finish(),
|
||||
AuthMethod::ApiKey { header_name, .. } => f
|
||||
.debug_struct("ApiKey")
|
||||
@@ -390,6 +392,7 @@ impl<'a> ApiRequestBuilder<'a> {
|
||||
request = request.headers(headers);
|
||||
|
||||
request = match &self.client.auth {
|
||||
AuthMethod::NoAuth => request,
|
||||
AuthMethod::BearerToken(token) => {
|
||||
request.header("Authorization", format!("Bearer {}", token))
|
||||
}
|
||||
|
||||
@@ -316,13 +316,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_openai_compatible_providers_config_keys() {
|
||||
let providers_list = providers().await;
|
||||
let cases = vec![
|
||||
("openai", "OPENAI_API_KEY"),
|
||||
let required_api_key_cases = vec![
|
||||
("groq", "GROQ_API_KEY"),
|
||||
("mistral", "MISTRAL_API_KEY"),
|
||||
("custom_deepseek", "DEEPSEEK_API_KEY"),
|
||||
];
|
||||
for (name, expected_key) in cases {
|
||||
for (name, expected_key) in required_api_key_cases {
|
||||
if let Some((meta, _)) = providers_list.iter().find(|(m, _)| m.name == name) {
|
||||
assert!(
|
||||
!meta.config_keys.is_empty(),
|
||||
@@ -346,5 +345,24 @@ mod tests {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((meta, _)) = providers_list.iter().find(|(m, _)| m.name == "openai") {
|
||||
assert!(
|
||||
!meta.config_keys.is_empty(),
|
||||
"openai provider should have config keys"
|
||||
);
|
||||
assert_eq!(
|
||||
meta.config_keys[0].name, "OPENAI_API_KEY",
|
||||
"First config key for openai should be OPENAI_API_KEY"
|
||||
);
|
||||
assert!(
|
||||
!meta.config_keys[0].required,
|
||||
"OPENAI_API_KEY should be optional for local server support"
|
||||
);
|
||||
assert!(
|
||||
meta.config_keys[0].secret,
|
||||
"OPENAI_API_KEY should be secret"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ impl LiteLLMProvider {
|
||||
let timeout_secs: u64 = config.get_param("LITELLM_TIMEOUT").unwrap_or(600);
|
||||
|
||||
let auth = if api_key.is_empty() {
|
||||
AuthMethod::Custom(Box::new(NoAuth))
|
||||
AuthMethod::NoAuth
|
||||
} else {
|
||||
AuthMethod::BearerToken(api_key)
|
||||
};
|
||||
@@ -129,17 +129,6 @@ impl LiteLLMProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// No authentication provider for LiteLLM when API key is not provided
|
||||
struct NoAuth;
|
||||
|
||||
#[async_trait]
|
||||
impl super::api_client::AuthProvider for NoAuth {
|
||||
async fn get_auth_header(&self) -> Result<(String, String)> {
|
||||
// Return a dummy header that won't be used
|
||||
Ok(("X-No-Auth".to_string(), "true".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Provider for LiteLLMProvider {
|
||||
fn metadata() -> ProviderMetadata {
|
||||
|
||||
@@ -71,8 +71,8 @@ impl OllamaProvider {
|
||||
.map_err(|_| anyhow::anyhow!("Failed to set default port"))?;
|
||||
}
|
||||
|
||||
let auth = AuthMethod::Custom(Box::new(NoAuth));
|
||||
let api_client = ApiClient::with_timeout(base_url.to_string(), auth, timeout)?;
|
||||
let api_client =
|
||||
ApiClient::with_timeout(base_url.to_string(), AuthMethod::NoAuth, timeout)?;
|
||||
|
||||
Ok(Self {
|
||||
api_client,
|
||||
@@ -108,8 +108,8 @@ impl OllamaProvider {
|
||||
.map_err(|_| anyhow::anyhow!("Failed to set default port"))?;
|
||||
}
|
||||
|
||||
let auth = AuthMethod::Custom(Box::new(NoAuth));
|
||||
let api_client = ApiClient::with_timeout(base_url.to_string(), auth, timeout)?;
|
||||
let api_client =
|
||||
ApiClient::with_timeout(base_url.to_string(), AuthMethod::NoAuth, timeout)?;
|
||||
|
||||
Ok(Self {
|
||||
api_client,
|
||||
@@ -132,15 +132,6 @@ impl OllamaProvider {
|
||||
}
|
||||
}
|
||||
|
||||
struct NoAuth;
|
||||
|
||||
#[async_trait]
|
||||
impl super::api_client::AuthProvider for NoAuth {
|
||||
async fn get_auth_header(&self) -> Result<(String, String)> {
|
||||
Ok(("X-No-Auth".to_string(), "true".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Provider for OllamaProvider {
|
||||
fn metadata() -> ProviderMetadata {
|
||||
|
||||
@@ -67,23 +67,27 @@ impl OpenAiProvider {
|
||||
let model = model.with_fast(OPEN_AI_DEFAULT_FAST_MODEL.to_string());
|
||||
|
||||
let config = crate::config::Config::global();
|
||||
let secrets = config.get_secrets("OPENAI_API_KEY", &["OPENAI_CUSTOM_HEADERS"])?;
|
||||
let api_key = secrets.get("OPENAI_API_KEY").unwrap().clone();
|
||||
let host: String = config
|
||||
.get_param("OPENAI_HOST")
|
||||
.unwrap_or_else(|_| "https://api.openai.com".to_string());
|
||||
|
||||
let api_key: Option<String> = config.get_secret("OPENAI_API_KEY").ok();
|
||||
let custom_headers: Option<HashMap<String, String>> = config
|
||||
.get_secret::<String>("OPENAI_CUSTOM_HEADERS")
|
||||
.ok()
|
||||
.map(parse_custom_headers);
|
||||
|
||||
let base_path: String = config
|
||||
.get_param("OPENAI_BASE_PATH")
|
||||
.unwrap_or_else(|_| "v1/chat/completions".to_string());
|
||||
let organization: Option<String> = config.get_param("OPENAI_ORGANIZATION").ok();
|
||||
let project: Option<String> = config.get_param("OPENAI_PROJECT").ok();
|
||||
let custom_headers: Option<HashMap<String, String>> = secrets
|
||||
.get("OPENAI_CUSTOM_HEADERS")
|
||||
.cloned()
|
||||
.map(parse_custom_headers);
|
||||
let timeout_secs: u64 = config.get_param("OPENAI_TIMEOUT").unwrap_or(600);
|
||||
|
||||
let auth = AuthMethod::BearerToken(api_key);
|
||||
let auth = match api_key {
|
||||
Some(key) if !key.is_empty() => AuthMethod::BearerToken(key),
|
||||
_ => AuthMethod::NoAuth,
|
||||
};
|
||||
let mut api_client =
|
||||
ApiClient::with_timeout(host, auth, std::time::Duration::from_secs(timeout_secs))?;
|
||||
|
||||
@@ -136,9 +140,12 @@ impl OpenAiProvider {
|
||||
config: DeclarativeProviderConfig,
|
||||
) -> Result<Self> {
|
||||
let global_config = crate::config::Config::global();
|
||||
let api_key: String = global_config
|
||||
.get_secret(&config.api_key_env)
|
||||
.map_err(|_e| anyhow::anyhow!("Missing API key: {}", config.api_key_env))?;
|
||||
|
||||
let api_key: Option<String> = if config.requires_auth && !config.api_key_env.is_empty() {
|
||||
global_config.get_secret(&config.api_key_env).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let url = url::Url::parse(&config.base_url)
|
||||
.map_err(|e| anyhow::anyhow!("Invalid base URL '{}': {}", config.base_url, e))?;
|
||||
@@ -161,7 +168,11 @@ impl OpenAiProvider {
|
||||
};
|
||||
|
||||
let timeout_secs = config.timeout_seconds.unwrap_or(600);
|
||||
let auth = AuthMethod::BearerToken(api_key);
|
||||
|
||||
let auth = match api_key {
|
||||
Some(key) if !key.is_empty() => AuthMethod::BearerToken(key),
|
||||
_ => AuthMethod::NoAuth,
|
||||
};
|
||||
let mut api_client =
|
||||
ApiClient::with_timeout(host, auth, std::time::Duration::from_secs(timeout_secs))?;
|
||||
|
||||
@@ -232,7 +243,7 @@ impl Provider for OpenAiProvider {
|
||||
models,
|
||||
OPEN_AI_DOC_URL,
|
||||
vec![
|
||||
ConfigKey::new("OPENAI_API_KEY", true, true, None),
|
||||
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),
|
||||
|
||||
@@ -98,12 +98,14 @@ impl ProviderRegistry {
|
||||
|
||||
let mut config_keys = base_metadata.config_keys.clone();
|
||||
|
||||
if let Some(api_key_index) = config_keys
|
||||
.iter()
|
||||
.position(|key| key.required && key.secret)
|
||||
{
|
||||
config_keys[api_key_index] =
|
||||
super::base::ConfigKey::new(&config.api_key_env, true, true, None);
|
||||
if let Some(api_key_index) = config_keys.iter().position(|key| key.secret) {
|
||||
if !config.requires_auth {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
let custom_metadata = ProviderMetadata {
|
||||
|
||||
Reference in New Issue
Block a user