use crate::config::CustomProviderConfig;
use std::sync::Arc;
pub(super) fn spawn_model_catalog_server(model: &str) -> (String, std::thread::JoinHandle<()>) {
spawn_delayed_model_catalog_server(Some(model), 200, std::time::Duration::ZERO, None)
}
pub(super) fn spawn_delayed_model_catalog_server(
model: Option<&str>,
status: u16,
delay: std::time::Duration,
seen: Option<std::sync::mpsc::Sender<std::time::Instant>>,
) -> (String, std::thread::JoinHandle<()>) {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
let model = model.map(str::to_string);
let handle = std::thread::spawn(move || {
use std::io::{Read, Write};
listener.set_nonblocking(true).unwrap();
let started = std::time::Instant::now();
let (mut stream, _) = loop {
match listener.accept() {
Ok(accepted) => break accepted,
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
if started.elapsed() > std::time::Duration::from_secs(5) {
return;
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
Err(error) => panic!("model catalog server accept failed: {error}"),
}
};
stream.set_nonblocking(false).unwrap();
let mut request = [0_u8; 1024];
let _ = stream.read(&mut request);
if let Some(seen) = seen {
let _ = seen.send(std::time::Instant::now());
}
std::thread::sleep(delay);
let body = match model {
Some(model) if (200..300).contains(&status) => {
serde_json::json!({"data": [{"id": model}]}).to_string()
}
_ => serde_json::json!({"error": "catalog unavailable"}).to_string(),
};
write!(
stream,
"HTTP/1.1 {status} OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{}",
body.len(),
body
)
.unwrap();
});
(format!("http://{address}/v1"), handle)
}
pub(super) fn spawn_counted_model_catalog_server(
model: &str,
delay: std::time::Duration,
active: Arc<std::sync::atomic::AtomicUsize>,
peak: Arc<std::sync::atomic::AtomicUsize>,
) -> (String, std::thread::JoinHandle<()>) {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
let model = model.to_string();
let handle = std::thread::spawn(move || {
use std::io::{Read, Write};
let (mut stream, _) = listener.accept().unwrap();
let current = active.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
peak.fetch_max(current, std::sync::atomic::Ordering::SeqCst);
let mut request = [0_u8; 1024];
let _ = stream.read(&mut request);
std::thread::sleep(delay);
active.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
let body = serde_json::json!({"data": [{"id": model}]}).to_string();
write!(
stream,
"HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{}",
body.len(),
body
)
.unwrap();
});
(format!("http://{address}/v1"), handle)
}
pub(super) fn custom_provider_config_with_models_dev_provider(
models_dev_provider: Option<&str>,
) -> CustomProviderConfig {
CustomProviderConfig {
label: "Local AI".to_string(),
base_url: "http://127.0.0.1:1/v1".to_string(),
fast_mode: None,
api_key_env_var: None,
models_dev_provider: models_dev_provider.map(str::to_string),
use_responses_endpoint: false,
supports_text_verbosity: false,
reasoning_protocol: crate::config::CustomReasoningProtocol::default(),
extra_models: Vec::new(),
request_headers: Default::default(),
}
}
mod cache;
mod catalog;
mod models_dev;
mod refresh;
mod switching;
mod types;