From 3cb27a630584d5f327f74a5f45cc58ded081494c Mon Sep 17 00:00:00 2001 From: Rodolfo Olivieri Date: Mon, 11 May 2026 20:02:21 -0300 Subject: [PATCH] chore: introduce DEFAULT_PROVIDER_TIMEOUT_SECS constant for providers (#8816) Signed-off-by: Rodolfo Olivieri Signed-off-by: Douwe Osinga Co-authored-by: Douwe Osinga --- crates/goose/src/providers/api_client.rs | 7 ++++++- crates/goose/src/providers/base.rs | 5 +++++ crates/goose/src/providers/databricks.rs | 19 ++++++++++++++----- crates/goose/src/providers/gcpvertexai.rs | 9 +++++---- crates/goose/src/providers/gemini_oauth.rs | 9 +++++---- crates/goose/src/providers/githubcopilot.rs | 6 ++++-- crates/goose/src/providers/kimicode.rs | 7 +++++-- crates/goose/src/providers/litellm.rs | 5 ++++- crates/goose/src/providers/ollama.rs | 7 +++++-- crates/goose/src/providers/openai.rs | 12 +++++++++--- crates/goose/src/providers/toolshim.rs | 3 ++- 11 files changed, 64 insertions(+), 25 deletions(-) diff --git a/crates/goose/src/providers/api_client.rs b/crates/goose/src/providers/api_client.rs index c0e4bbd1..86109073 100644 --- a/crates/goose/src/providers/api_client.rs +++ b/crates/goose/src/providers/api_client.rs @@ -1,3 +1,4 @@ +use crate::providers::base::DEFAULT_PROVIDER_TIMEOUT_SECS; use crate::session_context::SESSION_ID_HEADER; use anyhow::Result; use async_trait::async_trait; @@ -278,7 +279,11 @@ pub struct ApiRequestBuilder<'a> { impl ApiClient { pub fn new(host: String, auth: AuthMethod) -> Result { - Self::with_timeout(host, auth, Duration::from_secs(600)) + Self::with_timeout( + host, + auth, + Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS), + ) } pub fn with_timeout(host: String, auth: AuthMethod, timeout: Duration) -> Result { diff --git a/crates/goose/src/providers/base.rs b/crates/goose/src/providers/base.rs index acedf620..070bdf08 100644 --- a/crates/goose/src/providers/base.rs +++ b/crates/goose/src/providers/base.rs @@ -4,6 +4,11 @@ use futures::future::BoxFuture; use futures::Stream; use serde::{Deserialize, Serialize}; +/// Default HTTP timeout for all provider API calls. +/// Long-running model inference can take several minutes, so we allow up to 10 minutes +/// before giving up. Individual providers may override this via their own config key. +pub const DEFAULT_PROVIDER_TIMEOUT_SECS: u64 = 600; + use super::canonical::{map_to_canonical_model, CanonicalModelRegistry}; use super::errors::ProviderError; use super::inventory::{default_inventory_identity, InventoryIdentityInput}; diff --git a/crates/goose/src/providers/databricks.rs b/crates/goose/src/providers/databricks.rs index 5fc7b09a..115bfcdf 100644 --- a/crates/goose/src/providers/databricks.rs +++ b/crates/goose/src/providers/databricks.rs @@ -13,7 +13,10 @@ use tokio_util::codec::{FramedRead, LinesCodec}; use tokio_util::io::StreamReader; use super::api_client::{ApiClient, AuthMethod, AuthProvider}; -use super::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata}; +use super::base::{ + ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata, + DEFAULT_PROVIDER_TIMEOUT_SECS, +}; use super::embedding::EmbeddingCapable; use super::errors::ProviderError; use super::formats::databricks::create_request; @@ -41,7 +44,6 @@ use serde_json::json; const DEFAULT_CLIENT_ID: &str = "databricks-cli"; const DEFAULT_REDIRECT_URL: &str = "http://localhost"; const DEFAULT_SCOPES: &[&str] = &["all-apis", "offline_access"]; -const DEFAULT_TIMEOUT_SECS: u64 = 600; const DATABRICKS_PROVIDER_NAME: &str = "databricks"; pub const DATABRICKS_DEFAULT_MODEL: &str = "databricks-claude-sonnet-4"; @@ -177,8 +179,11 @@ impl DatabricksProvider { token_cache: token_cache.clone(), })); - let api_client = - ApiClient::with_timeout(host, auth_method, Duration::from_secs(DEFAULT_TIMEOUT_SECS))?; + let api_client = ApiClient::with_timeout( + host, + auth_method, + Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS), + )?; let mut provider = Self { api_client, @@ -242,7 +247,11 @@ impl DatabricksProvider { token_cache: token_cache.clone(), })); - let api_client = ApiClient::with_timeout(host, auth_method, Duration::from_secs(600))?; + let api_client = ApiClient::with_timeout( + host, + auth_method, + Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS), + )?; Ok(Self { api_client, diff --git a/crates/goose/src/providers/gcpvertexai.rs b/crates/goose/src/providers/gcpvertexai.rs index 24aed27c..45dd6cee 100644 --- a/crates/goose/src/providers/gcpvertexai.rs +++ b/crates/goose/src/providers/gcpvertexai.rs @@ -16,7 +16,10 @@ use url::Url; use crate::conversation::message::Message; use crate::model::ModelConfig; -use crate::providers::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata}; +use crate::providers::base::{ + ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata, + DEFAULT_PROVIDER_TIMEOUT_SECS, +}; use crate::providers::errors::ProviderError; use crate::providers::formats::gcpvertexai::{ @@ -33,8 +36,6 @@ use rmcp::model::Tool; const GCP_VERTEX_AI_PROVIDER_NAME: &str = "gcp_vertex_ai"; /// Base URL for GCP Vertex AI documentation const GCP_VERTEX_AI_DOC_URL: &str = "https://cloud.google.com/vertex-ai"; -/// Default timeout for API requests in seconds -const DEFAULT_TIMEOUT_SECS: u64 = 600; /// Default initial interval for retry (in milliseconds) const DEFAULT_INITIAL_RETRY_INTERVAL_MS: u64 = 5000; /// Default maximum number of retries @@ -171,7 +172,7 @@ impl GcpVertexAIProvider { let host = Self::build_host_url(&location); let client = Client::builder() - .timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS)) + .timeout(Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS)) .build()?; let auth = GcpAuth::new().await?; diff --git a/crates/goose/src/providers/gemini_oauth.rs b/crates/goose/src/providers/gemini_oauth.rs index 9dfaa9e4..cb4ef5b4 100644 --- a/crates/goose/src/providers/gemini_oauth.rs +++ b/crates/goose/src/providers/gemini_oauth.rs @@ -1,7 +1,10 @@ use crate::config::paths::Paths; use crate::conversation::message::Message; use crate::model::ModelConfig; -use crate::providers::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata}; +use crate::providers::base::{ + ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata, + DEFAULT_PROVIDER_TIMEOUT_SECS, +}; use crate::providers::errors::ProviderError; use crate::providers::formats::google::{create_request, response_to_streaming_message}; use crate::providers::google::GOOGLE_DOC_URL; @@ -35,11 +38,9 @@ use tokio_stream::StreamExt; use tokio_util::codec::{FramedRead, LinesCodec}; use tokio_util::io::StreamReader; -const HTTP_TIMEOUT_SECS: u64 = 600; - static HTTP_CLIENT: LazyLock = LazyLock::new(|| { reqwest::Client::builder() - .timeout(Duration::from_secs(HTTP_TIMEOUT_SECS)) + .timeout(Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS)) .build() .expect("failed to build HTTP client") }); diff --git a/crates/goose/src/providers/githubcopilot.rs b/crates/goose/src/providers/githubcopilot.rs index c65de163..35aecf17 100644 --- a/crates/goose/src/providers/githubcopilot.rs +++ b/crates/goose/src/providers/githubcopilot.rs @@ -14,7 +14,9 @@ use std::collections::HashMap; use std::path::PathBuf; use std::time::Duration; -use super::base::{Provider, ProviderDef, ProviderMetadata, ProviderUsage, Usage}; +use super::base::{ + Provider, ProviderDef, ProviderMetadata, ProviderUsage, Usage, DEFAULT_PROVIDER_TIMEOUT_SECS, +}; use super::errors::ProviderError; use super::formats::openai::{create_request, get_usage, response_to_message}; use super::openai_compatible::handle_response_openai_compat; @@ -224,7 +226,7 @@ impl GithubCopilotProvider { let copilot_token_url: Option = config.get_param("GITHUB_COPILOT_TOKEN_URL").ok(); let urls = GithubCopilotUrls::new(&host, copilot_token_url.as_deref()); let client = Client::builder() - .timeout(Duration::from_secs(600)) + .timeout(Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS)) .build()?; let cache = DiskCache::new(&host); let mu = tokio::sync::Mutex::new(RefCell::new(None)); diff --git a/crates/goose/src/providers/kimicode.rs b/crates/goose/src/providers/kimicode.rs index 2a46e14f..f1fad7b3 100644 --- a/crates/goose/src/providers/kimicode.rs +++ b/crates/goose/src/providers/kimicode.rs @@ -16,7 +16,10 @@ use tokio::pin; use tokio_util::io::StreamReader; use uuid::Uuid; -use super::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata}; +use super::base::{ + ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata, + DEFAULT_PROVIDER_TIMEOUT_SECS, +}; use super::errors::ProviderError; use super::formats::anthropic::{create_request, response_to_streaming_message}; use super::oauth_device_flow::{ @@ -162,7 +165,7 @@ impl KimiCodeProvider { pub async fn from_env(model: ModelConfig) -> Result { let model = model.with_fast(KIMI_CODE_DEFAULT_FAST_MODEL, KIMI_CODE_PROVIDER_NAME)?; let client = Client::builder() - .timeout(StdDuration::from_secs(600)) + .timeout(StdDuration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS)) .build()?; let device_id = Self::get_or_create_device_id().await?; Ok(Self { diff --git a/crates/goose/src/providers/litellm.rs b/crates/goose/src/providers/litellm.rs index 8008e065..8e6f08f5 100644 --- a/crates/goose/src/providers/litellm.rs +++ b/crates/goose/src/providers/litellm.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; use super::api_client::{ApiClient, AuthMethod}; use super::base::{ ConfigKey, MessageStream, ModelInfo, Provider, ProviderDef, ProviderMetadata, ProviderUsage, + DEFAULT_PROVIDER_TIMEOUT_SECS, }; use super::embedding::EmbeddingCapable; use super::errors::ProviderError; @@ -48,7 +49,9 @@ impl LiteLLMProvider { .get("LITELLM_CUSTOM_HEADERS") .cloned() .map(parse_custom_headers); - let timeout_secs: u64 = config.get_param("LITELLM_TIMEOUT").unwrap_or(600); + let timeout_secs: u64 = config + .get_param("LITELLM_TIMEOUT") + .unwrap_or(DEFAULT_PROVIDER_TIMEOUT_SECS); let auth = if api_key.is_empty() { AuthMethod::NoAuth diff --git a/crates/goose/src/providers/ollama.rs b/crates/goose/src/providers/ollama.rs index d397cf50..049e6a24 100644 --- a/crates/goose/src/providers/ollama.rs +++ b/crates/goose/src/providers/ollama.rs @@ -1,5 +1,8 @@ use super::api_client::{ApiClient, AuthMethod}; -use super::base::{ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata}; +use super::base::{ + ConfigKey, MessageStream, Provider, ProviderDef, ProviderMetadata, + DEFAULT_PROVIDER_TIMEOUT_SECS, +}; use super::errors::ProviderError; use super::inventory::InventoryIdentityInput; use super::openai_compatible::handle_status; @@ -26,7 +29,7 @@ use url::Url; const OLLAMA_PROVIDER_NAME: &str = "ollama"; pub const OLLAMA_HOST: &str = "localhost"; -pub const OLLAMA_TIMEOUT: u64 = 600; +pub const OLLAMA_TIMEOUT: u64 = DEFAULT_PROVIDER_TIMEOUT_SECS; pub const OLLAMA_DEFAULT_PORT: u16 = 11434; pub const OLLAMA_DEFAULT_MODEL: &str = "qwen3"; pub const OLLAMA_KNOWN_MODELS: &[&str] = &[ diff --git a/crates/goose/src/providers/openai.rs b/crates/goose/src/providers/openai.rs index c8c531aa..8c42cc4f 100644 --- a/crates/goose/src/providers/openai.rs +++ b/crates/goose/src/providers/openai.rs @@ -1,5 +1,7 @@ use super::api_client::{ApiClient, AuthMethod}; -use super::base::{ConfigKey, ModelInfo, Provider, ProviderDef, ProviderMetadata}; +use super::base::{ + ConfigKey, ModelInfo, Provider, ProviderDef, ProviderMetadata, DEFAULT_PROVIDER_TIMEOUT_SECS, +}; use super::embedding::{EmbeddingCapable, EmbeddingRequest, EmbeddingResponse}; use super::errors::ProviderError; use super::formats::openai::{create_request, get_usage, response_to_message}; @@ -229,7 +231,9 @@ impl OpenAiProvider { let organization: Option = config.get_param("OPENAI_ORGANIZATION").ok(); let project: Option = config.get_param("OPENAI_PROJECT").ok(); - let timeout_secs: u64 = config.get_param("OPENAI_TIMEOUT").unwrap_or(600); + let timeout_secs: u64 = config + .get_param("OPENAI_TIMEOUT") + .unwrap_or(DEFAULT_PROVIDER_TIMEOUT_SECS); let auth = match api_key { Some(key) if !key.is_empty() => AuthMethod::BearerToken(key), @@ -334,7 +338,9 @@ impl OpenAiProvider { Self::derive_base_path(url.path()) }; - let timeout_secs = config.timeout_seconds.unwrap_or(600); + let timeout_secs = config + .timeout_seconds + .unwrap_or(DEFAULT_PROVIDER_TIMEOUT_SECS); let auth = match api_key { Some(key) if !key.is_empty() => AuthMethod::BearerToken(key), diff --git a/crates/goose/src/providers/toolshim.rs b/crates/goose/src/providers/toolshim.rs index 61b48fe7..d0be2dd0 100644 --- a/crates/goose/src/providers/toolshim.rs +++ b/crates/goose/src/providers/toolshim.rs @@ -36,6 +36,7 @@ use super::ollama::OLLAMA_HOST; use crate::conversation::message::{Message, MessageContent}; use crate::conversation::Conversation; use crate::model::ModelConfig; +use crate::providers::base::DEFAULT_PROVIDER_TIMEOUT_SECS; use crate::providers::formats::openai::create_request; use anyhow::Result; use reqwest::Client; @@ -71,7 +72,7 @@ pub struct OllamaInterpreter { impl OllamaInterpreter { pub fn new() -> Result { let client = Client::builder() - .timeout(Duration::from_secs(600)) + .timeout(Duration::from_secs(DEFAULT_PROVIDER_TIMEOUT_SECS)) .build() .expect("Failed to create HTTP client");