From 053cb2e26ade782d74e5605fafd8a5bd84610729 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Fri, 3 Jan 2025 15:30:36 +1100 Subject: [PATCH 1/8] don't repeat --- crates/goose-server/src/state.rs | 57 ++------------------------------ 1 file changed, 3 insertions(+), 54 deletions(-) diff --git a/crates/goose-server/src/state.rs b/crates/goose-server/src/state.rs index cec731d4a0a1..2e4f47bd7552 100644 --- a/crates/goose-server/src/state.rs +++ b/crates/goose-server/src/state.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use goose::providers::configs::GroqProviderConfig; use goose::{ agent::Agent, developer::DeveloperSystem, @@ -11,6 +10,8 @@ use std::{env, sync::Arc}; use tokio::sync::Mutex; /// Shared application state +#[allow(dead_code)] +#[derive(Clone)] pub struct AppState { pub provider_config: ProviderConfig, pub agent: Arc>, @@ -59,56 +60,4 @@ impl AppState { secret_key, }) } -} - -// Manual Clone implementation since we know ProviderConfig variants can be cloned -impl Clone for AppState { - fn clone(&self) -> Self { - Self { - provider_config: match &self.provider_config { - ProviderConfig::OpenAi(config) => { - ProviderConfig::OpenAi(goose::providers::configs::OpenAiProviderConfig { - host: config.host.clone(), - api_key: config.api_key.clone(), - model: config.model.clone(), - }) - } - ProviderConfig::Databricks(config) => ProviderConfig::Databricks( - goose::providers::configs::DatabricksProviderConfig { - host: config.host.clone(), - auth: config.auth.clone(), - model: config.model.clone(), - image_format: config.image_format, - }, - ), - ProviderConfig::Ollama(config) => { - ProviderConfig::Ollama(goose::providers::configs::OllamaProviderConfig { - host: config.host.clone(), - model: config.model.clone(), - }) - } - ProviderConfig::Anthropic(config) => { - ProviderConfig::Anthropic(goose::providers::configs::AnthropicProviderConfig { - host: config.host.clone(), - api_key: config.api_key.clone(), - model: config.model.clone(), - }) - } - ProviderConfig::Google(config) => { - ProviderConfig::Google(goose::providers::configs::GoogleProviderConfig { - host: config.host.clone(), - api_key: config.api_key.clone(), - model: config.model.clone(), - }) - } - ProviderConfig::Groq(config) => ProviderConfig::Groq(GroqProviderConfig { - host: config.host.clone(), - api_key: config.api_key.clone(), - model: config.model.clone(), - }), - }, - agent: self.agent.clone(), - secret_key: self.secret_key.clone(), - } - } -} +} \ No newline at end of file From 5c54e8340593fcfe086287a119d1320eedd735bd Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Fri, 3 Jan 2025 16:03:20 +1100 Subject: [PATCH 2/8] tidy up so GUI can run with different models --- crates/goose-server/src/configuration.rs | 46 +++++++++++++++++++++++- crates/goose-server/src/state.rs | 2 +- ui/desktop/src/main.ts | 18 +++++----- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/crates/goose-server/src/configuration.rs b/crates/goose-server/src/configuration.rs index bbece127666e..870b0fba0dad 100644 --- a/crates/goose-server/src/configuration.rs +++ b/crates/goose-server/src/configuration.rs @@ -1,8 +1,11 @@ use crate::error::{to_env_var, ConfigError}; use config::{Config, Environment}; -use goose::providers::configs::{GoogleProviderConfig, GroqProviderConfig}; +use goose::providers::configs::{ + AnthropicProviderConfig, GoogleProviderConfig, GroqProviderConfig, +}; use goose::providers::openai::OPEN_AI_DEFAULT_MODEL; use goose::providers::{ + anthropic, configs::{ DatabricksAuth, DatabricksProviderConfig, ModelConfig, OllamaProviderConfig, OpenAiProviderConfig, ProviderConfig, @@ -108,6 +111,21 @@ pub enum ProviderSettings { #[serde(default)] estimate_factor: Option, }, + Anthropic { + #[serde(default = "default_anthropic_host")] + host: String, + api_key: String, + #[serde(default = "default_anthropic_model")] + model: String, + #[serde(default)] + temperature: Option, + #[serde(default)] + max_tokens: Option, + #[serde(default)] + context_limit: Option, + #[serde(default)] + estimate_factor: Option, + }, } impl ProviderSettings { @@ -120,6 +138,7 @@ impl ProviderSettings { ProviderSettings::Ollama { .. } => ProviderType::Ollama, ProviderSettings::Google { .. } => ProviderType::Google, ProviderSettings::Groq { .. } => ProviderType::Groq, + ProviderSettings::Anthropic { .. } => ProviderType::Anthropic, } } @@ -210,6 +229,23 @@ impl ProviderSettings { .with_context_limit(context_limit) .with_estimate_factor(estimate_factor), }), + ProviderSettings::Anthropic { + host, + api_key, + model, + temperature, + max_tokens, + context_limit, + estimate_factor, + } => ProviderConfig::Anthropic(AnthropicProviderConfig { + host, + api_key, + model: ModelConfig::new(model) + .with_temperature(temperature) + .with_max_tokens(max_tokens) + .with_context_limit(context_limit) + .with_estimate_factor(estimate_factor), + }), } } } @@ -317,6 +353,14 @@ fn default_groq_model() -> String { groq::GROQ_DEFAULT_MODEL.to_string() } +fn default_anthropic_host() -> String { + "https://api.anthropic.com".to_string() +} + +fn default_anthropic_model() -> String { + anthropic::ANTHROPIC_DEFAULT_MODEL.to_string() +} + fn default_image_format() -> ImageFormat { ImageFormat::Anthropic } diff --git a/crates/goose-server/src/state.rs b/crates/goose-server/src/state.rs index 2e4f47bd7552..08a2dd8f5041 100644 --- a/crates/goose-server/src/state.rs +++ b/crates/goose-server/src/state.rs @@ -60,4 +60,4 @@ impl AppState { secret_key, }) } -} \ No newline at end of file +} diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 70536589f881..058132746fdd 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -37,19 +37,19 @@ const checkApiCredentials = () => { loadZshEnv(app.isPackaged); - //{env-macro-start}// - const isDatabricksConfigValid = - process.env.GOOSE_PROVIDER__TYPE === 'databricks' && + //{env-macro-start}// + const apiKeyProvidersValid = + ['openai', 'anthropic', 'google', 'groq'].includes(process.env.GOOSE_PROVIDER__TYPE) && process.env.GOOSE_PROVIDER__HOST && - process.env.GOOSE_PROVIDER__MODEL; - - const isOpenAIDirectConfigValid = - process.env.GOOSE_PROVIDER__TYPE === 'openai' && - process.env.GOOSE_PROVIDER__HOST === 'https://api.openai.com' && process.env.GOOSE_PROVIDER__MODEL && process.env.GOOSE_PROVIDER__API_KEY; + + const optionalApiKeyProvidersValid = + ['ollama', 'databricks'].includes(process.env.GOOSE_PROVIDER__TYPE) && + process.env.GOOSE_PROVIDER__HOST && + process.env.GOOSE_PROVIDER__MODEL; - return isDatabricksConfigValid || isOpenAIDirectConfigValid + return apiKeyProvidersValid|| optionalApiKeyProvidersValid; //{env-macro-end}// }; From 16e0eca769bf874892ce6afbbc95a72d23f72921 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Fri, 3 Jan 2025 16:18:35 +1100 Subject: [PATCH 3/8] don't need these --- profiles.yaml | 13 ------------- providers.yaml | 16 ---------------- 2 files changed, 29 deletions(-) delete mode 100644 profiles.yaml delete mode 100644 providers.yaml diff --git a/profiles.yaml b/profiles.yaml deleted file mode 100644 index 9b1783af50a3..000000000000 --- a/profiles.yaml +++ /dev/null @@ -1,13 +0,0 @@ -default: - processor: - provider: databricks - model: claude-3-5-sonnet-2 - accelerator: - provider: openai / ollama - model: gpt-4o-mini / qwen2.5 - systems: - - name: synopsis - type: embedded - - name: qai - type: http - host: localhost:8001 diff --git a/providers.yaml b/providers.yaml deleted file mode 100644 index b96e58ed8d4e..000000000000 --- a/providers.yaml +++ /dev/null @@ -1,16 +0,0 @@ -providers: - - name: databricks - config: - host: databricks_host - token: databricks_token - - name: openai - config: - api_key: openai_api_key - - name: ollama - config: - host: "http://localhost:11434/" - - name: bedrock - config: - profile: aws_profile - access_key: key - accesss_secret: secret From 3919e7c2e725cf593e45c769fb6caa9a0a5b0c43 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Fri, 3 Jan 2025 16:53:51 +1100 Subject: [PATCH 4/8] checkpoint --- crates/goose-server/src/configuration.rs | 37 ++++ crates/goose/src/providers.rs | 1 + crates/goose/src/providers/configs.rs | 1 + crates/goose/src/providers/factory.rs | 4 +- crates/goose/src/providers/model_pricing.rs | 13 ++ crates/goose/src/providers/openrouter.rs | 201 ++++++++++++++++++++ ui/desktop/src/main.ts | 2 +- 7 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 crates/goose/src/providers/openrouter.rs diff --git a/crates/goose-server/src/configuration.rs b/crates/goose-server/src/configuration.rs index 870b0fba0dad..8e8be4f897ae 100644 --- a/crates/goose-server/src/configuration.rs +++ b/crates/goose-server/src/configuration.rs @@ -51,6 +51,21 @@ pub enum ProviderSettings { #[serde(default)] estimate_factor: Option, }, + OpenRouter { + #[serde(default = "default_openrouter_host")] + host: String, + api_key: String, + #[serde(default = "default_model")] + model: String, + #[serde(default)] + temperature: Option, + #[serde(default)] + max_tokens: Option, + #[serde(default)] + context_limit: Option, + #[serde(default)] + estimate_factor: Option, + }, Databricks { #[serde(default = "default_databricks_host")] host: String, @@ -139,6 +154,7 @@ impl ProviderSettings { ProviderSettings::Google { .. } => ProviderType::Google, ProviderSettings::Groq { .. } => ProviderType::Groq, ProviderSettings::Anthropic { .. } => ProviderType::Anthropic, + ProviderSettings::OpenRouter { .. } => ProviderType::OpenRouter, } } @@ -162,6 +178,23 @@ impl ProviderSettings { .with_context_limit(context_limit) .with_estimate_factor(estimate_factor), }), + ProviderSettings::OpenRouter { + host, + api_key, + model, + temperature, + max_tokens, + context_limit, + estimate_factor, + } => ProviderConfig::OpenRouter(OpenAiProviderConfig { + host, + api_key, + model: ModelConfig::new(model) + .with_temperature(temperature) + .with_max_tokens(max_tokens) + .with_context_limit(context_limit) + .with_estimate_factor(estimate_factor), + }), ProviderSettings::Databricks { host, model, @@ -317,6 +350,10 @@ fn default_port() -> u16 { 3000 } +pub fn default_openrouter_host() -> String { + "https://openrouter.ai".to_string() +} + fn default_model() -> String { OPEN_AI_DEFAULT_MODEL.to_string() } diff --git a/crates/goose/src/providers.rs b/crates/goose/src/providers.rs index 6d564eeb8e8f..aa34f537b805 100644 --- a/crates/goose/src/providers.rs +++ b/crates/goose/src/providers.rs @@ -12,6 +12,7 @@ pub mod utils; pub mod google; pub mod groq; +pub mod openrouter; #[cfg(test)] pub mod mock; diff --git a/crates/goose/src/providers/configs.rs b/crates/goose/src/providers/configs.rs index 94f6d585d3eb..0e613a2f50d7 100644 --- a/crates/goose/src/providers/configs.rs +++ b/crates/goose/src/providers/configs.rs @@ -15,6 +15,7 @@ pub enum ProviderConfig { Anthropic(AnthropicProviderConfig), Google(GoogleProviderConfig), Groq(GroqProviderConfig), + OpenRouter(OpenAiProviderConfig), } /// Configuration for model-specific settings and limits diff --git a/crates/goose/src/providers/factory.rs b/crates/goose/src/providers/factory.rs index 58ad7513bef2..96689bd4ff09 100644 --- a/crates/goose/src/providers/factory.rs +++ b/crates/goose/src/providers/factory.rs @@ -1,7 +1,7 @@ use super::{ anthropic::AnthropicProvider, base::Provider, configs::ProviderConfig, databricks::DatabricksProvider, google::GoogleProvider, groq::GroqProvider, - ollama::OllamaProvider, openai::OpenAiProvider, + ollama::OllamaProvider, openai::OpenAiProvider, openrouter::OpenRouterProvider, }; use anyhow::Result; use strum_macros::EnumIter; @@ -14,6 +14,7 @@ pub enum ProviderType { Anthropic, Google, Groq, + OpenRouter, } pub fn get_provider(config: ProviderConfig) -> Result> { @@ -28,5 +29,6 @@ pub fn get_provider(config: ProviderConfig) -> Result Ok(Box::new(GoogleProvider::new(google_config)?)), ProviderConfig::Groq(groq_config) => Ok(Box::new(GroqProvider::new(groq_config)?)), + ProviderConfig::OpenRouter(openrouter_config) => Ok(Box::new(OpenRouterProvider::new(openrouter_config)?)), } } diff --git a/crates/goose/src/providers/model_pricing.rs b/crates/goose/src/providers/model_pricing.rs index 0c3a15f7debe..fc5b074a5c5a 100644 --- a/crates/goose/src/providers/model_pricing.rs +++ b/crates/goose/src/providers/model_pricing.rs @@ -61,11 +61,24 @@ lazy_static::lazy_static! { input_token_price: dec!(15.00), output_token_price: dec!(75.00), }); + // OpenRouter Models + m.insert("anthropic/claude-3-sonnet".to_string(), Pricing { + input_token_price: dec!(3.00), + output_token_price: dec!(15.00), + }); + m.insert("claude-3-sonnet".to_string(), Pricing { + input_token_price: dec!(3.00), + output_token_price: dec!(15.00), + }); // OpenAI m.insert("gpt-4o".to_string(), Pricing { input_token_price: dec!(2.50), output_token_price: dec!(10.00), }); + m.insert("gpt-4".to_string(), Pricing { + input_token_price: dec!(2.50), + output_token_price: dec!(10.00), + }); m.insert("gpt-4o-2024-11-20".to_string(), Pricing { input_token_price: dec!(2.50), output_token_price: dec!(10.00), diff --git a/crates/goose/src/providers/openrouter.rs b/crates/goose/src/providers/openrouter.rs new file mode 100644 index 000000000000..c8b7eb140647 --- /dev/null +++ b/crates/goose/src/providers/openrouter.rs @@ -0,0 +1,201 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use reqwest::Client; +use serde_json::Value; +use std::time::Duration; + +use super::base::ProviderUsage; +use super::base::{Provider, Usage}; +use super::configs::OpenAiProviderConfig; +use super::configs::{ModelConfig, ProviderModelConfig}; +use super::model_pricing::cost; +use super::model_pricing::model_pricing_for; +use super::utils::{get_model, handle_response}; +use crate::message::Message; +use crate::providers::openai_utils::{ + check_openai_context_length_error, create_openai_request_payload, get_openai_usage, + openai_response_to_message, +}; +use mcp_core::tool::Tool; + +pub const OPENROUTER_DEFAULT_MODEL: &str = "anthropic/claude-3-sonnet"; + +pub struct OpenRouterProvider { + client: Client, + config: OpenAiProviderConfig, +} + +impl OpenRouterProvider { + pub fn new(config: OpenAiProviderConfig) -> Result { + let client = Client::builder() + .timeout(Duration::from_secs(600)) // 10 minutes timeout + .build()?; + + Ok(Self { client, config }) + } + + async fn post(&self, payload: Value) -> Result { + let url = format!( + "{}/api/v1/chat/completions", + self.config.host.trim_end_matches('/') + ); + + let response = self + .client + .post(&url) + .header("Content-Type", "application/json") + .header("Authorization", format!("Bearer {}", self.config.api_key)) + .header("HTTP-Referer", "https://github.com/block/goose") + .header("X-Title", "Goose") + .json(&payload) + .send() + .await?; + + handle_response(payload, response).await? + } +} + +#[async_trait] +impl Provider for OpenRouterProvider { + fn get_model_config(&self) -> &ModelConfig { + self.config.model_config() + } + + async fn complete( + &self, + system: &str, + messages: &[Message], + tools: &[Tool], + ) -> Result<(Message, ProviderUsage)> { + // Create the base payload + let mut payload = create_openai_request_payload(&self.config.model, system, messages, tools)?; + + // For OpenRouter, we need to ensure the model has a provider prefix + if let Some(model) = payload.get_mut("model") { + if let Some(model_str) = model.as_str() { + if !model_str.contains('/') { + // For models without a prefix, add the anthropic prefix as that's our default + *model = serde_json::Value::String(format!("anthropic/{}", model_str)); + } + } + } + + // Make request + let response = self.post(payload).await?; + + // Raise specific error if context length is exceeded + if let Some(error) = response.get("error") { + if let Some(err) = check_openai_context_length_error(error) { + return Err(err.into()); + } + return Err(anyhow!("OpenRouter API error: {}", error)); + } + + // Parse response + let message = openai_response_to_message(response.clone())?; + let usage = self.get_usage(&response)?; + let model = get_model(&response); + let cost = cost(&usage, &model_pricing_for(&model)); + + Ok((message, ProviderUsage::new(model, usage, cost))) + } + + fn get_usage(&self, data: &Value) -> Result { + get_openai_usage(data) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::message::MessageContent; + use crate::providers::configs::ModelConfig; + use crate::providers::mock_server::{ + create_mock_open_ai_response, create_mock_open_ai_response_with_tools, create_test_tool, + get_expected_function_call_arguments, setup_mock_server, TEST_INPUT_TOKENS, + TEST_OUTPUT_TOKENS, TEST_TOOL_FUNCTION_NAME, TEST_TOTAL_TOKENS, + }; + use rust_decimal_macros::dec; + use wiremock::MockServer; + + async fn _setup_mock_response(response_body: Value) -> (MockServer, OpenRouterProvider) { + let mock_server = setup_mock_server("/api/v1/chat/completions", response_body).await; + + // Create the OpenRouterProvider with the mock server's URL as the host + let config = OpenAiProviderConfig { + host: mock_server.uri(), + api_key: "test_api_key".to_string(), + model: ModelConfig::new("gpt-3.5-turbo".to_string()).with_temperature(Some(0.7)), + }; + + let provider = OpenRouterProvider::new(config).unwrap(); + (mock_server, provider) + } + + #[tokio::test] + async fn test_complete_basic() -> Result<()> { + let model_name = "gpt-4"; + // Mock response for normal completion + let response_body = + create_mock_open_ai_response(model_name, "Hello! How can I assist you today?"); + + let (_, provider) = _setup_mock_response(response_body).await; + + // Prepare input messages + let messages = vec![Message::user().with_text("Hello?")]; + + // Call the complete method + let (message, usage) = provider + .complete("You are a helpful assistant.", &messages, &[]) + .await?; + + // Assert the response + if let MessageContent::Text(text) = &message.content[0] { + assert_eq!(text.text, "Hello! How can I assist you today?"); + } else { + panic!("Expected Text content"); + } + assert_eq!(usage.usage.input_tokens, Some(TEST_INPUT_TOKENS)); + assert_eq!(usage.usage.output_tokens, Some(TEST_OUTPUT_TOKENS)); + assert_eq!(usage.usage.total_tokens, Some(TEST_TOTAL_TOKENS)); + assert_eq!(usage.model, model_name); + assert_eq!(usage.cost, Some(dec!(0.00018))); + + Ok(()) + } + + #[tokio::test] + async fn test_complete_tool_request() -> Result<()> { + // Mock response for tool calling + let response_body = create_mock_open_ai_response_with_tools("gpt-4"); + + let (_, provider) = _setup_mock_response(response_body).await; + + // Input messages + let messages = vec![Message::user().with_text("What's the weather in San Francisco?")]; + + // Call the complete method + let (message, usage) = provider + .complete( + "You are a helpful assistant.", + &messages, + &[create_test_tool()], + ) + .await?; + + // Assert the response + if let MessageContent::ToolRequest(tool_request) = &message.content[0] { + let tool_call = tool_request.tool_call.as_ref().unwrap(); + assert_eq!(tool_call.name, TEST_TOOL_FUNCTION_NAME); + assert_eq!(tool_call.arguments, get_expected_function_call_arguments()); + } else { + panic!("Expected ToolCall content"); + } + + assert_eq!(usage.usage.input_tokens, Some(TEST_INPUT_TOKENS)); + assert_eq!(usage.usage.output_tokens, Some(TEST_OUTPUT_TOKENS)); + assert_eq!(usage.usage.total_tokens, Some(TEST_TOTAL_TOKENS)); + + Ok(()) + } +} \ No newline at end of file diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 058132746fdd..d15b3ccc1167 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -39,7 +39,7 @@ const checkApiCredentials = () => { //{env-macro-start}// const apiKeyProvidersValid = - ['openai', 'anthropic', 'google', 'groq'].includes(process.env.GOOSE_PROVIDER__TYPE) && + ['openai', 'anthropic', 'google', 'groq', 'openrouter'].includes(process.env.GOOSE_PROVIDER__TYPE) && process.env.GOOSE_PROVIDER__HOST && process.env.GOOSE_PROVIDER__MODEL && process.env.GOOSE_PROVIDER__API_KEY; From d4f2e1d32c6bd89af8931a3b8487ab94a357b4b8 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Fri, 3 Jan 2025 18:33:09 +1100 Subject: [PATCH 5/8] now working for openrouter --- crates/goose/src/providers/openrouter.rs | 25 ++++++++++-------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/crates/goose/src/providers/openrouter.rs b/crates/goose/src/providers/openrouter.rs index c8b7eb140647..e33885e32753 100644 --- a/crates/goose/src/providers/openrouter.rs +++ b/crates/goose/src/providers/openrouter.rs @@ -13,12 +13,12 @@ use super::model_pricing::model_pricing_for; use super::utils::{get_model, handle_response}; use crate::message::Message; use crate::providers::openai_utils::{ - check_openai_context_length_error, create_openai_request_payload, get_openai_usage, - openai_response_to_message, + check_openai_context_length_error, create_openai_request_payload_with_concat_response_content, + get_openai_usage, openai_response_to_message, }; use mcp_core::tool::Tool; -pub const OPENROUTER_DEFAULT_MODEL: &str = "anthropic/claude-3-sonnet"; +pub const OPENROUTER_DEFAULT_MODEL: &str = "anthropic/claude-3.5-sonnet"; pub struct OpenRouterProvider { client: Client, @@ -68,17 +68,12 @@ impl Provider for OpenRouterProvider { tools: &[Tool], ) -> Result<(Message, ProviderUsage)> { // Create the base payload - let mut payload = create_openai_request_payload(&self.config.model, system, messages, tools)?; - - // For OpenRouter, we need to ensure the model has a provider prefix - if let Some(model) = payload.get_mut("model") { - if let Some(model_str) = model.as_str() { - if !model_str.contains('/') { - // For models without a prefix, add the anthropic prefix as that's our default - *model = serde_json::Value::String(format!("anthropic/{}", model_str)); - } - } - } + let payload = create_openai_request_payload_with_concat_response_content( + &self.config.model, + system, + messages, + tools, + )?; // Make request let response = self.post(payload).await?; @@ -198,4 +193,4 @@ mod tests { Ok(()) } -} \ No newline at end of file +} From 8082433456b491e11f4532fb5e8d6a19b0701387 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Fri, 3 Jan 2025 18:33:30 +1100 Subject: [PATCH 6/8] fmt correctly --- crates/goose/src/providers/factory.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/goose/src/providers/factory.rs b/crates/goose/src/providers/factory.rs index 96689bd4ff09..4c7f0a6a9151 100644 --- a/crates/goose/src/providers/factory.rs +++ b/crates/goose/src/providers/factory.rs @@ -29,6 +29,8 @@ pub fn get_provider(config: ProviderConfig) -> Result Ok(Box::new(GoogleProvider::new(google_config)?)), ProviderConfig::Groq(groq_config) => Ok(Box::new(GroqProvider::new(groq_config)?)), - ProviderConfig::OpenRouter(openrouter_config) => Ok(Box::new(OpenRouterProvider::new(openrouter_config)?)), + ProviderConfig::OpenRouter(openrouter_config) => { + Ok(Box::new(OpenRouterProvider::new(openrouter_config)?)) + } } } From 88add01e7aa79bfa87b44507173d03cfb4e761ad Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Fri, 3 Jan 2025 18:39:47 +1100 Subject: [PATCH 7/8] api key page --- ui/desktop/src/components/ApiKeyWarning.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/desktop/src/components/ApiKeyWarning.tsx b/ui/desktop/src/components/ApiKeyWarning.tsx index 5ff3a438bf1e..3b3caa97b958 100644 --- a/ui/desktop/src/components/ApiKeyWarning.tsx +++ b/ui/desktop/src/components/ApiKeyWarning.tsx @@ -15,15 +15,15 @@ export function ApiKeyWarning({ className }: ApiKeyWarningProps) {

API Key Required

- To use Goose, you need to set some combination of the following env variables + To use Goose, you need to set some env variables for an appropriate provider

# OpenAI

- export GOOSE_PROVIDER__TYPE=openai
- GOOSE_PROVIDER__HOST=https://api.openai.com
- GOOSE_PROVIDER__MODEL=gpt-4o
+ export GOOSE_PROVIDER__TYPE=openai|anthropic|openrouter|...
+ GOOSE_PROVIDER__HOST=https://api.openai.com|https://api.anthropic.com|https://openrouter.ai"|...
+ GOOSE_PROVIDER__MODEL=gpt-4o|claude-3-5-sonnet-latest|anthropic/claude-3.5-sonnet|...
GOOSE_PROVIDER__API_KEY=...


@@ -35,7 +35,6 @@ export function ApiKeyWarning({ className }: ApiKeyWarningProps) { export GOOSE_PROVIDER__MODEL="claude-3-5-sonnet-2"


- Please export these and restart the application.
From 4b79a7101f8971ec43f5912f93840adf0a4bdfae Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Mon, 6 Jan 2025 13:41:45 +1100 Subject: [PATCH 8/8] foldable api warning --- ui/desktop/src/components/ApiKeyWarning.tsx | 108 +++++++++++++++----- 1 file changed, 85 insertions(+), 23 deletions(-) diff --git a/ui/desktop/src/components/ApiKeyWarning.tsx b/ui/desktop/src/components/ApiKeyWarning.tsx index 3b3caa97b958..ffe05b01dc85 100644 --- a/ui/desktop/src/components/ApiKeyWarning.tsx +++ b/ui/desktop/src/components/ApiKeyWarning.tsx @@ -1,41 +1,103 @@ import React from 'react'; import { Card } from './ui/card'; import { Bird } from './ui/icons'; +import { ChevronDown } from 'lucide-react'; interface ApiKeyWarningProps { className?: string; } +interface CollapsibleProps { + title: string; + children: React.ReactNode; + defaultOpen?: boolean; +} + +function Collapsible({ title, children, defaultOpen = false }: CollapsibleProps) { + const [isOpen, setIsOpen] = React.useState(defaultOpen); + + return ( +
+ + {isOpen && ( +
+ {children} +
+ )} +
+ ); +} + +const OPENAI_CONFIG = `export GOOSE_PROVIDER__TYPE=openai +export GOOSE_PROVIDER__HOST=https://api.openai.com +export GOOSE_PROVIDER__MODEL=gpt-4 +export GOOSE_PROVIDER__API_KEY=your_api_key_here`; + +const ANTHROPIC_CONFIG = `export GOOSE_PROVIDER__TYPE=anthropic +export GOOSE_PROVIDER__HOST=https://api.anthropic.com +export GOOSE_PROVIDER__MODEL=claude-3-sonnet +export GOOSE_PROVIDER__API_KEY=your_api_key_here`; + +const DATABRICKS_CONFIG = `export GOOSE_PROVIDER__TYPE=databricks +export GOOSE_PROVIDER__HOST=your_databricks_host +export GOOSE_PROVIDER__MODEL=claude-3-sonnet-2`; + +const OPENROUTER_CONFIG = `export GOOSE_PROVIDER__TYPE=openrouter +export GOOSE_PROVIDER__HOST=https://openrouter.ai +export GOOSE_PROVIDER__MODEL=anthropic/claude-3-sonnet +export GOOSE_PROVIDER__API_KEY=your_api_key_here`; + export function ApiKeyWarning({ className }: ApiKeyWarningProps) { return ( - +
-
+

API Key Required

-
- To use Goose, you need to set some env variables for an appropriate provider -
-
- # OpenAI -
-
- export GOOSE_PROVIDER__TYPE=openai|anthropic|openrouter|...
- GOOSE_PROVIDER__HOST=https://api.openai.com|https://api.anthropic.com|https://openrouter.ai"|...
- GOOSE_PROVIDER__MODEL=gpt-4o|claude-3-5-sonnet-latest|anthropic/claude-3.5-sonnet|...
- GOOSE_PROVIDER__API_KEY=...
-
-
- # Databricks + Claude -
-
- export GOOSE_PROVIDER__TYPE=databricks
- export GOOSE_PROVIDER__HOST=...
- export GOOSE_PROVIDER__MODEL="claude-3-5-sonnet-2"
-
-
+

+ To use Goose, you need to set environment variables for one of the following providers: +

+ +
+ +
+              {OPENAI_CONFIG}
+            
+
+ + +
+              {ANTHROPIC_CONFIG}
+            
+
+ + +
+              {DATABRICKS_CONFIG}
+            
+
+ + +
+              {OPENROUTER_CONFIG}
+            
+
+ +

+ After setting these variables, restart Goose for the changes to take effect. +

);