ktxd 0.1.1

A local Responses API compatibility proxy for Codex CLI and Chat Completions backends.
Documentation
use ktxd::app_state::AppState;
use ktxd::config::AppConfig;
use ktxd::driver::TurnDriver;
use ktxd::responses::router;
use ktxd::session::MemoryStore;
use ktxd::substrate::{NullSeedResolver, NullSink};
use ktxd::upstream::ReqwestChatCompletionsClient;
use std::{env, path::PathBuf, sync::Arc};
use tower_http::trace::TraceLayer;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .init();

    let config_path = env::var_os("KTXD_CONFIG").map(PathBuf::from).or_else(|| {
        ["config.toml", "config.local.toml"]
            .into_iter()
            .map(PathBuf::from)
            .find(|path| path.is_file())
    });
    let config = Arc::new(AppConfig::load(config_path.as_deref())?);
    let store = MemoryStore::shared();
    let upstream = Arc::new(ReqwestChatCompletionsClient::default());
    let driver = Arc::new(TurnDriver::new(
        config.clone(),
        upstream,
        store.clone(),
        Arc::new(NullSink),
        Arc::new(NullSeedResolver),
    ));
    let app = router(AppState {
        config: config.clone(),
        store,
        driver,
    })
    .layer(TraceLayer::new_for_http());
    let listener = tokio::net::TcpListener::bind(config.server.bind).await?;
    tracing::info!(bind = %config.server.bind, "ktxd listening");
    axum::serve(listener, app).await?;
    Ok(())
}