api: singleton ApiClient, fix log closure threading

Make ApiClient a process-wide singleton via OnceLock so the
connection pool is reused across agent calls. Fix the sync wrapper
to properly pass the caller's log closure through thread::scope
instead of dropping it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kent Overstreet 2026-03-18 23:09:11 -04:00
parent 643f9890df
commit b04a98c6e5

View file

@ -12,6 +12,20 @@ use poc_agent::types::*;
use poc_agent::tools::{self, ProcessTracker};
use poc_agent::ui_channel::StreamTarget;
use std::sync::OnceLock;
static API_CLIENT: OnceLock<ApiClient> = OnceLock::new();
fn get_client() -> Result<&'static ApiClient, String> {
Ok(API_CLIENT.get_or_init(|| {
let config = crate::config::get();
let base_url = config.api_base_url.as_deref().unwrap_or("");
let api_key = config.api_key.as_deref().unwrap_or("");
let model = config.api_model.as_deref().unwrap_or("qwen-2.5-27b");
ApiClient::new(base_url, api_key, model)
}))
}
/// Run an agent prompt through the direct API with tool support.
/// Returns the final text response after all tool calls are resolved.
pub async fn call_api_with_tools(
@ -19,14 +33,7 @@ pub async fn call_api_with_tools(
prompt: &str,
log: &dyn Fn(&str),
) -> Result<String, String> {
let config = crate::config::get();
let base_url = config.api_base_url.as_deref()
.ok_or("api_base_url not configured")?;
let api_key = config.api_key.as_deref().unwrap_or("");
let model = config.api_model.as_deref().unwrap_or("qwen-2.5-27b");
let client = ApiClient::new(base_url, api_key, model);
let client = get_client()?;
// Set up a minimal UI channel (we just collect messages, no TUI)
let (ui_tx, _ui_rx) = poc_agent::ui_channel::channel();
@ -104,18 +111,15 @@ pub async fn call_api_with_tools(
pub fn call_api_with_tools_sync(
agent: &str,
prompt: &str,
log: &dyn Fn(&str),
log: &(dyn Fn(&str) + Sync),
) -> Result<String, String> {
// Run on a new thread to avoid conflicts with any existing runtime
let agent = agent.to_string();
let prompt = prompt.to_string();
std::thread::scope(|s| {
s.spawn(|| {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|e| format!("tokio runtime: {}", e))?;
rt.block_on(call_api_with_tools(&agent, &prompt, &|msg| eprintln!("[api] {}", msg)))
rt.block_on(call_api_with_tools(agent, prompt, log))
}).join().unwrap()
})
}