Custom providers update (#4099)
Co-authored-by: developerayo <shodipovi@gmail.com> Co-authored-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Zane Staggs <zane@squareup.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use crate::recipes::github_recipe::GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY;
|
||||
use cliclack::spinner;
|
||||
use console::style;
|
||||
use goose::agents::extension::ToolInfo;
|
||||
@@ -7,6 +8,7 @@ use goose::agents::platform_tools::{
|
||||
};
|
||||
use goose::agents::Agent;
|
||||
use goose::agents::{extension::Envs, ExtensionConfig};
|
||||
use goose::config::custom_providers::CustomProviderConfig;
|
||||
use goose::config::extensions::name_to_key;
|
||||
use goose::config::permission::PermissionLevel;
|
||||
use goose::config::{
|
||||
@@ -14,6 +16,7 @@ use goose::config::{
|
||||
PermissionManager,
|
||||
};
|
||||
use goose::conversation::message::Message;
|
||||
use goose::model::ModelConfig;
|
||||
use goose::providers::{create, providers};
|
||||
use rmcp::model::{Tool, ToolAnnotations};
|
||||
use rmcp::object;
|
||||
@@ -21,8 +24,6 @@ use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::recipes::github_recipe::GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY;
|
||||
|
||||
// useful for light themes where there is no dicernible colour contrast between
|
||||
// cursor-selected and cursor-unselected items.
|
||||
const MULTISELECT_VISIBILITY_HINT: &str = "<";
|
||||
@@ -221,6 +222,11 @@ pub async fn handle_configure() -> Result<(), Box<dyn Error>> {
|
||||
"Configure Providers",
|
||||
"Change provider or update credentials",
|
||||
)
|
||||
.item(
|
||||
"custom_providers",
|
||||
"Custom Providers",
|
||||
"Add custom provider with compatible API",
|
||||
)
|
||||
.item("add", "Add Extension", "Connect to a new extension")
|
||||
.item(
|
||||
"toggle",
|
||||
@@ -241,6 +247,7 @@ pub async fn handle_configure() -> Result<(), Box<dyn Error>> {
|
||||
"remove" => remove_extension_dialog(),
|
||||
"settings" => configure_settings_dialog().await.and(Ok(())),
|
||||
"providers" => configure_provider_dialog().await.and(Ok(())),
|
||||
"custom_providers" => configure_custom_provider_dialog(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -250,10 +257,7 @@ pub async fn handle_configure() -> Result<(), Box<dyn Error>> {
|
||||
async fn handle_oauth_configuration(
|
||||
provider_name: &str,
|
||||
key_name: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use goose::model::ModelConfig;
|
||||
use goose::providers::create;
|
||||
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let _ = cliclack::log::info(format!(
|
||||
"Configuring {} using OAuth device code flow...",
|
||||
key_name
|
||||
@@ -279,8 +283,7 @@ async fn handle_oauth_configuration(
|
||||
}
|
||||
}
|
||||
|
||||
/// Interactive model search that truncates the list to improve UX
|
||||
fn interactive_model_search(models: &[String]) -> Result<String, Box<dyn std::error::Error>> {
|
||||
fn interactive_model_search(models: &[String]) -> Result<String, Box<dyn Error>> {
|
||||
const MAX_VISIBLE: usize = 30;
|
||||
let mut query = String::new();
|
||||
|
||||
@@ -553,7 +556,7 @@ pub async fn configure_provider_dialog() -> Result<bool, Box<dyn Error>> {
|
||||
let spin = spinner();
|
||||
spin.start("Attempting to fetch supported models...");
|
||||
let models_res = {
|
||||
let temp_model_config = goose::model::ModelConfig::new(&provider_meta.default_model)?;
|
||||
let temp_model_config = ModelConfig::new(&provider_meta.default_model)?;
|
||||
let temp_provider = create(provider_name, temp_model_config)?;
|
||||
temp_provider.fetch_supported_models().await
|
||||
};
|
||||
@@ -585,7 +588,7 @@ pub async fn configure_provider_dialog() -> Result<bool, Box<dyn Error>> {
|
||||
.map(|val| val == "1" || val.to_lowercase() == "true")
|
||||
.unwrap_or(false);
|
||||
|
||||
let model_config = goose::model::ModelConfig::new(&model)?
|
||||
let model_config = ModelConfig::new(&model)?
|
||||
.with_max_tokens(Some(50))
|
||||
.with_toolshim(toolshim_enabled)
|
||||
.with_toolshim_model(std::env::var("GOOSE_TOOLSHIM_OLLAMA_MODEL").ok());
|
||||
@@ -1429,7 +1432,7 @@ pub async fn configure_tool_permissions_dialog() -> Result<(), Box<dyn Error>> {
|
||||
let model: String = config
|
||||
.get_param("GOOSE_MODEL")
|
||||
.expect("No model configured. Please set model first");
|
||||
let model_config = goose::model::ModelConfig::new(&model)?;
|
||||
let model_config = ModelConfig::new(&model)?;
|
||||
|
||||
// Create the agent
|
||||
let agent = Agent::new();
|
||||
@@ -1569,7 +1572,6 @@ fn configure_recipe_dialog() -> Result<(), Box<dyn Error>> {
|
||||
recipe_repo_input = recipe_repo_input.default_input(&recipe_repo);
|
||||
}
|
||||
let input_value: String = recipe_repo_input.interact()?;
|
||||
// if input is blank, it clears the recipe github repo settings in the config file
|
||||
if input_value.clone().trim().is_empty() {
|
||||
config.delete(key_name)?;
|
||||
} else {
|
||||
@@ -1767,3 +1769,128 @@ pub async fn handle_openrouter_auth() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_provider() -> Result<(), Box<dyn Error>> {
|
||||
let provider_type = cliclack::select("What type of API is this?")
|
||||
.item(
|
||||
"openai_compatible",
|
||||
"OpenAI Compatible",
|
||||
"Uses OpenAI API format",
|
||||
)
|
||||
.item(
|
||||
"anthropic_compatible",
|
||||
"Anthropic Compatible",
|
||||
"Uses Anthropic API format",
|
||||
)
|
||||
.item(
|
||||
"ollama_compatible",
|
||||
"Ollama Compatible",
|
||||
"Uses Ollama API format",
|
||||
)
|
||||
.interact()?;
|
||||
|
||||
let display_name: String = cliclack::input("What should we call this provider?")
|
||||
.placeholder("Your Provider Name")
|
||||
.validate(|input: &String| {
|
||||
if input.is_empty() {
|
||||
Err("Please enter a name")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.interact()?;
|
||||
|
||||
let api_url: String = cliclack::input("Provider API URL:")
|
||||
.placeholder("https://api.example.com/v1/messages")
|
||||
.validate(|input: &String| {
|
||||
if !input.starts_with("http://") && !input.starts_with("https://") {
|
||||
Err("URL must start with either http:// or https://")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.interact()?;
|
||||
|
||||
let api_key: String = cliclack::password("API key:").mask('▪').interact()?;
|
||||
|
||||
let models_input: String = cliclack::input("Available models (seperate with commas):")
|
||||
.placeholder("model-a, model-b, model-c")
|
||||
.validate(|input: &String| {
|
||||
if input.trim().is_empty() {
|
||||
Err("Please enter at least one model name")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.interact()?;
|
||||
|
||||
let models: Vec<String> = models_input
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
|
||||
let supports_streaming = cliclack::confirm("Does this provider support streaming responses?")
|
||||
.initial_value(true)
|
||||
.interact()?;
|
||||
|
||||
CustomProviderConfig::create_and_save(
|
||||
provider_type,
|
||||
display_name.clone(),
|
||||
api_url,
|
||||
api_key,
|
||||
models,
|
||||
Some(supports_streaming),
|
||||
)?;
|
||||
|
||||
cliclack::outro(format!("Custom provider added: {}", display_name))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_provider() -> Result<(), Box<dyn Error>> {
|
||||
let custom_providers_dir = goose::config::custom_providers::custom_providers_dir();
|
||||
let custom_providers = if custom_providers_dir.exists() {
|
||||
goose::config::custom_providers::load_custom_providers(&custom_providers_dir)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
if custom_providers.is_empty() {
|
||||
cliclack::outro("No custom providers added just yet.")?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let provider_items: Vec<_> = custom_providers
|
||||
.iter()
|
||||
.map(|p| (p.name.as_str(), p.display_name.as_str(), "Custom provider"))
|
||||
.collect();
|
||||
|
||||
let selected_id = cliclack::select("Which custom provider would you like to remove?")
|
||||
.items(&provider_items)
|
||||
.interact()?;
|
||||
|
||||
CustomProviderConfig::remove(selected_id)?;
|
||||
cliclack::outro(format!("Removed custom provider: {}", selected_id))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn configure_custom_provider_dialog() -> Result<(), Box<dyn Error>> {
|
||||
let action = cliclack::select("What would you like to do?")
|
||||
.item(
|
||||
"add",
|
||||
"Add A Custom Provider",
|
||||
"Add a new OpenAI/Anthropic/Ollama compatible Provider",
|
||||
)
|
||||
.item(
|
||||
"remove",
|
||||
"Remove Custom Provider",
|
||||
"Remove an existing custom provider",
|
||||
)
|
||||
.interact()?;
|
||||
|
||||
match action {
|
||||
"add" => add_provider(),
|
||||
"remove" => remove_provider(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user