brazen 0.0.4

A stateless, swiss-army-knife adapter for every LLM provider and protocol.
Documentation
//! The native impure impls behind brazen's seams (arch §6.5, §9.5, §10) — coverage-
//! excluded with the `bz` bin (Makefile `cov` `--ignore-filename-regex
//! 'src/(main\.rs|native)'`). The native impurities live here: the system clock, the
//! browser spawn, the loopback `bind`/`accept`, and the device-poll sleep. The
//! atomic 0600 credential store is in [`creds`], the OS RNG in [`rng`], and the
//! rustls-backed `HttpTransport` — the lone `ureq` user — in [`transport`]. The
//! library reaches 100% behind injection; the pure parsing these call
//! (`browser_argv`, `query_from_request_line`, the OAuth builders) is in the lib.

// The shim's lint posture, declared once at its root for BOTH consumers — the `bz` bin
// (`mod native` in main.rs) and the `native-host` lib exposure (`pub mod native` in
// lib.rs, bl-547d). This IS the impurity boundary, not the pure core, so the crate-level
// pure-core denies do not apply here: `expect`/`unwrap`/`panic` are how the shim aborts
// LOUDLY on an OS failure (e.g. `rng` refuses a silent fallback when `/dev/urandom` is
// unreadable — auth §7.4), and these `new()`s are wiring constructors, not a `Default`.
// Relaxing them at the shim root (they are already un-denied for the bin) keeps the pure
// library's discipline intact while letting the coverage-excluded shim link cleanly.
#![allow(
    clippy::expect_used,
    clippy::unwrap_used,
    clippy::panic,
    clippy::new_without_default
)]

mod cache;
mod creds;
mod exec;
mod listen;
mod relay;
mod rng;
mod transport;

pub use cache::XdgModelCache;
pub use creds::XdgCredStore;
pub use listen::{stash_root, TcpBind};
pub use rng::random_token;
pub use transport::HttpTransport;

use std::cell::RefCell;
use std::io::{self, BufRead, BufReader, Write};
use std::net::TcpListener;
use std::process::Command;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use brazen::{BrowserLauncher, Clock, CodeReceiver, Pacer};

/// The system clock (arch §6.5): the one place real time is read. A pre-1970 clock
/// is clamped to 0 rather than panicking.
pub struct SystemClock;

impl Clock for SystemClock {
    fn now(&self) -> u64 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map(|d| d.as_secs())
            .unwrap_or(0)
    }
}

/// Open the authorize URL in the user's browser (auth §7.2): spawn `browser_argv`
/// (the OS→argv map is pure lib data) — the one excluded `spawn` line.
pub struct SystemBrowserLauncher;

impl BrowserLauncher for SystemBrowserLauncher {
    fn open(&self, url: &str) -> io::Result<()> {
        let mut argv = brazen::browser_argv(url).into_iter();
        let prog = argv.next().unwrap_or_default();
        Command::new(prog).args(argv).spawn()?;
        Ok(())
    }
}

/// The RFC 8252 loopback receiver (auth §7.2, §7.4, §10.1): `bind` the IPv4 loopback
/// `127.0.0.1` at the row's redirect port (`None` ⇒ `:0`, ephemeral) — always the
/// v4 literal even when the redirect host string is `localhost` (the browser
/// resolves it to `127.0.0.1`) — then accept the provider's redirect, read the
/// request line, and defer the query extraction to the pure
/// `query_from_request_line`. Bound lazily (the port is known only after config
/// resolves), so the listener is held behind a `RefCell`. Coverage-excluded.
pub struct LoopbackReceiver {
    listener: RefCell<Option<TcpListener>>,
}

impl LoopbackReceiver {
    pub fn new() -> Self {
        LoopbackReceiver {
            listener: RefCell::new(None),
        }
    }
}

impl CodeReceiver for LoopbackReceiver {
    fn bind(&self, port: Option<u16>) -> io::Result<u16> {
        let listener = TcpListener::bind(("127.0.0.1", port.unwrap_or(0)))?;
        let actual = listener.local_addr()?.port();
        *self.listener.borrow_mut() = Some(listener);
        Ok(actual)
    }

    fn await_query(&self) -> io::Result<String> {
        let guard = self.listener.borrow();
        let listener = guard
            .as_ref()
            .ok_or_else(|| io::Error::other("loopback receiver was not bound"))?;
        let (stream, _) = listener.accept()?;
        let mut reader = BufReader::new(stream.try_clone()?);
        let mut line = String::new();
        reader.read_line(&mut line)?;
        let query = brazen::query_from_request_line(line.trim_end())
            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "callback had no query"))?;
        let body = "brazen: you may close this tab and return to the terminal.";
        let resp = format!(
            "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {}\r\n\r\n{body}",
            body.len()
        );
        let mut stream = stream;
        stream.write_all(resp.as_bytes())?;
        Ok(query)
    }
}

/// The device-flow poll pacer (auth §7.3): the real binary sleeps `secs`.
pub struct RealPacer;

impl Pacer for RealPacer {
    fn wait(&self, secs: u64) {
        std::thread::sleep(Duration::from_secs(secs));
    }
}

// The XdgCredStore IO invariants (atomic write, 0600 file, 0700 dir, round-trip) —
// a child module so it can root the real store at a private `dir` (bl-5b5a).
#[cfg(test)]
mod tests;

// The XdgModelCache IO invariants (round-trip, forgiving reads, atomic temp+rename;
// model-discovery §8) — same precedent: a child module rooting the real cache at a
// private `dir`, so the coverage-excluded XDG-file impl is still pinned.
#[cfg(test)]
mod cache_tests;