Onboarding detect provider from api key (#5955)

Co-authored-by: spencrmartin <spencermartin@squareup.com>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
Zane
2025-12-05 13:06:52 -08:00
committed by GitHub
parent 6fa3bd7e8a
commit 1db40709bb
18 changed files with 729 additions and 290 deletions
+3
View File
@@ -330,6 +330,7 @@ derive_utoipa!(Icon as IconSchema);
super::routes::status::diagnostics,
super::routes::mcp_ui_proxy::mcp_ui_proxy,
super::routes::config_management::backup_config,
super::routes::config_management::detect_provider,
super::routes::config_management::recover_config,
super::routes::config_management::validate_config,
super::routes::config_management::init_config,
@@ -398,6 +399,8 @@ derive_utoipa!(Icon as IconSchema);
components(schemas(
super::routes::config_management::UpsertConfigQuery,
super::routes::config_management::ConfigKeyQuery,
super::routes::config_management::DetectProviderRequest,
super::routes::config_management::DetectProviderResponse,
super::routes::config_management::ConfigResponse,
super::routes::config_management::ProvidersResponse,
super::routes::config_management::ProviderDetails,
@@ -11,6 +11,7 @@ use goose::config::paths::Paths;
use goose::config::ExtensionEntry;
use goose::config::{Config, ConfigError};
use goose::model::ModelConfig;
use goose::providers::auto_detect::detect_provider_from_api_key;
use goose::providers::base::{ProviderMetadata, ProviderType};
use goose::providers::create_with_default_model;
use goose::providers::pricing::{
@@ -131,6 +132,16 @@ pub struct SlashCommandsResponse {
pub commands: Vec<SlashCommand>,
}
#[derive(Deserialize, ToSchema)]
pub struct DetectProviderRequest {
pub api_key: String,
}
#[derive(Serialize, ToSchema)]
pub struct DetectProviderResponse {
pub provider_name: String,
pub models: Vec<String>,
}
#[utoipa::path(
post,
path = "/config/upsert",
@@ -596,6 +607,29 @@ pub async fn upsert_permissions(
Ok(Json("Permissions updated successfully".to_string()))
}
#[utoipa::path(
post,
path = "/config/detect-provider",
request_body = DetectProviderRequest,
responses(
(status = 200, description = "Provider detected successfully", body = DetectProviderResponse),
(status = 404, description = "No matching provider found"),
)
)]
pub async fn detect_provider(
Json(detect_request): Json<DetectProviderRequest>,
) -> Result<Json<DetectProviderResponse>, StatusCode> {
let api_key = detect_request.api_key.trim();
match detect_provider_from_api_key(api_key).await {
Some((provider_name, models)) => Ok(Json(DetectProviderResponse {
provider_name,
models,
})),
None => Err(StatusCode::NOT_FOUND),
}
}
#[utoipa::path(
post,
path = "/config/backup",
@@ -686,7 +720,6 @@ pub async fn validate_config() -> Result<Json<String>, StatusCode> {
}
}
}
#[utoipa::path(
post,
path = "/config/custom-providers",
@@ -834,6 +867,7 @@ pub fn routes(state: Arc<AppState>) -> Router {
.route("/config/extensions/{name}", delete(remove_extension))
.route("/config/providers", get(providers))
.route("/config/providers/{name}/models", get(get_provider_models))
.route("/config/detect-provider", post(detect_provider))
.route("/config/slash_commands", get(get_slash_commands))
.route("/config/pricing", post(get_pricing))
.route("/config/init", post(init_config))
+51
View File
@@ -0,0 +1,51 @@
use crate::model::ModelConfig;
pub async fn detect_provider_from_api_key(api_key: &str) -> Option<(String, Vec<String>)> {
let provider_tests = vec![
("anthropic", "ANTHROPIC_API_KEY"),
("openai", "OPENAI_API_KEY"),
("google", "GOOGLE_API_KEY"),
("groq", "GROQ_API_KEY"),
("xai", "XAI_API_KEY"),
// Ollama and OpenRouter don't validate keys, so they would match any input
];
let tasks: Vec<_> = provider_tests
.into_iter()
.map(|(provider_name, env_key)| {
let api_key = api_key.to_string();
tokio::spawn(async move {
let original_value = std::env::var(env_key).ok();
std::env::set_var(env_key, &api_key);
let result = match crate::providers::create(
provider_name,
ModelConfig::new_or_fail("default"),
)
.await
{
Ok(provider) => match provider.fetch_supported_models().await {
Ok(Some(models)) => Some((provider_name.to_string(), models)),
_ => None,
},
Err(_) => None,
};
match original_value {
Some(val) => std::env::set_var(env_key, val),
None => std::env::remove_var(env_key),
}
result
})
})
.collect();
for task in tasks {
if let Ok(Some(result)) = task.await {
return Some(result);
}
}
None
}
+1
View File
@@ -1,5 +1,6 @@
pub mod anthropic;
pub mod api_client;
pub mod auto_detect;
pub mod azure;
pub mod azureauth;
pub mod base;