Expand description
brazen — the engine behind the bz command, and the pure, fully-tested core
of a stateless LLM adapter.
cargo install brazen builds the bz binary (this crate’s [[bin]]); the
library is that binary’s engine, published alongside it in case it is useful as
a dependency. Its API is not yet a stability contract — pin an exact version
if you build on it.
This crate holds the canonical model (the single source of truth every
provider/protocol projects to and from) and the traits behind which all
impurity (network, clock, credentials, browser) is injected. The bz binary
and src/native/ own the native impls; the library reaches 100% coverage on its
own because nothing here touches IO — a boundary tests/purity.rs keeps real now
that the bin and the library share one crate.
Beyond the canonical model and error model (the dependency root), this crate
defines the seams the rest of the pipeline plugs into: the Protocol,
Auth, Transport, CredStore, and Clock traits, the data records they
exchange (WireRequest, Provider, Cred, …), and the Registry that
dispatches by id without ever matching a vendor name (§4 of the architecture).
It also holds the pure pipeline — input resolution, canonical-in parsing, and
the output projections + pump loop (§5). Concrete protocol/auth/transport
impls land via their own tasks; the shared test doubles live in [testing].
Structs§
- Ambient
Spec - An ambient credential source as row DATA (auth §5.5):
pathis the source LOCATOR thebzimpl reads — a~/$HOME-expanded filesystem path for a file format, an environment-variable NAME forapi_key_env— andformatselects the pure parser that maps its bytes to aCred. Neither lives in core code, so deleting the row’sambientblock deletes the capability (severability). - Args
- The injected process inputs handed to
run: the program arguments (excludingargv[0]), a snapshot of the environment, and the one bit of terminal state the pure lib can’t observe — whether stdin is an interactive tty (§5.5).mainbuilds it fromstd::env/isatty; tests build it from literals — sorunis exercised end-to-end without touching the real process state (arch §6.5, §9.6). - Canonical
Error - A normalized error, carried in-band as
Event::Error(§3.3). It stores only what cannot be computed:retryableand the exit code are queries overkind, never fields that could drift. - Canonical
Request - The single canonical request. A field set on the wire is used as-is; a field
it omits defaults (
getConfigValuefills it later — §6.1).extrais the long-tail valve: an unmodelled top-level key is forwarded verbatim. - CountIo
- The injected seams + writers for one
bz --count-tokens— the sibling ofListIo, with areader(the count op CONSUMES a request, unlike the listing verb). Reuses the data-planeTransport/CredStore/ModelCache/Clock: the model seed is placed against the same cache (READ-only — no write), and the one round-trip goes through the sameAuth::apply.stdoutgets the count; any error goes tostderr. - EnvSnapshot
- A snapshot of the process environment, injected by
main. A newtype over aBTreeMapso the projection is deterministic and pure (config §3.4). - Header
Spec - The auth-header shape as data (auth §2): the only thing that names the auth
header, so
ApiKey/Bearershare one data-driven header write. - Host
- The four impure data-plane seams, bundled (arch §1, §6.5) — the sibling of the
verbs’
ListIo/LoginIoIO bundles. Every round-trip the generation path makes goes through exactly these: theTransport(the oneurequser), the credential store, the model cache, and the clock (auth-refresh expiry). The writers stay separate from theHostbecauserunborrowsstdout/stderrmutably AND simultaneously when it builds the sink — a seam reference is shared, a writer reference is exclusive, so they cannot live in one struct. - ListIo
- The injected seams + writers for one
bz --list-models(model-discovery §2), the sibling ofLoginIo. The verb writes its listing tostdoutand any error tostderr, reuses the data-planeTransport/CredStore/Clockfor the one GET (auth/refresh and all, through the sameAuth::applyseam), and is the WHOLESALE writer of thecache— itputs the decoded list the generation path later reads, which that path then appends learned ids to on success (§5, §5.4). - LoginIo
- The injected control-plane seams + RNG for one
bz --login(auth §7.2). - Message
- A transcript message.
contentis ALWAYS aVec<Content>; a bare wire string decodes tovec. - Model
- One available model, the canonical projection of a provider list entry (§3).
Ordered position in the returned
VecIS the provider’s suggested order — the single source the heuristics read, so there is no rank field.defaultis CARRIED, not invented: a dialect that flags one sets it; today none does, so it isfalseand §4’s first-in-list rule governs — the seam stays so a provider that DOES flag one needs no code change. - Models
Override - The
[provider.models]per-row model-discovery override (config §4.4, model-discovery §3.2): thebz --list-modelsGET’s path/query and the response list keys, OVERRIDING the protocol’s defaultModelsShape(§3.1). Every key is optional — an omitted one inherits the protocol default — and the whole block is optional (absent ⇒ pure protocol default).deny_unknown_fieldsmakes a typo’d key aMalformedFile(config §2.3), likeoauth.stripis NOT here: it is protocol-only (Google’s leadingmodels/), never row-overridable.querymirrorsauthorize_params(aVec<(k, v)>URL-encoded by the same codec, auth §7.4); theskip_serializing_ifkeeps an omitted key out of a--dump-configround-trip and off the TOML serializer’s no-Nonepath. - OAuth
Config - The OAuth auth-row as data (auth §7.1): endpoints,
client_id,scope, and the auth-mode-dependentbeta_headers(e.g.anthropic-beta: oauth-…). The provider NAME is deliberately absent — it lives once, as the row key /store_key. The pure OAuth builders take&OAuthConfig, so no vendor policy is compiled into the core. Like the[[provider]]row (config §2.3),deny_unknown_fieldsmakes a typo’d or MISPLACED key aMalformedFilerather than a silent drop — a TOP-LEVEL row key (e.g.unsupported_body_keys) typed under[provider.oauth]would otherwise vanish and the strip never fire (bl-9649). - Provider
- A resolved provider row (arch §4.2). Pure data:
nameis a table key never matched on in the pipeline;protocol/authare registry keys;model_aliasesdrives the computed alias→wire-id lookup. Sparse user/file rows fold onto the embedded defaults before a completeProvideris resolved (config §3.2). - Redirect
Spec - The loopback redirect endpoint as data (auth §10.1). The default reproduces
today’s literal —
127.0.0.1(RFC 8252), an ephemeral port (None⇒:0), and/callback— so deleting the block restores it (severability). A provider whose registered redirect differs (OpenAI:localhost:1455/auth/callback) names it here as data; the socket still binds the IPv4 loopback127.0.0.1. - Resolved
Config - The one config the pipeline runs on (config §7).
modelis the alias- resolved WIRE id, soProviderCtx.modelis final andencodehas no model logic (arch §4.1). Each gen scalar already carries the routed row’sbody_defaultsbeneath flag/env/file (folded at resolve, config §4.1). - Secret
- A plaintext secret whose
Debug/Displayredact and whose only plaintext reads areexpose()(the single audited site) andSerialize(reached only byCredStore::putwriting the 0600 file) — auth §5.3. - Timeouts
- The per-request transport budgets (config §4.3), in WHOLE SECONDS; each
Noneleaves that bound unset (the transport’s own default). ureq’s three phase budgets — the seam’s internal vocabulary, NOT the config surface: the resolved config carries ONEtimeout(the silence budget), andResolvedConfig::timeouts()FANS it onto all three here (all equal, or allNone), so the collapse to one knob is a surface fact and the fan is observable at this seam (arch §13.15). Carried on theWireRequest— the one thing crossing the seam — so config- sourced policy reaches the impure transport without widening thesendsignature. The one number lives in config (floordata/defaults.toml), never as magic in the bin (severability — policy in config, not core). - Transport
Response - The peeked HTTP status (read even under
--raw, for exit-code correctness) plus the blocking, incremental body stream (arch §4.1). - Usage
- Token accounting (§3.2). Every field is
Option: a provider that never reports a counter leaves itNone(0would be a lie), never fabricated. Token-explicit names — these count tokens (Anthropicinput_tokens/…, OpenAIprompt_tokens/…) — frozen with the rest of thev=1vocabulary. - Wire
Request - The HTTP request that flows encode → auth → transport (arch §4.1).
encodebuilds the body + non-auth headers;Auth::applyadds the auth headers in place;Transport::sendconsumes it. Header names match case-insensitively so an auth overwrite never duplicates a header.methodisPostfor every generation request (the default —encodebuilds POSTs vianew) andGetfor thelist-modelsverb’s GET (§6).timeoutsis the per-request transport policy (config §4):encodeleaves it at theDefault(all unset) andrunstamps the resolved config onto it beforesend, so a config-driven bound reaches the impure transport without a widersendsignature.
Enums§
- Ambient
Format - Which foreign credential format an
AmbientSpecnames (auth §5.5). A closed enum, not a JSON-pointer DSL: each shape needs a parser anyway, so one variant per known source is less mechanism than a speculative mapping language. The variant ALSO tells thebzdiscoverimpl where to read: a file (ClaudeCode) or a process env var (ApiKeyEnv). - AuthId
- Which auth model a provider uses (arch §4.2, §4.4). A registry key.
ApiKeyandBearerdiffer only inHeaderScheme; both ship, plusOAuth2andNone.Noneis a keyless row (e.g. local Ollama): no credential is read and no auth header is written, so it carries noapi_header— a resolve invariant mirroring the way anOAuth2row carries anoauthblock. - Content
- A piece of content.
Textis expressible as a bare string or a{"type":"text",…}object; other variants are tagged objects.Thinking/RedactedThinkingpayloads and the two server-tool variants (CR-4) round-trip verbatim — provider-executed blocks carried untouched, mirroring theRedactedThinkingrule. - Content
Kind - What kind of content block is opening (§3.2). Externally tagged so it
renders
{"text":{}}/{"tool_use":{…}}exactly as the §5.2 sample shows. - Cred
- The stored secret bundle for one provider (auth §5.1). The variant IS the
token-kind discriminant;
expires_atis ABSOLUTE unix-seconds; there is nois_validflag (freshness is thenow + SKEW >= expires_atquery) and no provider name (the file path is the name). - Delta
- A streamed content fragment (§3.2). Externally tagged so a newtype variant
renders
{"text_delta":"Hel"}. Tool arguments rideJsonDeltaas text fragments, never a parsedValue. - Document
Source - A document source — the
Imageanalogue for PDFs/files,kind-tagged exactly likeImageSource; a dialect that can’t express a source rejects at encode (providers §9). - Error
Kind - The taxonomy every failure normalizes to (§3.3).
Providercarries the HTTP status soretryable/exit derive without a second table.Otheris the forward-compat escape hatch (§3.2v=1contract): an error event carries novhandshake, so a future kind cannot be version-gated — instead an unrecognized snake_casekinddecodes here verbatim (mirroringFinishReason::Other) so a 0.1.0-pinned consumer degrades instead of failing. Serde is hand-rolled (below) to route the unknown tag, not derived. - Event
- Finish
Reason - Why generation stopped (§3.2). Carried flattened into
Event::Finish, keyed onreason. Refusal is aFinish, never anError.Otherpreserves any unknown reason string so decode never panics on a new value. - Header
Scheme - How the secret is written into the header value (auth §2). Two arms cover
every shipped wire convention; a
matchon it is value formatting, not vendor dispatch. - Image
Source - Method
- The HTTP verb a
WireRequestcarries (model-discovery §6): every generation request is aPost(the default —encodeis unchanged), thelist-modelsverb’s GET aGet. Data on the one struct already crossing the transport seam (mirrorstimeouts), not a newsendparameter — the impureHttpTransportreads it to pick the verb,MockTransportrecords it. - OutMode
- The output projection (arch §5.1):
--textdefault,--json→Ndjson,--raw→Raw. The single enum behind bothPartialConfig.outputandResolvedConfig.output— one home for “which projection” (config §7). - Output
Format - A PORTABLE structured-output intent — one canonical knob every structured-output-
capable dialect spells differently, lifted out of
extraso each adapter owns its projection (the same rule asToolChoice/reasoning). Internally tagged ontype({"type":"json"}/{"type":"json_schema",...}), so it rides the wire and config the same way.name/strictfeed only the dialects whose wire has them (OpenAI); Anthropic/Google/Ollama read onlyschema(providers.md §6). - Protocol
Id - Which wire dialect a provider speaks (arch §4.2). A registry key, never a
matchtarget; the explicitrenamekeeps the config spelling (openai_chat) stable regardless of the Rust identifier. - Reasoning
Effort - A PORTABLE reasoning-effort intent — one canonical knob every reasoning-capable
dialect spells differently, lifted out of
extraso each adapter owns its projection (the same rule asToolChoice/parallel_tool_calls). serde lowercase, so"low"/"medium"/"high"on the wire and in config (providers.md §6). - Role
- Route
- Which control plane the
bzshim should wire (§5.10.1). Computed by the ONE authoritative [parse_args], so the coverage-excluded shim never hand-rolls an argv scan and can never disagree with the lib on flag-vs-prompt: a value whose text looks like a control flag (--system=--login) is the value, and any word after--is the prompt, so neither is ever mistaken for a route. - Tool
- A declared tool — an OPEN SET (brazen enumerates none, registers none). The
enum distinguishes only NORMALIZE vs CARRY, and the harness declares which by
the shape it hands over: a wire object with no
typekey isCustom, one with atypekey isProvider(hand-rolled serde inrequest_de, keyed ontype). - Tool
Choice - All four tool-use intents, lifted explicitly rather than left in
extra.
Constants§
- EVENT_
SCHEMA_ VERSION - Event-schema version stamped into the first
MessageStart(§3.2). The one handshake a harness pins to; a backward-incompatible change to theEventvocabulary bumps it (an additive kind/event does NOT — see the module doc).
Traits§
- Browser
Launcher - Open
urlin the user’s browser (auth §7.2). Real implCommand::spawns thebrowser_argv; the fake records the argv and never execs. - Clock
- The one injected time source in the data plane (auth §5.4): unix seconds. The
library never calls
SystemTime::now;bzwiresSystemClock, tests wireFakeClock. - Code
Receiver - Capture the loopback redirect (auth §7.2, §10.1).
bindbinds the listener on127.0.0.1at the requestedport(None⇒ an OS-assigned ephemeral port, RFC 8252 §7.3) and returns the ACTUALLY-bound port, whichbrowser_flowsubstitutes into theredirect_uri— single-sourcing the port through the receiver whether fixed or ephemeral.await_querythen blocks until the redirect arrives and returns its rawcode=…&state=…query, whichparse_callbackvalidates. - Cred
Store - Persist and retrieve one secret bundle per provider (auth §5.2, §5.5).
getreturnsNonefor a missing cred (the no-creds path), never an error; refresh isOAuth2::applyusing get+put (freshness is a query); list/delete are control-plane.discoveris the third primitive (auth §5.5): read a foreign credential source named by anAmbientSpec— a file (Claude Code’s~/.claude/…) or a process env var (a vendor key alias) — into a brazenCred. The IO — a file read with$HOMEexpansion, or an env read — lives in thebzimpl; the format parse is the pureparse_ambient; a store with no ambient backing returnsNone. - Model
Cache - The per-provider model-list cache (model-discovery §5.1) — filesystem state, so
like
CredStoreit lives behind an injected trait; the pure lib never touches the disk. A SIBLING ofCredStore, not folded into it: a secret and a regenerable model list are different facts with different files. Thebzbin backs it with one JSON file per provider under$XDG_CACHE_HOME/brazen/models/<provider>.json(the{"models":[{id,default}]}shapelist-models --jsonemits, reused); the in-memory double lives intesting. - Pacer
- Pace the device-flow poll loop (auth §7.3): the real bin sleeps
secs; the test fake records the interval and returns instantly — so the whole flow runs offline with no real time. A control-plane concern only, kept off the data-planeClock. - Transport
- The single network seam (arch §4.1). Object-safe;
Send + Syncso an impl is shareable. Exactly one round-trip per process — a caller wanting N concurrent requests spawns Nbz.
Functions§
- browser_
argv - The argv to open
urlin the user’s default browser on the build target (arch §7.3). A thin pass of the compile-time OS to the pureargv_for; the caller (bz --login --browser)Command::spawns the result. - count_
tokens - Run
bz --count-tokensand return the POSIX exit code (§5.10.1). Reads the request fromreader(stdin /--input) or the positional prompt, resolves provider/model as the data plane does, does ONE round-trip, and prints{"input_tokens": N}under--jsonelse the bareN. A failure is written to stderr and mapped to its exit (usage 64 / no-input 66 / config 78 / auth 77 / non-2xx 69-70 / malformed body 70). - generate
- Generate against a resolved config (arch §1): drive ONE round-trip and yield the
canonical event stream, terminated by a single
End. THE pure typed core —runwraps it in byte I/O, an embedder consumes the events directly. The model SEED is resolved against the per-provider cache here (a local file read viahost.cache, model-discovery §5.2), then the request is encoded, authenticated, and sent over the oneTransport. Every failure is an in-bandEvent::Error, so the call is total. This is the normalized-in / canonical-out composition — [send_encoded] (the request half) then [canonical_events] (the response half); the--raw=outvariant reusessend_encodedunder aRawSink(arch §5.4, §13.14). - list_
models - Run
bz --list-modelsand return the POSIX exit code (model-discovery §2). Reuses the full flag parser +into_resolved(None)to pick the provider (an explicit--provider, else the row owning a configuredmodel; neither →NoProvider/78), does ONE GET tomodels_path, and prints —--jsonthe{"models":[…]}object, else the ids one per line with(default)on the default. The listing goes to stdout; any failure is written to stderr and mapped to its exit (config 78 / auth 77 / non-2xx 69-70 / a malformed body 70 — the same run-level table). - login
- Run
bz --loginand return the POSIX exit code (auth §7). Resolves the provider’sOAuthConfig, runs the selected flow, and persists the resultingCred::OAuth2. Any failure is written to STDERR and mapped to its exit (login failure → 77, unresolvable / no-oauth / no-device-endpoint provider → 78, bad flag → 64). - parse_
ambient - Map a foreign credential source’s bytes to a brazen
Cred(auth §5.5) — the pure half of discovery, so thebzimpl does only the IO.Nonefor malformed or incomplete input (the no-creds path, likeget). Theclaude_codecase readsclaudeAiOauthinto aCred::OAuth2:expiresAtis MILLISECONDS, divided to absolute unix-seconds once here (the single home for that unit mismatch), andscopesjoin into thescopestring (Nonewhen empty);account_idisNone(Anthropic binds no account id). Theapi_key_envcase is the env var’s value as a rawCred::ApiKey(trimmed; empty/non-UTF-8 ⇒None). - query_
from_ request_ line - Extract the query string from an HTTP request line, e.g.
GET /callback?code=x&state=y HTTP/1.1→code=x&state=y(auth §7.4). PURE, so the bin’s loopback receiver reads the line over the socket and defers the parse here;Nonewhen the request-target carries no?query. - route
- Read the routing decision from argv (§5.10.1). A parse error (an unknown flag, two
combined control ops) routes to
Route::Run, whose lib entry re-parses and surfaces the same error as the authoritative 64 — so routing owns no error path. - run
- The binary in one call (arch §1). Resolves config, reads the request (positional
XOR stdin), encodes, authenticates, sends one round-trip, decodes the framed
response into canonical events, and projects them through the mode’s sink —
returning the POSIX exit code (
mainmaterializes theExitCode).
Type Aliases§
- Bytes
- One transport body chunk. An alias, so the framers’
Vec<u8>and the body stream speak the same type.