Kill reqwest — minimal HTTP client on raw hyper + tokio-rustls
New src/agent/api/http.rs: ~240 lines, supports GET/POST, JSON/form bodies, SSE streaming via chunk(), TLS via rustls. No tracing dep. Removes reqwest from the main crate and telegram channel crate. Cargo.lock drops ~900 lines of transitive dependencies. tracing now only pulled in by tui-markdown. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
a421c3c9f3
commit
1cf4f504c0
9 changed files with 360 additions and 915 deletions
|
|
@ -6,6 +6,7 @@
|
|||
// Diagnostics: anomalies always logged to debug panel.
|
||||
// Set POC_DEBUG=1 for verbose per-turn logging.
|
||||
|
||||
pub mod http;
|
||||
pub mod parsing;
|
||||
pub mod types;
|
||||
mod openai;
|
||||
|
|
@ -13,9 +14,10 @@ mod openai;
|
|||
pub use types::*;
|
||||
|
||||
use anyhow::Result;
|
||||
use reqwest::Client;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use self::http::{HttpClient, HttpResponse};
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::agent::tools::{self as agent_tools, summarize_args, ActiveToolCall};
|
||||
|
|
@ -77,7 +79,7 @@ pub enum StreamEvent {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct ApiClient {
|
||||
client: Client,
|
||||
client: HttpClient,
|
||||
api_key: String,
|
||||
pub model: String,
|
||||
base_url: String,
|
||||
|
|
@ -85,11 +87,10 @@ pub struct ApiClient {
|
|||
|
||||
impl ApiClient {
|
||||
pub fn new(base_url: &str, api_key: &str, model: &str) -> Self {
|
||||
let client = Client::builder()
|
||||
let client = HttpClient::builder()
|
||||
.connect_timeout(Duration::from_secs(30))
|
||||
.timeout(Duration::from_secs(600))
|
||||
.build()
|
||||
.expect("failed to build HTTP client");
|
||||
.build();
|
||||
|
||||
Self {
|
||||
client,
|
||||
|
|
@ -198,14 +199,14 @@ impl ApiClient {
|
|||
|
||||
/// Send an HTTP request and check for errors. Shared by both backends.
|
||||
pub(crate) async fn send_and_check(
|
||||
client: &Client,
|
||||
client: &HttpClient,
|
||||
url: &str,
|
||||
body: &impl serde::Serialize,
|
||||
auth_header: (&str, &str),
|
||||
extra_headers: &[(&str, &str)],
|
||||
debug_label: &str,
|
||||
request_json: Option<&str>,
|
||||
) -> Result<reqwest::Response> {
|
||||
) -> Result<HttpResponse> {
|
||||
let debug = std::env::var("POC_DEBUG").is_ok();
|
||||
let start = Instant::now();
|
||||
|
||||
|
|
@ -219,49 +220,36 @@ pub(crate) async fn send_and_check(
|
|||
);
|
||||
}
|
||||
|
||||
let mut req = client
|
||||
.post(url)
|
||||
.header(auth_header.0, auth_header.1)
|
||||
.header("Content-Type", "application/json");
|
||||
let mut headers: Vec<(&str, &str)> = Vec::with_capacity(extra_headers.len() + 1);
|
||||
headers.push(auth_header);
|
||||
headers.extend_from_slice(extra_headers);
|
||||
|
||||
for (name, value) in extra_headers {
|
||||
req = req.header(*name, *value);
|
||||
}
|
||||
|
||||
let response = req
|
||||
.json(body)
|
||||
.send()
|
||||
let response = client
|
||||
.send_json("POST", url, &headers, body)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let cause = if e.is_connect() {
|
||||
let msg = e.to_string();
|
||||
let cause = if msg.contains("connect timeout") || msg.contains("TCP connect") {
|
||||
"connection refused"
|
||||
} else if e.is_timeout() {
|
||||
} else if msg.contains("request timeout") {
|
||||
"request timed out"
|
||||
} else if e.is_request() {
|
||||
"request error"
|
||||
} else {
|
||||
"unknown"
|
||||
"request error"
|
||||
};
|
||||
anyhow::anyhow!("{} ({}): {:?}", cause, url, e.without_url())
|
||||
anyhow::anyhow!("{} ({}): {}", cause, url, msg)
|
||||
})?;
|
||||
|
||||
let status = response.status();
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
if debug {
|
||||
// Log interesting response headers
|
||||
let headers = response.headers();
|
||||
for name in [
|
||||
"x-ratelimit-remaining",
|
||||
"x-ratelimit-limit",
|
||||
"x-request-id",
|
||||
] {
|
||||
if let Some(val) = headers.get(name) {
|
||||
dbglog!(
|
||||
"header {}: {}",
|
||||
name,
|
||||
val.to_str().unwrap_or("?")
|
||||
);
|
||||
if let Some(val) = response.header(name) {
|
||||
dbglog!("header {}: {}", name, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -357,7 +345,7 @@ impl SseReader {
|
|||
/// Ok(None) when the stream ends or [DONE] is received.
|
||||
pub(crate) async fn next_event(
|
||||
&mut self,
|
||||
response: &mut reqwest::Response,
|
||||
response: &mut HttpResponse,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
loop {
|
||||
// Drain complete lines from the buffer before reading more chunks
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue