feat(runtime): replace tokio+reqwest with asupersync async runtime

- Add HTTP adapter layer (src/http.rs) wrapping asupersync h1 client
- Migrate gitlab client, graphql, and ollama to HTTP adapter
- Swap entrypoint from #[tokio::main] to RuntimeBuilder::new().block_on()
- Rewrite signal handler for asupersync (RuntimeHandle::spawn + ctrl_c())
- Migrate rate limiter sleeps to asupersync::time::sleep(wall_now(), d)
- Add asupersync-native HTTP integration tests
- Convert timeline_seed_tests to RuntimeBuilder pattern

Phases 1-3 of asupersync migration (atomic: code won't compile without all pieces).
This commit is contained in:
teernisse
2026-03-06 15:23:55 -05:00
parent bf977eca1a
commit e8d6c5b15f
16 changed files with 1974 additions and 1189 deletions

View File

@@ -1,10 +1,10 @@
use reqwest::Client;
use serde::Deserialize;
use serde_json::Value;
use std::time::{Duration, SystemTime};
use tracing::warn;
use crate::core::error::LoreError;
use crate::http::Client;
pub struct GraphqlClient {
http: Client,
@@ -21,13 +21,8 @@ pub struct GraphqlQueryResult {
impl GraphqlClient {
pub fn new(base_url: &str, token: &str) -> Self {
let http = Client::builder()
.timeout(Duration::from_secs(30))
.build()
.unwrap_or_else(|_| Client::new());
Self {
http,
http: Client::with_timeout(Duration::from_secs(30)),
base_url: base_url.trim_end_matches('/').to_string(),
token: token.to_string(),
}
@@ -45,23 +40,13 @@ impl GraphqlClient {
"variables": variables,
});
let bearer = format!("Bearer {}", self.token);
let response = self
.http
.post(&url)
.header("Authorization", format!("Bearer {}", self.token))
.header("Content-Type", "application/json")
.json(&body)
.send()
.await
.map_err(|e| LoreError::GitLabNetworkError {
base_url: self.base_url.clone(),
kind: crate::core::error::NetworkErrorKind::Other,
detail: Some(format!("{e:?}")),
})?;
.post_json(&url, &[("Authorization", bearer.as_str())], &body)
.await?;
let status = response.status();
match status.as_u16() {
match response.status {
401 | 403 => return Err(LoreError::GitLabAuthFailed),
404 => {
return Err(LoreError::GitLabNotFound {
@@ -73,14 +58,13 @@ impl GraphqlClient {
return Err(LoreError::GitLabRateLimited { retry_after });
}
s if s >= 400 => {
return Err(LoreError::Other(format!("GraphQL HTTP {status}")));
return Err(LoreError::Other(format!("GraphQL HTTP {s}")));
}
_ => {}
}
let json: Value = response
.json()
.await
.map_err(|e| LoreError::Other(format!("Failed to parse GraphQL response: {e}")))?;
let errors = json.get("errors").and_then(|e| e.as_array());
@@ -117,12 +101,8 @@ impl GraphqlClient {
}
}
fn parse_retry_after(response: &reqwest::Response) -> u64 {
let header = match response
.headers()
.get("retry-after")
.and_then(|v| v.to_str().ok())
{
fn parse_retry_after(response: &crate::http::Response) -> u64 {
let header = match response.header("retry-after") {
Some(s) => s,
None => return 60,
};