diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index a9dcc2cb0657..e2d0a272b27f 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -1520,13 +1520,9 @@ impl Agent { self.extension_manager .suggest_disable_extensions_prompt() .await, - Some(model_name), + model_name, false, ); - tracing::debug!( - "Built system prompt with {} characters", - system_prompt.len() - ); let recipe_prompt = prompt_manager.get_recipe_prompt().await; let tools = self @@ -1537,7 +1533,6 @@ impl Agent { tracing::error!("Failed to get tools for recipe creation: {}", e); e })?; - tracing::debug!("Retrieved {} tools for recipe creation", tools.len()); messages.push(Message::user().with_text(recipe_prompt)); @@ -1752,7 +1747,7 @@ mod tests { let prompt_manager = agent.prompt_manager.lock().await; let system_prompt = - prompt_manager.build_system_prompt(vec![], None, Value::Null, None, false); + prompt_manager.build_system_prompt(vec![], None, Value::Null, "gpt-4o", false); let final_output_tool_ref = agent.final_output_tool.lock().await; let final_output_tool_system_prompt = diff --git a/crates/goose/src/agents/prompt_manager.rs b/crates/goose/src/agents/prompt_manager.rs index 679480685315..5ff0e911aa9c 100644 --- a/crates/goose/src/agents/prompt_manager.rs +++ b/crates/goose/src/agents/prompt_manager.rs @@ -3,8 +3,8 @@ use serde_json::Value; use std::collections::HashMap; use crate::agents::extension::ExtensionInfo; +use crate::agents::recipe_tools::dynamic_task_tools::should_enabled_subagents; use crate::agents::router_tools::llm_search_tool_prompt; -use crate::providers::base::get_current_model; use crate::{config::Config, prompt_template, utils::sanitize_unicode_tags}; pub struct PromptManager { @@ -39,35 +39,12 @@ impl PromptManager { self.system_prompt_override = Some(template); } - /// Normalize a model name (replace - and / with _, lower case) - fn normalize_model_name(name: &str) -> String { - name.replace(['-', '/', '.'], "_").to_lowercase() - } - - /// Map model (normalized) to prompt filenames; returns filename if a key is contained in the normalized model - fn model_prompt_map(model: &str) -> &'static str { - let mut map = HashMap::new(); - map.insert("gpt_4_1", "system_gpt_4.1.md"); - // Add more mappings as needed - let norm_model = Self::normalize_model_name(model); - for (key, val) in &map { - if norm_model.contains(key) { - return val; - } - } - "system.md" - } - - /// Build the final system prompt - /// - /// * `extensions_info` – extension information for each extension/MCP - /// * `frontend_instructions` – instructions for the "frontend" tool pub fn build_system_prompt( &self, extensions_info: Vec, frontend_instructions: Option, suggest_disable_extensions_prompt: Value, - model_name: Option<&str>, + model_name: &str, router_enabled: bool, ) -> String { let mut context: HashMap<&str, Value> = HashMap::new(); @@ -113,36 +90,23 @@ impl PromptManager { Value::String(suggest_disable_extensions_prompt.to_string()), ); - // Add the mode to the context for conditional rendering let config = Config::global(); let goose_mode = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string()); context.insert("goose_mode", Value::String(goose_mode.clone())); - context.insert("is_autonomous", Value::Bool(goose_mode == "auto")); - - // First check the global store, and only if it's not available, fall back to the provided model_name - let model_to_use: Option = - get_current_model().or_else(|| model_name.map(|s| s.to_string())); + context.insert( + "enable_subagents", + Value::Bool(should_enabled_subagents(model_name)), + ); - // Conditionally load the override prompt or the global system prompt let base_prompt = if let Some(override_prompt) = &self.system_prompt_override { let sanitized_override_prompt = sanitize_unicode_tags(override_prompt); prompt_template::render_inline_once(&sanitized_override_prompt, &context) - .expect("Prompt should render") - } else if let Some(model) = &model_to_use { - // Use the fuzzy mapping to determine the prompt file, or fall back to legacy logic - let prompt_file = Self::model_prompt_map(model); - match prompt_template::render_global_file(prompt_file, &context) { - Ok(prompt) => prompt, - Err(_) => { - // Fall back to the standard system.md if model-specific one doesn't exist - prompt_template::render_global_file("system.md", &context) - .expect("Prompt should render") - } - } } else { prompt_template::render_global_file("system.md", &context) - .expect("Prompt should render") - }; + } + .unwrap_or_else(|_| { + "You are a general-purpose AI agent called goose, created by Block".to_string() + }); let mut system_prompt_extras = self.system_prompt_extras.clone(); if goose_mode == "chat" { @@ -150,9 +114,6 @@ impl PromptManager { "Right now you are in the chat only mode, no access to any tool use and system." .to_string(), ); - } else { - system_prompt_extras - .push("Right now you are *NOT* in the chat only mode and have access to tool use and system.".to_string()); } let sanitized_system_prompt_extras: Vec = system_prompt_extras @@ -173,7 +134,8 @@ impl PromptManager { pub async fn get_recipe_prompt(&self) -> String { let context: HashMap<&str, Value> = HashMap::new(); - prompt_template::render_global_file("recipe.md", &context).expect("Prompt should render") + prompt_template::render_global_file("recipe.md", &context) + .unwrap_or_else(|_| "The recipe prompt is busted. Tell the user.".to_string()) } } @@ -181,66 +143,19 @@ impl PromptManager { mod tests { use super::*; - #[test] - fn test_normalize_model_name() { - assert_eq!(PromptManager::normalize_model_name("gpt-4.1"), "gpt_4_1"); - assert_eq!(PromptManager::normalize_model_name("gpt/3.5"), "gpt_3_5"); - assert_eq!( - PromptManager::normalize_model_name("GPT-3.5/PLUS"), - "gpt_3_5_plus" - ); - } - - #[test] - fn test_model_prompt_map_matches() { - // should match prompts based on contained normalized keys - assert_eq!( - PromptManager::model_prompt_map("gpt-4.1"), - "system_gpt_4.1.md" - ); - - assert_eq!( - PromptManager::model_prompt_map("gpt-4.1-2025-04-14"), - "system_gpt_4.1.md" - ); - - assert_eq!( - PromptManager::model_prompt_map("openai/gpt-4.1"), - "system_gpt_4.1.md" - ); - assert_eq!( - PromptManager::model_prompt_map("goose-gpt-4-1"), - "system_gpt_4.1.md" - ); - assert_eq!( - PromptManager::model_prompt_map("gpt-4-1-huge"), - "system_gpt_4.1.md" - ); - } - - #[test] - fn test_model_prompt_map_none() { - // should return system.md for unrecognized/unsupported model names - assert_eq!(PromptManager::model_prompt_map("llama-3-70b"), "system.md"); - assert_eq!(PromptManager::model_prompt_map("goose"), "system.md"); - assert_eq!( - PromptManager::model_prompt_map("claude-3.7-sonnet"), - "system.md" - ); - assert_eq!( - PromptManager::model_prompt_map("xxx-unknown-model"), - "system.md" - ); - } - #[test] fn test_build_system_prompt_sanitizes_override() { let mut manager = PromptManager::new(); let malicious_override = "System prompt\u{E0041}\u{E0042}\u{E0043}with hidden text"; manager.set_system_prompt_override(malicious_override.to_string()); - let result = - manager.build_system_prompt(vec![], None, Value::String("".to_string()), None, false); + let result = manager.build_system_prompt( + vec![], + None, + Value::String("".to_string()), + "gpt-4o", + false, + ); assert!(!result.contains('\u{E0041}')); assert!(!result.contains('\u{E0042}')); @@ -255,8 +170,13 @@ mod tests { let malicious_extra = "Extra instruction\u{E0041}\u{E0042}\u{E0043}hidden"; manager.add_system_prompt_extra(malicious_extra.to_string()); - let result = - manager.build_system_prompt(vec![], None, Value::String("".to_string()), None, false); + let result = manager.build_system_prompt( + vec![], + None, + Value::String("".to_string()), + "gpt-4o", + false, + ); assert!(!result.contains('\u{E0041}')); assert!(!result.contains('\u{E0042}')); @@ -272,8 +192,13 @@ mod tests { manager.add_system_prompt_extra("Second\u{E0042}instruction".to_string()); manager.add_system_prompt_extra("Third\u{E0043}instruction".to_string()); - let result = - manager.build_system_prompt(vec![], None, Value::String("".to_string()), None, false); + let result = manager.build_system_prompt( + vec![], + None, + Value::String("".to_string()), + "gpt-4o", + false, + ); assert!(!result.contains('\u{E0041}')); assert!(!result.contains('\u{E0042}')); @@ -289,8 +214,13 @@ mod tests { let legitimate_unicode = "Instruction with δΈ–η•Œ and 🌍 emojis"; manager.add_system_prompt_extra(legitimate_unicode.to_string()); - let result = - manager.build_system_prompt(vec![], None, Value::String("".to_string()), None, false); + let result = manager.build_system_prompt( + vec![], + None, + Value::String("".to_string()), + "gpt-4o", + false, + ); assert!(result.contains("δΈ–η•Œ")); assert!(result.contains("🌍")); @@ -311,7 +241,7 @@ mod tests { vec![malicious_extension_info], None, Value::String("".to_string()), - None, + "gpt-4o", false, ); diff --git a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs index 91acfd053b6a..f87cda290337 100644 --- a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs +++ b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs @@ -27,6 +27,7 @@ pub struct CreateDynamicTaskParams { /// How to execute multiple tasks (default: parallel for multiple tasks, sequential for single task) #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(with = "Option")] pub execution_mode: Option, } @@ -90,6 +91,18 @@ pub struct TaskParameter { pub return_last_only: Option, } +pub fn should_enabled_subagents(model_name: &str) -> bool { + let config = crate::config::Config::global(); + let is_autonomous = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string()) == "auto"; + if !is_autonomous { + return false; + } + if model_name.starts_with("gemini") { + return false; + } + true +} + pub fn create_dynamic_task_tool() -> Tool { let schema = schema_for!(CreateDynamicTaskParams); let schema_value = diff --git a/crates/goose/src/agents/reply_parts.rs b/crates/goose/src/agents/reply_parts.rs index c4dd379b6880..9b5d1403bfbe 100644 --- a/crates/goose/src/agents/reply_parts.rs +++ b/crates/goose/src/agents/reply_parts.rs @@ -15,6 +15,7 @@ use crate::providers::toolshim::{ modify_system_prompt_for_tool_json, OllamaInterpreter, }; +use crate::agents::recipe_tools::dynamic_task_tools::should_enabled_subagents; use crate::session::SessionManager; use rmcp::model::Tool; @@ -32,23 +33,20 @@ async fn toolshim_postprocess( } impl Agent { - /// Prepares tools and system prompt for a provider request - pub async fn prepare_tools_and_prompt(&self) -> anyhow::Result<(Vec, Vec, String)> { + pub async fn prepare_tools_and_prompt(&self) -> Result<(Vec, Vec, String)> { // Get router enabled status let router_enabled = self.tool_route_manager.is_router_enabled().await; // Get tools from extension manager let mut tools = self.list_tools_for_router().await; - let config = crate::config::Config::global(); - let is_autonomous = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string()) == "auto"; - // If router is disabled and no tools were returned, fall back to regular tools if !router_enabled && tools.is_empty() { - // Get all tools but filter out subagent tools if not in autonomous mode tools = self.list_tools(None).await; - if !is_autonomous { - // Filter out subagent-related tools + let provider = self.provider().await?; + let model_name = provider.get_model_config().model_name; + + if !should_enabled_subagents(&model_name) { tools.retain(|tool| { tool.name != crate::agents::subagent_execution_tool::subagent_execute_task_tool::SUBAGENT_EXECUTE_TASK_TOOL_NAME && tool.name != crate::agents::recipe_tools::dynamic_task_tools::DYNAMIC_TASK_TOOL_NAME_PREFIX @@ -77,7 +75,7 @@ impl Agent { self.extension_manager .suggest_disable_extensions_prompt() .await, - Some(model_name), + model_name, router_enabled, ); diff --git a/crates/goose/src/permission/permission_judge.rs b/crates/goose/src/permission/permission_judge.rs index 5e54fbfd0dca..4d1466466881 100644 --- a/crates/goose/src/permission/permission_judge.rs +++ b/crates/goose/src/permission/permission_judge.rs @@ -473,56 +473,4 @@ mod tests { assert!(result.needs_approval.iter().any(|req| req.id == "tool_3")); assert!(enable_extension_request_ids.iter().any(|id| id == "tool_3")); } - - #[tokio::test] - async fn test_check_tool_permissions_auto() { - // Setup mocks - let temp_file = NamedTempFile::new().unwrap(); - let temp_path = temp_file.path(); - let mut permission_manager = PermissionManager::new(temp_path); - let provider = create_mock_provider(); - - let tools_with_readonly_annotation: HashSet = - vec!["file_reader".to_string()].into_iter().collect(); - let tools_without_annotation: HashSet = - vec!["data_fetcher".to_string()].into_iter().collect(); - - permission_manager.update_user_permission("file_reader", PermissionLevel::AlwaysAllow); - permission_manager - .update_smart_approve_permission("data_fetcher", PermissionLevel::AskBefore); - - let tool_request_1 = ToolRequest { - id: "tool_1".to_string(), - tool_call: Ok(CallToolRequestParam { - name: "file_reader".into(), - arguments: Some(object!({"path": "/path/to/file"})), - }), - }; - - let tool_request_2 = ToolRequest { - id: "tool_2".to_string(), - tool_call: Ok(CallToolRequestParam { - name: "data_fetcher".into(), - arguments: Some(object!({"url": "http://example.com"})), - }), - }; - - let candidate_requests: Vec = vec![tool_request_1, tool_request_2]; - - // Call the function under test - let (result, _) = check_tool_permissions( - &candidate_requests, - "auto", - tools_with_readonly_annotation, - tools_without_annotation, - &mut permission_manager, - provider, - ) - .await; - - // Validate the result - assert_eq!(result.approved.len(), 2); // file_reader should be approved - assert_eq!(result.needs_approval.len(), 0); // data_fetcher should need approval - assert_eq!(result.denied.len(), 0); // No tool should be denied in this test - } } diff --git a/crates/goose/src/prompts/system.md b/crates/goose/src/prompts/system.md index f8bda7cd9610..e84d17cde069 100644 --- a/crates/goose/src/prompts/system.md +++ b/crates/goose/src/prompts/system.md @@ -48,7 +48,8 @@ No extensions are defined. You should let the user know that they should add ext {% endif %} {{tool_selection_strategy}} -{% if is_autonomous %} +{% if enable_subagents %} + # sub agents Execute self contained tasks where step-by-step visibility is not important through subagents. @@ -60,7 +61,7 @@ Execute self contained tasks where step-by-step visibility is not important thro - Provide all needed context β€” subagents cannot see your context - Use extension filters to limit resource access - Use return_last_only when only a summary or simple answer is required β€” inform subagent of this choice. -{% endif %} + {% endif %} # Response Guidelines diff --git a/crates/goose/src/providers/databricks.rs b/crates/goose/src/providers/databricks.rs index f21bc925a866..44dc59971dd7 100644 --- a/crates/goose/src/providers/databricks.rs +++ b/crates/goose/src/providers/databricks.rs @@ -46,7 +46,6 @@ pub const DATABRICKS_KNOWN_MODELS: &[&str] = &[ "databricks-meta-llama-3-3-70b-instruct", "databricks-meta-llama-3-1-405b-instruct", "databricks-dbrx-instruct", - "databricks-mixtral-8x7b-instruct", ]; pub const DATABRICKS_DOC_URL: &str = diff --git a/crates/goose/src/providers/formats/databricks.rs b/crates/goose/src/providers/formats/databricks.rs index c7a34e54273a..dd2a0f0ae565 100644 --- a/crates/goose/src/providers/formats/databricks.rs +++ b/crates/goose/src/providers/formats/databricks.rs @@ -264,23 +264,31 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec anyhow::Result> { +pub fn format_tools(tools: &[Tool], model_name: &str) -> anyhow::Result> { let mut tool_names = std::collections::HashSet::new(); let mut result = Vec::new(); + let is_gemini = model_name.starts_with("gemini"); + for tool in tools { if !tool_names.insert(&tool.name) { return Err(anyhow!("Duplicate tool name: {}", tool.name)); } + let parameters = if is_gemini { + let mut cleaned_schema = tool.input_schema.as_ref().clone(); + cleaned_schema.remove("$schema"); + json!(cleaned_schema) + } else { + json!(tool.input_schema) + }; + result.push(json!({ "type": "function", "function": { "name": tool.name, - // do not silently truncate description "description": tool.description, - "parameters": tool.input_schema, + "parameters": parameters, } })); } @@ -544,7 +552,7 @@ pub fn create_request( let messages_spec = format_messages(messages, image_format); let mut tools_spec = if !tools.is_empty() { - format_tools(tools)? + format_tools(tools, &model_config.model_name)? } else { vec![] }; @@ -639,82 +647,6 @@ mod tests { use rmcp::object; use serde_json::json; - #[test] - fn test_validate_tool_schemas() { - // Test case 1: Empty parameters object - // Input JSON with an incomplete parameters object - let mut actual = vec![json!({ - "type": "function", - "function": { - "name": "test_func", - "description": "test description", - "parameters": { - "type": "object" - } - } - })]; - - // Run the function to validate and update schemas - validate_tool_schemas(&mut actual); - - // Expected JSON after validation - let expected = vec![json!({ - "type": "function", - "function": { - "name": "test_func", - "description": "test description", - "parameters": { - "type": "object", - "properties": {}, - "required": [] - } - } - })]; - - // Compare entire JSON structures instead of individual fields - assert_eq!(actual, expected); - - // Test case 2: Missing type field - let mut tools = vec![json!({ - "type": "function", - "function": { - "name": "test_func", - "description": "test description", - "parameters": { - "properties": {} - } - } - })]; - - validate_tool_schemas(&mut tools); - - let params = tools[0]["function"]["parameters"].as_object().unwrap(); - assert_eq!(params["type"], "object"); - - // Test case 3: Complete valid schema should remain unchanged - let original_schema = json!({ - "type": "function", - "function": { - "name": "test_func", - "description": "test description", - "parameters": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "City and country" - } - }, - "required": ["location"] - } - } - }); - - let mut tools = vec![original_schema.clone()]; - validate_tool_schemas(&mut tools); - assert_eq!(tools[0], original_schema); - } - const OPENAI_TOOL_USE_RESPONSE: &str = r#"{ "choices": [{ "role": "assistant", @@ -752,6 +684,7 @@ mod tests { "test_tool", "A test tool", object!({ + "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "input": { @@ -763,11 +696,16 @@ mod tests { }), ); - let spec = format_tools(&[tool])?; + let spec = format_tools(&[tool.clone()], "gpt-4o")?; + assert_eq!( + spec[0]["function"]["parameters"]["$schema"], + "http://json-schema.org/draft-07/schema#" + ); + + let spec = format_tools(&[tool], "gemini-2-5-flash")?; + assert!(spec[0]["function"]["parameters"].get("$schema").is_none()); + assert_eq!(spec[0]["function"]["parameters"]["type"], "object"); - assert_eq!(spec.len(), 1); - assert_eq!(spec[0]["type"], "function"); - assert_eq!(spec[0]["function"]["name"], "test_tool"); Ok(()) } @@ -785,7 +723,6 @@ mod tests { ), ]; - // Get the ID from the tool request to use in the response let tool_id = if let MessageContent::ToolRequest(request) = &messages[2].content[0] { &request.id } else { @@ -823,7 +760,6 @@ mod tests { }), )]; - // Get the ID from the tool request to use in the response let tool_id = if let MessageContent::ToolRequest(request) = &messages[0].content[0] { &request.id } else { @@ -879,7 +815,7 @@ mod tests { }), ); - let result = format_tools(&[tool1, tool2]); + let result = format_tools(&[tool1, tool2], "gpt-4o"); assert!(result.is_err()); assert!(result .unwrap_err() @@ -889,16 +825,8 @@ mod tests { Ok(()) } - #[test] - fn test_format_tools_empty() -> anyhow::Result<()> { - let spec = format_tools(&[])?; - assert!(spec.is_empty()); - Ok(()) - } - #[test] fn test_format_messages_with_image_path() -> anyhow::Result<()> { - // Create a temporary PNG file with valid PNG magic numbers let temp_dir = tempfile::tempdir()?; let png_path = temp_dir.path().join("test.png"); let png_data = [ diff --git a/crates/goose/src/providers/formats/google.rs b/crates/goose/src/providers/formats/google.rs index a59b917d1f2a..06c4a22c71a4 100644 --- a/crates/goose/src/providers/formats/google.rs +++ b/crates/goose/src/providers/formats/google.rs @@ -130,7 +130,6 @@ pub fn format_messages(messages: &[Message]) -> Vec { .collect() } -/// Convert internal Tool format to Google's API tool specification pub fn format_tools(tools: &[Tool]) -> Vec { tools .iter() @@ -139,7 +138,7 @@ pub fn format_tools(tools: &[Tool]) -> Vec { parameters.insert("name".to_string(), json!(tool.name)); parameters.insert("description".to_string(), json!(tool.description)); let tool_input_schema = &tool.input_schema; - // Only add the parameters key if the tool schema has non-empty properties. + if tool_input_schema .get("properties") .and_then(|v| v.as_object()) diff --git a/crates/goose/src/providers/google.rs b/crates/goose/src/providers/google.rs index 01cc45fc3a9f..d6d7b30dd2ac 100644 --- a/crates/goose/src/providers/google.rs +++ b/crates/goose/src/providers/google.rs @@ -13,10 +13,9 @@ use rmcp::model::Tool; use serde_json::Value; pub const GOOGLE_API_HOST: &str = "https://generativelanguage.googleapis.com"; -pub const GOOGLE_DEFAULT_MODEL: &str = "gemini-2.5-flash"; -pub const GOOGLE_DEFAULT_FAST_MODEL: &str = "gemini-1.5-flash"; +pub const GOOGLE_DEFAULT_MODEL: &str = "gemini-2.5-pro"; +pub const GOOGLE_DEFAULT_FAST_MODEL: &str = "gemini-2.5-flash"; pub const GOOGLE_KNOWN_MODELS: &[&str] = &[ - // Gemini 2.5 models (latest generation) "gemini-2.5-pro", "gemini-2.5-pro-preview-06-05", "gemini-2.5-pro-preview-05-06", @@ -27,20 +26,10 @@ pub const GOOGLE_KNOWN_MODELS: &[&str] = &[ "gemini-2.5-flash-exp-native-audio-thinking-dialog", "gemini-2.5-flash-preview-tts", "gemini-2.5-pro-preview-tts", - // Gemini 2.0 models "gemini-2.0-flash", "gemini-2.0-flash-exp", "gemini-2.0-flash-preview-image-generation", "gemini-2.0-flash-lite", - // Gemini 1.5 models - "gemini-1.5-flash", - "gemini-1.5-flash-latest", - "gemini-1.5-flash-002", - "gemini-1.5-flash-8b", - "gemini-1.5-flash-8b-latest", - "gemini-1.5-pro", - "gemini-1.5-pro-latest", - "gemini-1.5-pro-002", ]; pub const GOOGLE_DOC_URL: &str = "https://ai.google.dev/gemini-api/docs/models"; @@ -115,7 +104,6 @@ impl Provider for GoogleProvider { let payload = create_request(model_config, system, messages, tools)?; let mut log = RequestLog::start(model_config, &payload)?; - // Make request let response = self .with_retry(|| async { let payload_clone = payload.clone(); @@ -123,7 +111,6 @@ impl Provider for GoogleProvider { }) .await?; - // Parse response let message = response_to_message(unescape_json_values(&response))?; let usage = get_usage(&response)?; let response_model = match response.get("modelVersion") { @@ -135,7 +122,6 @@ impl Provider for GoogleProvider { Ok((message, provider_usage)) } - /// Fetch supported models from Google Generative Language API; returns Err on failure, Ok(None) if not present async fn fetch_supported_models(&self) -> Result>, ProviderError> { let response = self.api_client.response_get("v1beta/models").await?; let json: serde_json::Value = response.json().await?; diff --git a/crates/goose/src/providers/openrouter.rs b/crates/goose/src/providers/openrouter.rs index a3707af89a8e..f890e87b704e 100644 --- a/crates/goose/src/providers/openrouter.rs +++ b/crates/goose/src/providers/openrouter.rs @@ -17,7 +17,7 @@ use crate::providers::formats::openai::{create_request, get_usage, response_to_m use rmcp::model::Tool; pub const OPENROUTER_DEFAULT_MODEL: &str = "anthropic/claude-sonnet-4"; -pub const OPENROUTER_DEFAULT_FAST_MODEL: &str = "google/gemini-flash-1.5"; +pub const OPENROUTER_DEFAULT_FAST_MODEL: &str = "google/gemini-flash-2.5"; pub const OPENROUTER_MODEL_PREFIX_ANTHROPIC: &str = "anthropic"; // OpenRouter can run many models, we suggest the default @@ -28,7 +28,7 @@ pub const OPENROUTER_KNOWN_MODELS: &[&str] = &[ "anthropic/claude-opus-4", "anthropic/claude-3.7-sonnet", "google/gemini-2.5-pro", - "google/gemini-flash-1.5", + "google/gemini-flash-2.5", "deepseek/deepseek-r1-0528", "qwen/qwen3-coder", "moonshotai/kimi-k2", diff --git a/scripts/test_providers.sh b/scripts/test_providers.sh new file mode 100755 index 000000000000..68908622755d --- /dev/null +++ b/scripts/test_providers.sh @@ -0,0 +1,61 @@ +#!/bin/bash +if [ -f .env ]; then + export $(grep -v '^#' .env | xargs) +fi + +echo "Building goose..." +cargo build --release --bin goose +echo "" + +SCRIPT_DIR=$(pwd) + +PROVIDERS=( + "openrouter:anthropic/claude-sonnet-4.5:google/gemini-flash-2.5:qwen/qwen3-coder" + "openai:gpt-4o:gpt-4o-mini:gpt-3.5-turbo" + "anthropic:claude-sonnet-4-0:claude-3-7-sonnet-latest" + "google:gemini-2.5-pro:gemini-2.5-pro:gemini-2.5-flash" + "databricks:databricks-claude-sonnet-4:gemini-2-5-flash:gpt-4o" +) + +RESULTS=() + +for provider_config in "${PROVIDERS[@]}"; do + IFS=':' read -ra PARTS <<< "$provider_config" + PROVIDER="${PARTS[0]}" + for i in $(seq 1 $((${#PARTS[@]} - 1))); do + MODEL="${PARTS[$i]}" + export GOOSE_PROVIDER="$PROVIDER" + export GOOSE_MODEL="$MODEL" + TESTDIR=$(mktemp -d) + echo "hello" > "$TESTDIR/hello.txt" + echo "Provider: ${PROVIDER}" + echo "Model: ${MODEL}" + echo "" + TMPFILE=$(mktemp) + (cd "$TESTDIR" && "$SCRIPT_DIR/target/release/goose" run --text "please list files in the current directory" --with-builtin developer 2>&1) | tee "$TMPFILE" + echo "" + if grep -q "shell | developer" "$TMPFILE"; then + echo "βœ“ SUCCESS: Test passed - developer tool called" + RESULTS+=("βœ“ ${PROVIDER}/${MODEL}") + else + echo "βœ— FAILED: Test failed - no developer tools called" + RESULTS+=("βœ— ${PROVIDER}/${MODEL}") + fi + rm "$TMPFILE" + rm -rf "$TESTDIR" + echo "---" + done +done +echo "" +echo "=== Test Summary ===" +for result in "${RESULTS[@]}"; do + echo "$result" +done +if echo "${RESULTS[@]}" | grep -q "βœ—"; then + echo "" + echo "Some tests failed!" + exit 1 +else + echo "" + echo "All tests passed!" +fi