diff --git a/rust/crates/api/src/error.rs b/rust/crates/api/src/error.rs index e8ec73a4..578800ff 100644 --- a/rust/crates/api/src/error.rs +++ b/rust/crates/api/src/error.rs @@ -135,6 +135,10 @@ impl ApiError { /// Return the `Retry-After` delay if this error came from a 429 response /// that included a `retry-after` header. Callers should prefer this value /// over the computed backoff delay when it exists. +<<<<<<< HEAD +======= + #[must_use] +>>>>>>> 07ce5aee (feat: API timeout config, Retry-After header support, and configurable retry) pub fn retry_after(&self) -> Option { match self { Self::Api { retry_after, .. } => *retry_after, diff --git a/rust/crates/api/src/http_client.rs b/rust/crates/api/src/http_client.rs index 648a0811..30e1ddd9 100644 --- a/rust/crates/api/src/http_client.rs +++ b/rust/crates/api/src/http_client.rs @@ -132,20 +132,20 @@ pub fn build_http_client() -> Result { #[must_use] pub fn build_http_client_or_default() -> reqwest::Client { build_http_client_with_opts(&ProxyConfig::from_env(), &TimeoutConfig::from_env()) +<<<<<<< HEAD .unwrap_or_else(|_| { reqwest::Client::builder() .user_agent("clawd-rust-tools/0.1") .build() .expect("default client with user_agent should always succeed") }) +======= + .unwrap_or_else(|_| reqwest::Client::new()) +>>>>>>> 07ce5aee (feat: API timeout config, Retry-After header support, and configurable retry) } /// Build a `reqwest::Client` from an explicit [`ProxyConfig`]. Used by tests /// and by callers that want to override process-level environment lookups. -/// -/// When `config.proxy_url` is set it overrides the per-scheme `http_proxy` -/// and `https_proxy` fields and is registered as both an HTTP and HTTPS -/// proxy so a single value can route every outbound request. pub fn build_http_client_with(config: &ProxyConfig) -> Result { build_http_client_with_opts(config, &TimeoutConfig::from_env()) } @@ -158,7 +158,10 @@ pub fn build_http_client_with_opts( ) -> Result { let mut builder = reqwest::Client::builder() .no_proxy() +<<<<<<< HEAD .user_agent("clawd-rust-tools/0.1") +======= +>>>>>>> 07ce5aee (feat: API timeout config, Retry-After header support, and configurable retry) .connect_timeout(timeout.connect_timeout) .timeout(timeout.request_timeout); diff --git a/rust/crates/api/src/lib.rs b/rust/crates/api/src/lib.rs index e96e92f8..93262914 100644 --- a/rust/crates/api/src/lib.rs +++ b/rust/crates/api/src/lib.rs @@ -12,8 +12,14 @@ pub use client::{ }; pub use error::ApiError; pub use http_client::{ +<<<<<<< HEAD build_http_client, build_http_client_or_default, build_http_client_with, build_http_client_with_opts, ProxyConfig, TimeoutConfig, +======= + TimeoutConfig, + build_http_client, build_http_client_or_default, build_http_client_with, + build_http_client_with_opts, ProxyConfig, +>>>>>>> 07ce5aee (feat: API timeout config, Retry-After header support, and configurable retry) }; pub use prompt_cache::{ CacheBreakEvent, PromptCache, PromptCacheConfig, PromptCachePaths, PromptCacheRecord, diff --git a/rust/crates/api/src/providers/anthropic.rs b/rust/crates/api/src/providers/anthropic.rs index 15f438cd..efe0da5c 100644 --- a/rust/crates/api/src/providers/anthropic.rs +++ b/rust/crates/api/src/providers/anthropic.rs @@ -467,8 +467,12 @@ impl AnthropicClient { break; } +<<<<<<< HEAD let delay = if let Some(retry_after) = last_error.as_ref().and_then(|e| e.retry_after()) { +======= + let delay = if let Some(retry_after) = last_error.as_ref().and_then(|e| e.retry_after()) { +>>>>>>> 07ce5aee (feat: API timeout config, Retry-After header support, and configurable retry) retry_after } else { self.jittered_backoff_for_attempt(attempts)? @@ -907,10 +911,14 @@ async fn expect_success(response: reqwest::Response) -> Result Option { +======= +fn parse_retry_after(headers: &reqwest::header::HeaderMap, status: reqwest::StatusCode) -> Option { +>>>>>>> 07ce5aee (feat: API timeout config, Retry-After header support, and configurable retry) if status != reqwest::StatusCode::TOO_MANY_REQUESTS { return None; } diff --git a/rust/crates/api/src/providers/openai_compat.rs b/rust/crates/api/src/providers/openai_compat.rs index 75460b42..bd5df820 100644 --- a/rust/crates/api/src/providers/openai_compat.rs +++ b/rust/crates/api/src/providers/openai_compat.rs @@ -1764,10 +1764,14 @@ async fn expect_success(response: reqwest::Response) -> Result Option { +======= +fn parse_retry_after(headers: &reqwest::header::HeaderMap, status: reqwest::StatusCode) -> Option { +>>>>>>> 07ce5aee (feat: API timeout config, Retry-After header support, and configurable retry) if status != reqwest::StatusCode::TOO_MANY_REQUESTS { return None; } diff --git a/rust/crates/runtime/src/config.rs b/rust/crates/runtime/src/config.rs index 65b2dd78..3813c220 100644 --- a/rust/crates/runtime/src/config.rs +++ b/rust/crates/runtime/src/config.rs @@ -152,7 +152,29 @@ pub struct LspServerConfig { pub command: String, pub args: Vec, pub enabled: bool, +<<<<<<< HEAD >>>>>>> 856409d3 (feat: full LSP (Language Server Protocol) integration) +======= +/// API timeout and retry configuration. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ApiTimeoutConfig { + /// Connect timeout in seconds. Defaults to 30. + pub connect_timeout_secs: u64, + /// Request timeout in seconds. Defaults to 300 (5 minutes). + pub request_timeout_secs: u64, + /// Maximum retry attempts on transient failures. Defaults to 8. + pub max_retries: u32, +} + +impl Default for ApiTimeoutConfig { + fn default() -> Self { + Self { + connect_timeout_secs: 30, + request_timeout_secs: 300, + max_retries: 8, + } + } +>>>>>>> 07ce5aee (feat: API timeout config, Retry-After header support, and configurable retry) } /// Structured feature configuration consumed by runtime subsystems. @@ -233,6 +255,7 @@ impl RuntimeProviderConfig { pub fn model(&self) -> Option<&str> { self.model.as_deref() } + api_timeout: ApiTimeoutConfig, } /// Ordered chain of fallback model identifiers used when the primary @@ -680,7 +703,16 @@ impl ConfigLoader { trusted_roots: parse_optional_trusted_roots(&merged_value)?, provider: parse_optional_provider_config(&merged_value)?, lsp: parse_optional_lsp_config(&merged_value)?, +<<<<<<< HEAD >>>>>>> 856409d3 (feat: full LSP (Language Server Protocol) integration) +======= + lsp_auto_start: merged_value + .as_object() + .and_then(|o| o.get("lspAutoStart")) + .and_then(JsonValue::as_bool) + .unwrap_or(true), + api_timeout: parse_optional_api_timeout_config(&merged_value)?, +>>>>>>> 07ce5aee (feat: API timeout config, Retry-After header support, and configurable retry) }; ConfigInspection { @@ -2142,8 +2174,15 @@ fn parse_optional_api_timeout_config(root: &JsonValue) -> Result>>>>>> 07ce5aee (feat: API timeout config, Retry-After header support, and configurable retry) let max_retries = optional_u64(obj, "maxRetries", context)? .map(|v| v as u32) .unwrap_or(8); diff --git a/rust/crates/runtime/src/lib.rs b/rust/crates/runtime/src/lib.rs index 23878eeb..f75db53e 100644 --- a/rust/crates/runtime/src/lib.rs +++ b/rust/crates/runtime/src/lib.rs @@ -84,6 +84,9 @@ pub use config::{ ConfigLoader, ConfigSource, LspServerConfig, McpConfigCollection, McpManagedProxyServerConfig, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig, McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig, + ApiTimeoutConfig, ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpConfigCollection, + McpManagedProxyServerConfig, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig, + McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig, ProviderFallbackConfig, ResolvedPermissionMode, RuntimeConfig, RuntimeFeatureConfig, RuntimeHookConfig, RuntimePermissionRuleConfig, RuntimePluginConfig, RuntimeProviderConfig, ScopedMcpServerConfig, CLAW_SETTINGS_SCHEMA_NAME, diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 42f24251..bef34c2c 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -14648,7 +14648,11 @@ mod tests { retryable: false, suggested_action: None, retry_after: None, +<<<<<<< HEAD }), +======= +}), +>>>>>>> 07ce5aee (feat: API timeout config, Retry-After header support, and configurable retry) }; let rendered = format_user_visible_api_error("session-issue-32", &error);