fix(openrouter): respect provider_timeout_secs and improve error messages (#3973)
* fix(openrouter): wire provider_timeout_secs through factory Apply the configured provider_timeout_secs to OpenRouterProvider in the provider factory, matching the pattern used for compatible providers. * fix(openrouter): add timeout_secs field to OpenRouterProvider Add a configurable timeout_secs field (default 120s) and a with_timeout_secs() builder method so the HTTP client timeout can be overridden via provider config instead of being hardcoded. * refactor(openrouter): improve response decode error messages Read the response body as text first, then parse with serde_json::from_str so that decode failures include a truncated snippet of the raw body for easier debugging. * test(openrouter): add timeout_secs configuration tests Verify that the default timeout is 120s and that with_timeout_secs correctly overrides it. * style: run rustfmt on openrouter.rs
This commit is contained in:
parent
8f241cd4b7
commit
8ee3e71e90
@ -1119,10 +1119,13 @@ fn create_provider_with_url_and_options(
|
||||
)?))
|
||||
}
|
||||
// ── Primary providers (custom implementations) ───────
|
||||
"openrouter" => Ok(Box::new(openrouter::OpenRouterProvider::new(
|
||||
key,
|
||||
options.provider_timeout_secs,
|
||||
))),
|
||||
"openrouter" => {
|
||||
let mut p = openrouter::OpenRouterProvider::new(key);
|
||||
if let Some(t) = options.provider_timeout_secs {
|
||||
p = p.with_timeout_secs(t);
|
||||
}
|
||||
Ok(Box::new(p))
|
||||
}
|
||||
"anthropic" => Ok(Box::new(anthropic::AnthropicProvider::new(key))),
|
||||
"openai" => Ok(Box::new(openai::OpenAiProvider::with_base_url(api_url, key))),
|
||||
// Ollama uses api_url for custom base URL (e.g. remote Ollama instance)
|
||||
|
||||
@ -4,6 +4,7 @@ use crate::providers::traits::{
|
||||
Provider, ProviderCapabilities, TokenUsage, ToolCall as ProviderToolCall,
|
||||
};
|
||||
use crate::tools::ToolSpec;
|
||||
use anyhow::Context as _;
|
||||
use async_trait::async_trait;
|
||||
use reqwest::Client;
|
||||
use serde::de::DeserializeOwned;
|
||||
@ -154,12 +155,16 @@ impl OpenRouterProvider {
|
||||
pub fn new(credential: Option<&str>, timeout_secs: Option<u64>) -> Self {
|
||||
Self {
|
||||
credential: credential.map(ToString::to_string),
|
||||
timeout_secs: timeout_secs
|
||||
.filter(|secs| *secs > 0)
|
||||
.unwrap_or(DEFAULT_OPENROUTER_TIMEOUT_SECS),
|
||||
timeout_secs: 120,
|
||||
}
|
||||
}
|
||||
|
||||
/// Override the HTTP request timeout for LLM API calls.
|
||||
pub fn with_timeout_secs(mut self, secs: u64) -> Self {
|
||||
self.timeout_secs = secs;
|
||||
self
|
||||
}
|
||||
|
||||
fn convert_tools(tools: Option<&[ToolSpec]>) -> Option<Vec<NativeToolSpec>> {
|
||||
let items = tools?;
|
||||
if items.is_empty() {
|
||||
@ -339,7 +344,7 @@ impl OpenRouterProvider {
|
||||
crate::config::build_runtime_proxy_client_with_timeouts(
|
||||
"provider.openrouter",
|
||||
self.timeout_secs,
|
||||
OPENROUTER_CONNECT_TIMEOUT_SECS,
|
||||
10,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -412,9 +417,13 @@ impl Provider for OpenRouterProvider {
|
||||
return Err(super::api_error("OpenRouter", response).await);
|
||||
}
|
||||
|
||||
let body = Self::read_response_body("OpenRouter", response).await?;
|
||||
let chat_response =
|
||||
Self::parse_response_body::<ApiChatResponse>("OpenRouter", &body, "chat-completions")?;
|
||||
let text = response.text().await?;
|
||||
let chat_response: ApiChatResponse = serde_json::from_str(&text).with_context(|| {
|
||||
format!(
|
||||
"OpenRouter: failed to decode response body: {}",
|
||||
&text[..text.len().min(500)]
|
||||
)
|
||||
})?;
|
||||
|
||||
chat_response
|
||||
.choices
|
||||
@ -461,9 +470,13 @@ impl Provider for OpenRouterProvider {
|
||||
return Err(super::api_error("OpenRouter", response).await);
|
||||
}
|
||||
|
||||
let body = Self::read_response_body("OpenRouter", response).await?;
|
||||
let chat_response =
|
||||
Self::parse_response_body::<ApiChatResponse>("OpenRouter", &body, "chat-completions")?;
|
||||
let text = response.text().await?;
|
||||
let chat_response: ApiChatResponse = serde_json::from_str(&text).with_context(|| {
|
||||
format!(
|
||||
"OpenRouter: failed to decode response body: {}",
|
||||
&text[..text.len().min(500)]
|
||||
)
|
||||
})?;
|
||||
|
||||
chat_response
|
||||
.choices
|
||||
@ -508,9 +521,14 @@ impl Provider for OpenRouterProvider {
|
||||
return Err(super::api_error("OpenRouter", response).await);
|
||||
}
|
||||
|
||||
let body = Self::read_response_body("OpenRouter", response).await?;
|
||||
let native_response =
|
||||
Self::parse_response_body::<NativeChatResponse>("OpenRouter", &body, "native chat")?;
|
||||
let text = response.text().await?;
|
||||
let native_response: NativeChatResponse =
|
||||
serde_json::from_str(&text).with_context(|| {
|
||||
format!(
|
||||
"OpenRouter: failed to decode response body: {}",
|
||||
&text[..text.len().min(500)]
|
||||
)
|
||||
})?;
|
||||
let usage = native_response.usage.map(|u| TokenUsage {
|
||||
input_tokens: u.prompt_tokens,
|
||||
output_tokens: u.completion_tokens,
|
||||
@ -602,9 +620,14 @@ impl Provider for OpenRouterProvider {
|
||||
return Err(super::api_error("OpenRouter", response).await);
|
||||
}
|
||||
|
||||
let body = Self::read_response_body("OpenRouter", response).await?;
|
||||
let native_response =
|
||||
Self::parse_response_body::<NativeChatResponse>("OpenRouter", &body, "native chat")?;
|
||||
let text = response.text().await?;
|
||||
let native_response: NativeChatResponse =
|
||||
serde_json::from_str(&text).with_context(|| {
|
||||
format!(
|
||||
"OpenRouter: failed to decode response body: {}",
|
||||
&text[..text.len().min(500)]
|
||||
)
|
||||
})?;
|
||||
let usage = native_response.usage.map(|u| TokenUsage {
|
||||
input_tokens: u.prompt_tokens,
|
||||
output_tokens: u.completion_tokens,
|
||||
@ -1115,4 +1138,20 @@ mod tests {
|
||||
assert!(json.contains("reasoning_content"));
|
||||
assert!(json.contains("thinking..."));
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// timeout_secs configuration tests
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
#[test]
|
||||
fn default_timeout_is_120() {
|
||||
let provider = OpenRouterProvider::new(Some("key"));
|
||||
assert_eq!(provider.timeout_secs, 120);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_timeout_secs_overrides_default() {
|
||||
let provider = OpenRouterProvider::new(Some("key")).with_timeout_secs(300);
|
||||
assert_eq!(provider.timeout_secs, 300);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user