diff --git a/crates/goose/src/config/declarative_providers.rs b/crates/goose/src/config/declarative_providers.rs index acd35714..e0da2d56 100644 --- a/crates/goose/src/config/declarative_providers.rs +++ b/crates/goose/src/config/declarative_providers.rs @@ -7,7 +7,16 @@ use crate::providers::openai::OpenAiProvider; use anyhow::Result; use include_dir::{include_dir, Dir}; use once_cell::sync::Lazy; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; + +/// Deserialize an optional string, treating empty/whitespace-only values as None. +fn deserialize_non_empty_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt: Option = Option::deserialize(deserializer)?; + Ok(opt.filter(|s| !s.trim().is_empty())) +} use std::collections::HashMap; use std::path::Path; use std::sync::Mutex; @@ -66,6 +75,8 @@ pub struct DeclarativeProviderConfig { pub dynamic_models: Option, #[serde(default)] pub skip_canonical_filtering: bool, + #[serde(default, deserialize_with = "deserialize_non_empty_string")] + pub fast_model: Option, } fn default_requires_auth() -> bool { @@ -221,6 +232,7 @@ pub fn create_custom_provider( env_vars: None, dynamic_models: None, skip_canonical_filtering: false, + fast_model: None, }; let custom_providers_dir = custom_providers_dir(); @@ -287,6 +299,7 @@ pub fn update_custom_provider(params: UpdateCustomProviderParams) -> Result<()> env_vars: existing_config.env_vars, dynamic_models: existing_config.dynamic_models, skip_canonical_filtering: existing_config.skip_canonical_filtering, + fast_model: existing_config.fast_model.clone(), }; let file_path = custom_providers_dir().join(format!("{}.json", updated_config.name)); diff --git a/crates/goose/src/providers/anthropic.rs b/crates/goose/src/providers/anthropic.rs index a446797d..43039d3e 100644 --- a/crates/goose/src/providers/anthropic.rs +++ b/crates/goose/src/providers/anthropic.rs @@ -127,6 +127,12 @@ impl AnthropicProvider { None }; + let model = if let Some(ref fast_model_name) = config.fast_model { + model.with_fast(fast_model_name, &config.name)? + } else { + model + }; + Ok(Self { api_client, model, diff --git a/crates/goose/src/providers/ollama.rs b/crates/goose/src/providers/ollama.rs index 5678a1da..4dceb3ab 100644 --- a/crates/goose/src/providers/ollama.rs +++ b/crates/goose/src/providers/ollama.rs @@ -182,6 +182,12 @@ impl OllamaProvider { )); } + let model = if let Some(ref fast_model_name) = config.fast_model { + model.with_fast(fast_model_name, &config.name)? + } else { + model + }; + Ok(Self { api_client, model, diff --git a/crates/goose/src/providers/openai.rs b/crates/goose/src/providers/openai.rs index d7d26177..aad6ddd0 100644 --- a/crates/goose/src/providers/openai.rs +++ b/crates/goose/src/providers/openai.rs @@ -221,6 +221,12 @@ impl OpenAiProvider { None }; + let model = if let Some(ref fast_model_name) = config.fast_model { + model.with_fast(fast_model_name, &config.name)? + } else { + model + }; + Ok(Self { api_client, base_path, diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index db75a237..1f6b7b05 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -4618,6 +4618,10 @@ }, "nullable": true }, + "fast_model": { + "type": "string", + "nullable": true + }, "headers": { "type": "object", "additionalProperties": { diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 19065eee..902e3e86 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -216,6 +216,7 @@ export type DeclarativeProviderConfig = { dynamic_models?: boolean | null; engine: ProviderEngine; env_vars?: Array | null; + fast_model?: string | null; headers?: { [key: string]: string; } | null;