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::tools::{self, ProcessTracker};
use poc_agent::ui_channel::StreamTarget; 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. /// Run an agent prompt through the direct API with tool support.
/// Returns the final text response after all tool calls are resolved. /// Returns the final text response after all tool calls are resolved.
pub async fn call_api_with_tools( pub async fn call_api_with_tools(
@ -19,14 +33,7 @@ pub async fn call_api_with_tools(
prompt: &str, prompt: &str,
log: &dyn Fn(&str), log: &dyn Fn(&str),
) -> Result<String, String> { ) -> Result<String, String> {
let config = crate::config::get(); let client = get_client()?;
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);
// Set up a minimal UI channel (we just collect messages, no TUI) // Set up a minimal UI channel (we just collect messages, no TUI)
let (ui_tx, _ui_rx) = poc_agent::ui_channel::channel(); 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( pub fn call_api_with_tools_sync(
agent: &str, agent: &str,
prompt: &str, prompt: &str,
log: &dyn Fn(&str), log: &(dyn Fn(&str) + Sync),
) -> Result<String, String> { ) -> 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| { std::thread::scope(|s| {
s.spawn(|| { s.spawn(|| {
let rt = tokio::runtime::Builder::new_current_thread() let rt = tokio::runtime::Builder::new_current_thread()
.enable_all() .enable_all()
.build() .build()
.map_err(|e| format!("tokio runtime: {}", e))?; .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() }).join().unwrap()
}) })
} }