fix(tui): install rustls provider before HTTP clients
Install the ring rustls provider through a shared TUI helper and route reqwest client construction through it so no-provider TLS builds do not panic in engine, runtime API, tool, MCP, config, and test paths. Keep the skill-installer integration include compatible with a local helper, and pin prompt byte-stability tests to an isolated home/skills environment under the shared env lock. Verification: cargo fmt --all -- --check; git diff --check; ./scripts/release/check-versions.sh; cargo clippy --workspace --all-features --locked -- -D warnings; cargo test --workspace --all-features --locked; focused skill_install, finance, goal-tool, and MCP reruns.
This commit is contained in:
@@ -140,6 +140,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
dedicated hydrated status, so it is no longer indistinguishable from a real
|
||||
successful execution. A hydrated row also ranks with active work rather than
|
||||
completed successes (#2648).
|
||||
- TUI HTTP clients now install the Rustls ring crypto provider before building
|
||||
`reqwest` clients, covering engine, runtime API, tool, MCP, config, and skill
|
||||
download paths. This keeps the no-provider TLS build from panicking during
|
||||
tests or embedded startup paths that do not enter through the main binary.
|
||||
- Prompt byte-stability tests now pin their temporary home and skills
|
||||
environment under the shared test-env lock so global skill directories cannot
|
||||
perturb deterministic prompt bytes during parallel test runs.
|
||||
|
||||
### Community
|
||||
|
||||
|
||||
@@ -140,6 +140,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
dedicated hydrated status, so it is no longer indistinguishable from a real
|
||||
successful execution. A hydrated row also ranks with active work rather than
|
||||
completed successes (#2648).
|
||||
- TUI HTTP clients now install the Rustls ring crypto provider before building
|
||||
`reqwest` clients, covering engine, runtime API, tool, MCP, config, and skill
|
||||
download paths. This keeps the no-provider TLS build from panicking during
|
||||
tests or embedded startup paths that do not enter through the main binary.
|
||||
- Prompt byte-stability tests now pin their temporary home and skills
|
||||
environment under the shared test-env lock so global skill directories cannot
|
||||
perturb deterministic prompt bytes during parallel test runs.
|
||||
|
||||
### Community
|
||||
|
||||
|
||||
@@ -625,7 +625,7 @@ impl DeepSeekClient {
|
||||
base_url: &str,
|
||||
) -> Result<reqwest::Client> {
|
||||
let headers = build_default_headers(api_key, extra_headers, api_provider, base_url)?;
|
||||
let mut builder = reqwest::Client::builder()
|
||||
let mut builder = crate::tls::reqwest_client_builder()
|
||||
.default_headers(headers)
|
||||
.user_agent(concat!(
|
||||
"Mozilla/5.0 (compatible; codewhale/",
|
||||
|
||||
@@ -5202,7 +5202,7 @@ fn refresh_kimi_oauth_token(refresh_token: &str) -> Result<KimiOAuthCredential>
|
||||
.or_else(|_| std::env::var("KIMI_OAUTH_HOST"))
|
||||
.unwrap_or_else(|_| "https://auth.kimi.com".to_string());
|
||||
let url = format!("{}/api/oauth/token", oauth_host.trim_end_matches('/'));
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
let client = crate::tls::reqwest_blocking_client_builder()
|
||||
.timeout(Duration::from_secs(15))
|
||||
.build()
|
||||
.context("Failed to build Kimi OAuth refresh client")?;
|
||||
|
||||
@@ -405,7 +405,7 @@ pub async fn start_web_editor(app: &App, config: &Config) -> Result<WebConfigSes
|
||||
let poll_tx = tx.clone();
|
||||
let poll_url = format!("{url}/api/session");
|
||||
let poll_task = tokio::spawn(async move {
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
let mut last: Option<ConfigUiDocument> = Some(app_snapshot);
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_millis(750)).await;
|
||||
|
||||
@@ -621,6 +621,8 @@ impl Engine {
|
||||
|
||||
/// Create a new engine with the given configuration
|
||||
pub fn new(config: EngineConfig, api_config: &Config) -> (Self, EngineHandle) {
|
||||
crate::tls::ensure_rustls_crypto_provider();
|
||||
|
||||
if let Some(objective) = normalized_goal_objective(config.goal_objective.as_deref()) {
|
||||
sync_goal_state_from_host(&config.goal_state, Some(&objective), None, false);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ mod task_manager;
|
||||
#[cfg(test)]
|
||||
mod test_support;
|
||||
mod theme_qa_audit;
|
||||
mod tls;
|
||||
mod tool_output_receipts;
|
||||
mod tools;
|
||||
mod tui;
|
||||
@@ -111,7 +112,7 @@ fn configure_windows_console_utf8() {
|
||||
fn configure_windows_console_utf8() {}
|
||||
|
||||
fn install_rustls_crypto_provider() {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
crate::tls::ensure_rustls_crypto_provider();
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
|
||||
@@ -1319,8 +1319,8 @@ impl McpConnection {
|
||||
// local Clash / Shadowsocks tunnel, etc. previously had MCP
|
||||
// HTTP traffic bypass the proxy entirely while every other
|
||||
// tool on the box (curl, npm, …) used it.
|
||||
let mut client_builder =
|
||||
reqwest::Client::builder().timeout(Duration::from_secs(connect_timeout_secs));
|
||||
let mut client_builder = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_secs(connect_timeout_secs));
|
||||
let env_proxy_url = std::env::var("HTTPS_PROXY")
|
||||
.or_else(|_| std::env::var("https_proxy"))
|
||||
.or_else(|_| std::env::var("HTTP_PROXY"))
|
||||
@@ -2942,7 +2942,7 @@ mod tests {
|
||||
|
||||
fn test_http_client() -> reqwest::Client {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
reqwest::Client::new()
|
||||
crate::tls::reqwest_client()
|
||||
}
|
||||
|
||||
async fn lock_mcp_loopback_tests() -> tokio::sync::MutexGuard<'static, ()> {
|
||||
|
||||
@@ -2566,7 +2566,7 @@ mod tests {
|
||||
// in the cached prefix must produce identical bytes given identical
|
||||
// inputs across calls.
|
||||
|
||||
use crate::test_support::assert_byte_identical;
|
||||
use crate::test_support::{EnvVarGuard, assert_byte_identical};
|
||||
|
||||
#[test]
|
||||
fn compose_prompt_is_byte_stable_across_calls() {
|
||||
@@ -2592,7 +2592,12 @@ mod tests {
|
||||
// identical bytes. This pins the most representative production
|
||||
// surface (engine.rs builds the system prompt via this fn or
|
||||
// its sibling _and_skills variant on every turn).
|
||||
let _env_guard = crate::test_support::lock_test_env();
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let home = tmp.path().join("home");
|
||||
let _home = EnvVarGuard::set("HOME", home.as_os_str());
|
||||
let _userprofile = EnvVarGuard::set("USERPROFILE", home.as_os_str());
|
||||
let _skills_dir = EnvVarGuard::remove("DEEPSEEK_SKILLS_DIR");
|
||||
let workspace = tmp.path();
|
||||
|
||||
for mode in [AppMode::Agent, AppMode::Yolo, AppMode::Plan] {
|
||||
|
||||
@@ -2721,7 +2721,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let health: serde_json::Value = client
|
||||
.get(format!("http://{addr}/health"))
|
||||
@@ -2786,7 +2786,7 @@ mod tests {
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let health = client
|
||||
.get(format!("http://{addr}/health"))
|
||||
@@ -2825,7 +2825,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let workspace: serde_json::Value = client
|
||||
.get(format!("http://{addr}/v1/workspace/status"))
|
||||
@@ -2949,7 +2949,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let resp = client
|
||||
.post(format!("http://{addr}/v1/stream"))
|
||||
@@ -2966,7 +2966,7 @@ mod tests {
|
||||
let Some((addr, runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let created: serde_json::Value = client
|
||||
.post(format!("http://{addr}/v1/threads"))
|
||||
@@ -3238,7 +3238,7 @@ mod tests {
|
||||
let Some((addr, runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let created: serde_json::Value = client
|
||||
.post(format!("http://{addr}/v1/threads"))
|
||||
@@ -3364,7 +3364,7 @@ mod tests {
|
||||
let Some((addr, runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let created: serde_json::Value = client
|
||||
.post(format!("http://{addr}/v1/threads"))
|
||||
@@ -3587,7 +3587,7 @@ mod tests {
|
||||
let Some((addr, runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
// Create a thread and install a mock engine so /v1/stream doesn't call the real API.
|
||||
let created: serde_json::Value = client
|
||||
@@ -3704,7 +3704,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let resp = client
|
||||
.get(format!("http://{addr}/v1/sessions/nonexistent_id"))
|
||||
@@ -3721,7 +3721,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let get_resp = client
|
||||
.get(format!("http://{addr}/v1/sessions/invalid%20id"))
|
||||
@@ -3753,7 +3753,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let resp = client
|
||||
.post(format!(
|
||||
@@ -3808,7 +3808,7 @@ mod tests {
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let resp = client
|
||||
.post(format!(
|
||||
@@ -3849,7 +3849,7 @@ mod tests {
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let created: serde_json::Value = client
|
||||
.post(format!("http://{addr}/v1/threads"))
|
||||
@@ -3956,7 +3956,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let resp = client
|
||||
.post(format!("http://{addr}/v1/sessions"))
|
||||
@@ -3974,7 +3974,7 @@ mod tests {
|
||||
let Some((addr, runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let created: serde_json::Value = client
|
||||
.post(format!("http://{addr}/v1/threads"))
|
||||
@@ -4086,7 +4086,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
let resp = client
|
||||
.delete(format!("http://{addr}/v1/sessions/nonexistent-id"))
|
||||
.send()
|
||||
@@ -4121,7 +4121,7 @@ mod tests {
|
||||
let _ = axum::serve(listener, router).await;
|
||||
});
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
// The user-supplied origin is allowed.
|
||||
let resp = client
|
||||
@@ -4191,7 +4191,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let created: serde_json::Value = client
|
||||
.post(format!("http://{addr}/v1/threads"))
|
||||
@@ -4277,7 +4277,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
// Two threads — keep one active, archive the other.
|
||||
let active: serde_json::Value = client
|
||||
@@ -4388,7 +4388,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let body: serde_json::Value = client
|
||||
.get(format!("http://{addr}/v1/usage"))
|
||||
@@ -4447,7 +4447,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
let info: serde_json::Value = client
|
||||
.get(format!("http://{addr}/v1/runtime/info"))
|
||||
.send()
|
||||
@@ -4478,7 +4478,7 @@ mod tests {
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
let disabled = client.get(format!("http://{addr}/mobile")).send().await?;
|
||||
assert_eq!(disabled.status(), StatusCode::NOT_FOUND);
|
||||
handle.abort();
|
||||
@@ -4517,7 +4517,7 @@ mod tests {
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let unauthorized = client.get(format!("http://{addr}/mobile")).send().await?;
|
||||
assert_eq!(unauthorized.status(), StatusCode::UNAUTHORIZED);
|
||||
@@ -4552,7 +4552,7 @@ mod tests {
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
|
||||
let page = client
|
||||
.get(format!("http://{addr}/mobile"))
|
||||
@@ -4577,7 +4577,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
let resp = client
|
||||
.post(format!("http://{addr}/v1/approvals/no_such_id"))
|
||||
.json(&json!({ "decision": "allow" }))
|
||||
@@ -4594,7 +4594,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
let resp = client
|
||||
.post(format!("http://{addr}/v1/approvals/whatever"))
|
||||
.json(&json!({ "decision": "yolo" }))
|
||||
@@ -4611,7 +4611,7 @@ mod tests {
|
||||
let Some((addr, runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
let rx = runtime_threads.register_pending_approval_for_test("ext_id");
|
||||
|
||||
let resp = client
|
||||
@@ -4640,7 +4640,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
let body: serde_json::Value = client
|
||||
.get(format!("http://{addr}/v1/skills"))
|
||||
.send()
|
||||
@@ -4663,7 +4663,7 @@ mod tests {
|
||||
let Some((addr, _runtime_threads, handle)) = spawn_test_server().await? else {
|
||||
return Ok(());
|
||||
};
|
||||
let client = reqwest::Client::new();
|
||||
let client = crate::tls::reqwest_client();
|
||||
let resp = client
|
||||
.post(format!("http://{addr}/v1/skills/no-such-skill"))
|
||||
.json(&json!({ "enabled": false }))
|
||||
|
||||
@@ -54,7 +54,7 @@ impl OpenSandboxBackend {
|
||||
/// `Authorization: Bearer <key>` when set. `timeout_secs` controls the
|
||||
/// HTTP request timeout.
|
||||
pub fn new(base_url: String, api_key: Option<String>, timeout_secs: u64) -> Result<Self> {
|
||||
let client = reqwest::Client::builder()
|
||||
let client = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_secs(timeout_secs))
|
||||
.build()
|
||||
.context("failed to construct HTTP client for OpenSandbox backend")?;
|
||||
|
||||
@@ -45,6 +45,11 @@ use thiserror::Error;
|
||||
|
||||
use crate::network_policy::{Decision, NetworkPolicy, host_from_url};
|
||||
|
||||
fn reqwest_client() -> reqwest::Client {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
reqwest::Client::new()
|
||||
}
|
||||
|
||||
/// Cache directory for registry-synced skills.
|
||||
///
|
||||
/// Lives at `~/.codewhale/cache/skills/` so it's separate from user-installed
|
||||
@@ -497,7 +502,9 @@ pub async fn fetch_registry(
|
||||
Decision::Deny => return Ok(RegistryFetchResult::Denied(host)),
|
||||
Decision::Prompt => return Ok(RegistryFetchResult::NeedsApproval(host)),
|
||||
}
|
||||
let body = reqwest::get(registry_url)
|
||||
let body = reqwest_client()
|
||||
.get(registry_url)
|
||||
.send()
|
||||
.await
|
||||
.with_context(|| format!("failed to fetch registry {registry_url}"))?
|
||||
.error_for_status()
|
||||
@@ -665,7 +672,7 @@ async fn sync_one_skill(
|
||||
.flatten();
|
||||
|
||||
// Build the request — add If-None-Match if we have a cached ETag.
|
||||
let client = reqwest::Client::new();
|
||||
let client = reqwest_client();
|
||||
let mut req = client.get(url);
|
||||
if let Some(ref meta) = existing_meta
|
||||
&& let Some(ref etag) = meta.etag
|
||||
@@ -981,7 +988,9 @@ enum DownloadAttempt {
|
||||
/// would push the buffer over `max_size * 4` (the *4 accounts for compression;
|
||||
/// the unpack step still enforces `max_size` on the *uncompressed* bytes).
|
||||
async fn download_with_cap(url: &str, max_size: u64) -> Result<DownloadAttempt> {
|
||||
let resp = reqwest::get(url)
|
||||
let resp = reqwest_client()
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
.with_context(|| format!("failed to GET {url}"))?;
|
||||
let status = resp.status();
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
pub(crate) fn ensure_rustls_crypto_provider() {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn reqwest_client() -> reqwest::Client {
|
||||
ensure_rustls_crypto_provider();
|
||||
reqwest::Client::new()
|
||||
}
|
||||
|
||||
pub(crate) fn reqwest_client_builder() -> reqwest::ClientBuilder {
|
||||
ensure_rustls_crypto_provider();
|
||||
reqwest::Client::builder()
|
||||
}
|
||||
|
||||
pub(crate) fn reqwest_blocking_client_builder() -> reqwest::blocking::ClientBuilder {
|
||||
ensure_rustls_crypto_provider();
|
||||
reqwest::blocking::Client::builder()
|
||||
}
|
||||
@@ -163,7 +163,7 @@ impl ToolSpec for FetchUrlTool {
|
||||
|
||||
let resp = loop {
|
||||
let dns_pinning = validate_fetch_target(¤t_url, context).await?;
|
||||
let mut client_builder = reqwest::Client::builder()
|
||||
let mut client_builder = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_millis(timeout_ms))
|
||||
.user_agent(USER_AGENT)
|
||||
.redirect(reqwest::redirect::Policy::none());
|
||||
|
||||
@@ -151,7 +151,7 @@ impl FinanceTool {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
endpoints: FinanceEndpoints::default(),
|
||||
client: Client::builder()
|
||||
client: crate::tls::reqwest_client_builder()
|
||||
.user_agent(USER_AGENT)
|
||||
.build()
|
||||
.expect("failed to build HTTP client"),
|
||||
@@ -165,7 +165,7 @@ impl FinanceTool {
|
||||
quote_base: quote_base.into(),
|
||||
chart_base: chart_base.into(),
|
||||
},
|
||||
client: Client::builder()
|
||||
client: crate::tls::reqwest_client_builder()
|
||||
.user_agent(USER_AGENT)
|
||||
.build()
|
||||
.expect("failed to build HTTP client"),
|
||||
|
||||
@@ -774,7 +774,7 @@ async fn run_search(
|
||||
timeout_ms: u64,
|
||||
domains: &[String],
|
||||
) -> Result<(Vec<SearchEntry>, String, Option<String>), ToolError> {
|
||||
let client = reqwest::Client::builder()
|
||||
let client = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_millis(timeout_ms))
|
||||
.user_agent(USER_AGENT)
|
||||
.build()
|
||||
@@ -970,7 +970,7 @@ async fn run_image_search(
|
||||
timeout_ms: u64,
|
||||
domains: &[String],
|
||||
) -> Result<(Vec<ImageResultEntry>, Option<String>), ToolError> {
|
||||
let client = reqwest::Client::builder()
|
||||
let client = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_millis(timeout_ms))
|
||||
.user_agent(USER_AGENT)
|
||||
.build()
|
||||
@@ -1123,7 +1123,7 @@ fn check_network_policy(url: &str, context: &ToolContext) -> Result<(), ToolErro
|
||||
}
|
||||
|
||||
async fn fetch_page(url: &str, timeout_ms: u64) -> Result<WebPage, ToolError> {
|
||||
let client = reqwest::Client::builder()
|
||||
let client = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_millis(timeout_ms))
|
||||
.user_agent(USER_AGENT)
|
||||
.build()
|
||||
|
||||
@@ -242,7 +242,7 @@ impl ToolSpec for WebSearchTool {
|
||||
}
|
||||
|
||||
let decider = context.network_policy.as_ref();
|
||||
let client = reqwest::Client::builder()
|
||||
let client = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_millis(timeout_ms))
|
||||
.user_agent(USER_AGENT)
|
||||
.build()
|
||||
@@ -382,7 +382,7 @@ impl WebSearchTool {
|
||||
)
|
||||
})?;
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
let client = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_millis(timeout_ms))
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
@@ -479,7 +479,7 @@ impl WebSearchTool {
|
||||
)
|
||||
})?;
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
let client = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_millis(timeout_ms))
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
@@ -588,7 +588,7 @@ impl WebSearchTool {
|
||||
.or(env_key.as_deref())
|
||||
.unwrap_or(METASO_DEFAULT_API_KEY);
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
let client = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_millis(timeout_ms))
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
@@ -693,7 +693,7 @@ impl WebSearchTool {
|
||||
)
|
||||
})?;
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
let client = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_millis(timeout_ms))
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
@@ -778,7 +778,7 @@ impl WebSearchTool {
|
||||
// when it exceeds 90_000 ms.
|
||||
let effective_timeout = timeout_ms.max(90_000);
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
let client = crate::tls::reqwest_client_builder()
|
||||
.connect_timeout(Duration::from_secs(15))
|
||||
.timeout(Duration::from_millis(effective_timeout))
|
||||
.tcp_keepalive(Some(Duration::from_secs(30)))
|
||||
|
||||
@@ -1080,7 +1080,7 @@ const BALANCE_FETCH_COOLDOWN: Duration = Duration::from_secs(60);
|
||||
/// Shared `reqwest::Client` for balance fetches so connection pools are
|
||||
/// reused across successive background polls.
|
||||
static BALANCE_CLIENT: LazyLock<::reqwest::Client> = LazyLock::new(|| {
|
||||
::reqwest::Client::builder()
|
||||
crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()
|
||||
.unwrap_or_default()
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct ImageAnalyzeTool {
|
||||
impl ImageAnalyzeTool {
|
||||
#[must_use]
|
||||
pub fn new(config: VisionModelConfig) -> Self {
|
||||
let client = reqwest::Client::builder()
|
||||
let client = crate::tls::reqwest_client_builder()
|
||||
.timeout(Duration::from_secs(120))
|
||||
.build()
|
||||
.expect("Failed to build HTTP client");
|
||||
|
||||
@@ -155,6 +155,7 @@ v0.9 branch so the remaining Windows/manual checks are explicit.
|
||||
| #2755 roll back provider after auth failure | Draft / forwarded | Snapshot+rollback of provider/model on auth failure (#2754). Design is sound and tested, but author opened it as draft noting they could not reproduce the live Moonshot auth failure end-to-end. Help-forward: needs maintainer validation against a real provider auth failure (engine respawn + model restore). Credit @cyq1017. |
|
||||
| #2756 Xiaomi MiMo Token Plan region docs | Mergeable / locally harvested | Docs-only; verified accurate against branch `resolve_xiaomi_mimo_base_url` (tp- keys default to `token-plan-sgp`, pay-as-you-go to `api.xiaomimimo.com`, CN requires explicit `base_url`). Conflict on the CONFIGURATION.md provider bullet resolved by keeping the branch `path_suffix` bullet and adopting the PR's accurate base_url wording. Credit @xyuai; comment/close after branch is public. Fixes #2735. |
|
||||
| #2757 hydrated deferred-tool render | Mergeable / locally harvested | Harvested in full (6 files): deferred-tool first-use schema hydration now renders as "tool loaded — retry required" via `ToolStatus::Hydrated` instead of "run done". Local correction: hydrated rows rank with active work (rank 1) not completed successes; kept the contributor's hydration detection (sole emitter always sets `executed=false`, consistent with the engine's own check, so the missing-field default was not changed). `cargo test -p codewhale-tui --bin codewhale-tui --locked hydrat` (6 pass), clippy clean. Credit @mvanhorn; comment/close after branch is public. Fixes #2648. |
|
||||
| Local verification sweep stabilizer | Added after the full workspace verification sweep found test-only no-provider TLS panics and prompt byte instability. | Shared TUI Rustls provider helpers now wrap `reqwest` client construction across engine, runtime API, tool, MCP, config, and skill paths; the skill-installer integration include keeps its own local helper. Prompt byte-stability tests pin home and skills env under the shared test-env lock. Evidence: `cargo fmt --all -- --check`, `git diff --check`, `./scripts/release/check-versions.sh`, `cargo clippy --workspace --all-features --locked -- -D warnings`, focused skill/finance/goal/MCP reruns, and `cargo test --workspace --all-features --locked` all passed locally. |
|
||||
|
||||
## Issue Reduction Strategy
|
||||
|
||||
|
||||
Reference in New Issue
Block a user