Channel-neutral provider management service.
Everything a channel needs to render and mutate the provider fleet
— status probing, account limits, live model catalogs, presets, and
config persistence — WITHOUT any UI. Hoisted from the TUI extension
(channel_tui/provider.clj) so any future surface manages the SAME
fleet through the SAME primitives; the channels keep only their
interaction layer (lanterna dialogs, ...).
Auth is classified, not implemented, here: auth-kind tells a
channel whether a provider wants an API key, an interactive OAuth
flow (owned by the provider extension + channel), or nothing
(local). The registry's :provider/*-fn contract stays the single
integration point for provider extensions, so a provider extension
automatically works in every channel.
Channel-neutral provider management service. Everything a channel needs to render and mutate the provider fleet — status probing, account limits, live model catalogs, presets, and config persistence — WITHOUT any UI. Hoisted from the TUI extension (`channel_tui/provider.clj`) so any future surface manages the SAME fleet through the SAME primitives; the channels keep only their interaction layer (lanterna dialogs, ...). Auth is classified, not implemented, here: `auth-kind` tells a channel whether a provider wants an API key, an interactive OAuth flow (owned by the provider extension + channel), or nothing (local). The registry's `:provider/*-fn` contract stays the single integration point for provider extensions, so a provider extension automatically works in every channel.
(add-config-provider! provider-cfg)(add-config-provider! provider-cfg source)Append a provider config to the persisted fleet (no-op when its id exists).
Append a provider config to the persisted fleet (no-op when its id exists).
(auth-kind pid)(auth-kind pid provider)How a provider authenticates: :command (an api_key_command mints the
credential — never prompt), :oauth (the registered extension declares an
interactive :provider/auth-fn), :managed (the extension owns configuration
and exposes no interactive flow), :none (local, no credentials), or :api-key.
A descriptor may DECLARE :provider/auth-kind, and that declaration outranks
every inference below except a machine-minted credential. An interactive
:provider/auth-fn is NOT proof of OAuth: the shared static-API-key shape
registers one to print key guidance for vis-agent providers auth, and reading
that as OAuth left those providers unable to collect a key over the wire at all.
Ownership never overrides authentication: a managed provider with auth-fn is
OAuth-capable while remaining automatically bound and absent from Add Provider.
The 1-arity classifies by id alone and therefore can never see a command-minted
provider; pass the configured provider map when that answer decides whether to
prompt a human.
How a provider authenticates: `:command` (an `api_key_command` mints the credential — never prompt), `:oauth` (the registered extension declares an interactive `:provider/auth-fn`), `:managed` (the extension owns configuration and exposes no interactive flow), `:none` (local, no credentials), or `:api-key`. A descriptor may DECLARE `:provider/auth-kind`, and that declaration outranks every inference below except a machine-minted credential. An interactive `:provider/auth-fn` is NOT proof of OAuth: the shared static-API-key shape registers one to print key guidance for `vis-agent providers auth`, and reading that as OAuth left those providers unable to collect a key over the wire at all. Ownership never overrides authentication: a managed provider with `auth-fn` is OAuth-capable while remaining automatically bound and absent from Add Provider. The 1-arity classifies by id alone and therefore can never see a command-minted provider; pass the configured provider map when that answer decides whether to prompt a human.
(auth-summary status decorated?)The one human reading of a provider's authentication state: verified,
rejected, usable; live check unavailable, or -- while nothing has proven
the credential -- saved, not verified / not verified. A key that is only
STORED is never reported as proven. decorated? appends the glyph the
dialogs paint; the CLI passes false.
The one human reading of a provider's authentication state: `verified`, `rejected`, `usable; live check unavailable`, or -- while nothing has proven the credential -- `saved, not verified` / `not verified`. A key that is only STORED is never reported as proven. `decorated?` appends the glyph the dialogs paint; the CLI passes false.
(auth-verdict status)The daemon-classified authentication state. Missing evidence is neutral, never inferred as success or rejection from credential presence alone.
The daemon-classified authentication state. Missing evidence is neutral, never inferred as success or rejection from credential presence alone.
(authenticated-preset-providers)Registered providers with credentials outside the persisted fleet.
Rows include default catalog models and follow catalog/preset-order.
picker-fleet appends these rows after configured providers.
A managed provider binds through credentials from its runtime.
Other providers use their local :provider/detect-fn without network requests.
Saved rejection metadata keeps the row visible for Connect, but does not authorize requests.
Skip providers without credential metadata or default models.
Registered providers with credentials outside the persisted fleet. Rows include default catalog models and follow [[catalog/preset-order]]. [[picker-fleet]] appends these rows after configured providers. A managed provider binds through credentials from its runtime. Other providers use their local `:provider/detect-fn` without network requests. Saved rejection metadata keeps the row visible for Connect, but does not authorize requests. Skip providers without credential metadata or default models.
(available-presets)Provider presets not yet in the configured fleet — the 'Add Provider' picker contents.
A MANAGED provider is never offered here: it carries no credential a human
supplies and it binds itself, so an Add provider row for it could only ask
for a key that every seam below must refuse.
Provider presets not yet in the configured fleet — the 'Add Provider' picker contents. A MANAGED provider is never offered here: it carries no credential a human supplies and it binds itself, so an `Add provider` row for it could only ask for a key that every seam below must refuse.
(clear-fallback-selection!)(clear-fallback-selection! source)Drop the fallback tag. The fleet keeps every provider; only the second root goes away, leaving svar's own priority order to decide what follows the primary.
Drop the fallback tag. The fleet keeps every provider; only the second root goes away, leaving svar's own priority order to decide what follows the primary.
(clear-provider-api-key! provider-id)(clear-provider-api-key! provider-id source)Forget provider-id's stored API key while KEEPING its config entry.
This is what "log out" means for a key-only provider: the credential goes, the provider stays configured with its models, base-url and tags, so signing back in is one key away. Returns true when a key was actually cleared.
Forget `provider-id`'s stored API key while KEEPING its config entry. This is what "log out" means for a key-only provider: the credential goes, the provider stays configured with its models, base-url and tags, so signing back in is one key away. Returns true when a key was actually cleared.
(command-minted? provider)True when the credential is minted BY THE MACHINE: config carries an
api_key_command, so the helper mints (and rotates) the token on every
request and there is nothing for a human to type or paste.
True when the credential is minted BY THE MACHINE: config carries an `api_key_command`, so the helper mints (and rotates) the token on every request and there is nothing for a human to type or paste.
(configured-model-names provider)Model names a provider declares in config, deduped, in the provider map's order. Never filtered by svar's catalog visibility rules - an explicitly configured model is a statement of intent, not a suggestion.
Model names a provider declares in config, deduped, in the provider map's order. Never filtered by svar's catalog visibility rules - an explicitly configured model is a statement of intent, not a suggestion.
(configured-providers)The persisted provider fleet (global + project overlay), priority order, catalog metadata applied (base-url/api-style filled in).
The persisted provider fleet (global + project overlay), priority order, catalog metadata applied (base-url/api-style filled in).
(configured-providers-cached)Frame/request-frequency read of configured-providers that never re-runs
the full enumeration on a warm caller. The enumeration behind
config/load-config parses four config files per call — ~200ms on
machines with slow file IO — which stalled every TUI footer frame when it
ran on the render thread (issue #29).
Frame/request-frequency read of `configured-providers` that never re-runs the full enumeration on a warm caller. The enumeration behind `config/load-config` parses four config files per call — ~200ms on machines with slow file IO — which stalled every TUI footer frame when it ran on the render thread (issue #29). - FRESH snapshot → returned as-is (pure atom read). - STALE snapshot → returned immediately; a single-flight background refresh replaces it off-thread. - COLD (first read / just invalidated) → enumerates synchronously ONCE, so callers always get a real fleet, never a nil-because-cold.
(default-model-configs preset)Preset :default-models as persisted model maps, in svar's canonical model
order (svar/sort-models). A bare-string entry
becomes {:name str}; a MAP entry is carried through verbatim (name
normalized) so a provider can declare :context / :output-limit / … for
a model svar's pinned catalog doesn't know yet — no svar release, no
enrich hook. ->svar-model whitelists which of those keys svar honors, so
extra keys are harmless.
Preset `:default-models` as persisted model maps, in svar's canonical model
order (`svar/sort-models`). A bare-string entry
becomes `{:name str}`; a MAP entry is carried through verbatim (name
normalized) so a provider can declare `:context` / `:output-limit` / … for
a model svar's pinned catalog doesn't know yet — no svar release, no
enrich hook. `->svar-model` whitelists which of those keys svar honors, so
extra keys are harmless.(default-model-names provider)Union of model names already on the provider map plus the preset /
provider :default-models, deduped. model-options ranks them in svar's
canonical model order. Managed providers keep only their declared model ids.
Union of model names already on the provider map plus the preset / provider `:default-models`, deduped. `model-options` ranks them in svar's canonical model order. Managed providers keep only their declared model ids.
(default-selection)(default-selection fleet)The valid PRIMARY provider/model pair for fleet, read against THIS machine's
merged config. resolve-default-selection is the resolution itself.
The valid PRIMARY provider/model pair for `fleet`, read against THIS machine's merged config. `resolve-default-selection` is the resolution itself.
(demote-unreachable-providers router)Health-order a ROUTER (svar shape, {:providers [...]}) for one
turn: LOCAL providers that fail the liveness probe sink to the END
of the fleet — kept as last resort, never silently dropped — so a
dead local endpoint can't catch a turn (or an svar fallback) that a
healthy provider should have taken. Probes run ONLY when local
providers are configured (≤ ~2.5s each; zero cost otherwise).
Returns {:router r :demoted [provider-ids]}. NEVER throws —
routing must survive a broken probe (falls back to the router
as-is), so callers need no defensive wrapping.
Health-order a ROUTER (svar shape, `{:providers [...]}`) for one
turn: LOCAL providers that fail the liveness probe sink to the END
of the fleet — kept as last resort, never silently dropped — so a
dead local endpoint can't catch a turn (or an svar fallback) that a
healthy provider should have taken. Probes run ONLY when local
providers are configured (≤ ~2.5s each; zero cost otherwise).
Returns `{:router r :demoted [provider-ids]}`. NEVER throws —
routing must survive a broken probe (falls back to the router
as-is), so callers need no defensive wrapping.(fallback-selection)(fallback-selection fleet)(fallback-selection fleet primary)Return the valid FALLBACK provider/model pair for fleet, or nil.
The fallback is the pair the router drops to when the primary provider cannot
serve the turn, so — unlike the primary — it is never implicit: an unset,
unknown, or same-provider-as-primary tag resolves to nil rather than inventing
a second choice nobody asked for. Only the MODEL is lenient (an unknown model
name falls back to the provider's first), mirroring default-selection.
Return the valid FALLBACK provider/model pair for `fleet`, or nil. The fallback is the pair the router drops to when the primary provider cannot serve the turn, so — unlike the primary — it is never implicit: an unset, unknown, or same-provider-as-primary tag resolves to nil rather than inventing a second choice nobody asked for. Only the MODEL is lenient (an unknown model name falls back to the provider's first), mirroring `default-selection`.
(fetch-model-catalog provider)Fetch normalized live models and their credential-safe account/endpoint identity. Managed providers learn metadata only for their declared models. Missing fields stay missing. No raw provider response or credential is persisted.
Fetch normalized live models and their credential-safe account/endpoint identity. Managed providers learn metadata only for their declared models. Missing fields stay missing. No raw provider response or credential is persisted.
(fetch-models provider)List visible chat model ids from the live catalog, or nil on failure.
List visible chat model ids from the live catalog, or nil on failure.
(forget-provider-status!)(forget-provider-status! provider-id)Drop the remembered verdict for one provider (or all of them) so the next non-probing read stops repeating it. Auth changes call this: a credential that just signed in or out makes the last verdict a lie.
Drop the remembered verdict for one provider (or all of them) so the next non-probing read stops repeating it. Auth changes call this: a credential that just signed in or out makes the last verdict a lie.
(format-limit-row {:keys [label scope kind is-unlimited used limit remaining
note window]})(format-status-value v)One provider-status VALUE as text: a keyword by name, a map or sequence
flattened to k: v in key order, anything else str. Recursive, so a nested
plan map renders inside its own row.
One provider-status VALUE as text: a keyword by name, a map or sequence flattened to `k: v` in key order, anything else `str`. Recursive, so a nested plan map renders inside its own row.
(initial-provider-limits provider)Placeholder limits report while the real fetch runs.
Placeholder limits report while the real fetch runs.
(initial-provider-status provider)Placeholder status while a real probe runs in the background. A credential gap is decided synchronously — it is a pure read of the config already in hand, plus at most one cached credential-command probe — so the card never flashes an authenticated verdict it is about to retract.
Placeholder status while a real probe runs in the background. A credential gap is decided synchronously — it is a pure read of the config already in hand, plus at most one cached credential-command probe — so the card never flashes an authenticated verdict it is about to retract.
(invalidate-configured-providers!)Drop the fleet snapshot so the next configured-providers-cached read
re-enumerates. Called by every same-process fleet mutation — which is what
lets the TTL stay long (issue #29 follow-up: invalidate on change instead
of polling).
Drop the fleet snapshot so the next `configured-providers-cached` read re-enumerates. Called by every same-process fleet mutation — which is what lets the TTL stay long (issue #29 follow-up: invalidate on change instead of polling).
Wall a provider's :provider/limits-fn gets before its report is declared
late. /v1/router asks EVERY provider for one before the model picker can
paint, so a single account endpoint that hangs used to hold the entire payload
until the client's own 30s bound aborted it — and the model you were switching
to never arrived.
Wall a provider's `:provider/limits-fn` gets before its report is declared late. `/v1/router` asks EVERY provider for one before the model picker can paint, so a single account endpoint that hangs used to hold the entire payload until the client's own 30s bound aborted it — and the model you were switching to never arrived.
Local OpenAI-compatible providers that need no credentials.
Local OpenAI-compatible providers that need no credentials.
(managed? provider-id)True when the extension that registered provider-id owns its binding and
configuration. Managed providers bind as soon as their extension loads and stay
out of Add Provider. Authentication is independent: :provider/auth-fn may still
obtain a provider-owned credential on first use.
True when the extension that registered `provider-id` owns its binding and configuration. Managed providers bind as soon as their extension loads and stay out of Add Provider. Authentication is independent: `:provider/auth-fn` may still obtain a provider-owned credential on first use.
(model-options provider)(model-options provider default-models show-all?)Selectable model ids for a provider in svar's canonical model order
(svar/sort-models), env default pinned first. Configured, live-fetched and
preset ids are deduped; ids the order does not rank keep configured order,
then alphabetical order. When show-all? is false, dated snapshot variants
(gpt-4o-2024-08-06) are hidden. Managed providers never show models outside
their extension's declaration, including when show-all? is true.
Returns {:models [id ...] :hidden-count n} - channels render
their own 'show all' affordance from :hidden-count.
Selectable model ids for a provider in svar's canonical model order
(`svar/sort-models`), env default pinned first. Configured, live-fetched and
preset ids are deduped; ids the order does not rank keep configured order,
then alphabetical order. When `show-all?` is false, dated snapshot variants
(gpt-4o-2024-08-06) are hidden. Managed providers never show models outside
their extension's declaration, including when `show-all?` is true.
Returns `{:models [id ...] :hidden-count n}` - channels render
their own 'show all' affordance from `:hidden-count`.Providers whose credentials come from an interactive OAuth flow and live OUTSIDE config.edn (keychain / token files owned by the provider extension).
Providers whose credentials come from an interactive OAuth flow and live OUTSIDE config.edn (keychain / token files owned by the provider extension).
(persisted-provider-config provider)Convert an in-memory provider entry to the durable on-disk shape.
Convert an in-memory provider entry to the durable on-disk shape.
(picker-fleet)The provider fleet a model picker should render: the persisted
configured-providers first, in config order, then authenticated-preset-providers
(authenticated-but-unconfigured OAuth providers whose creds live outside
config) appended in canonical provider order. This is what channel model pickers enumerate so
authenticated providers are selectable even before they're saved into the
fleet.
Failure-isolated by construction: the base fleet reads through the
never-nil configured-providers-cached (no per-open 4-file parse, no
render-thread stall), and the authenticated-preset enumeration degrades to
empty on any error. A transient hiccup therefore drops the OAuth extras at
worst — it NEVER throws and NEVER blanks the picker of already-configured
providers.
The provider fleet a model picker should render: the persisted `configured-providers` first, in config order, then `authenticated-preset-providers` (authenticated-but-unconfigured OAuth providers whose creds live outside config) appended in canonical provider order. This is what channel model pickers enumerate so authenticated providers are selectable even before they're saved into the fleet. Failure-isolated by construction: the base fleet reads through the never-nil `configured-providers-cached` (no per-open 4-file parse, no render-thread stall), and the authenticated-preset enumeration degrades to empty on any error. A transient hiccup therefore drops the OAuth extras at worst — it NEVER throws and NEVER blanks the picker of already-configured providers.
(probe-local-reachable provider)Probe a local OpenAI-compatible provider (Ollama / LM Studio) by
GETting its <base-url>/models endpoint with a short timeout.
Reachable → {:is-authenticated true …}; refused / timeout / other →
{:is-authenticated false :error "<human hint>"} so the channel can
SAY why the dot is red. Blocking ≤ ~2.5s — call off the render path.
Probe a local OpenAI-compatible provider (Ollama / LM Studio) by
GETting its `<base-url>/models` endpoint with a short timeout.
Reachable → `{:is-authenticated true …}`; refused / timeout / other →
`{:is-authenticated false :error "<human hint>"}` so the channel can
SAY why the dot is red. Blocking ≤ ~2.5s — call off the render path.Wall a provider lifecycle callback (:provider/status-fn,
:provider/detect-fn) gets before it is declared wedged. These callbacks are
trusted extension code — often Python — and a hung one used to hold the
gateway request, and the card behind it, for the client's whole 30s budget.
Wall a provider lifecycle callback (`:provider/status-fn`, `:provider/detect-fn`) gets before it is declared wedged. These callbacks are trusted extension code — often Python — and a hung one used to hold the gateway request, and the card behind it, for the client's whole 30s budget.
(provider-config-with-models preset models)Persistable provider config carrying the provider's complete catalog.
Persistable provider config carrying the provider's complete catalog.
(provider-limits-safe provider)Normalized limits report for a provider id; an error report instead
of a throw, and never slower than limits-probe-timeout-ms.
A late probe is deliberately NOT cancelled: provider-limits memoizes the
report it is still computing, so abandoning this read leaves the next one warm.
Normalized limits report for a provider id; an error report instead of a throw, and never slower than `limits-probe-timeout-ms`. A late probe is deliberately NOT cancelled: `provider-limits` memoizes the report it is still computing, so abandoning this read leaves the next one warm.
(provider-reachable? provider)Cheap ROUTING-time liveness verdict: local providers (Ollama / LM Studio) get the real HTTP probe; remote providers are assumed reachable — their auth/network failures surface as call errors svar already fails over on, and a per-turn network check against every remote backend would tax every turn.
Cheap ROUTING-time liveness verdict: local providers (Ollama / LM Studio) get the real HTTP probe; remote providers are assumed reachable — their auth/network failures surface as call errors svar already fails over on, and a per-turn network check against every remote backend would tax every turn.
(provider-status provider)Auth/liveness status for a CONFIGURED provider map, with one explicit
:auth-state every channel paints:
:verified — a live provider check accepted the credential,:rejected — the credential/config was explicitly refused,:degraded — the credential remains usable but its live check failed,:unverified — a credential exists but no live check can prove it (or
no credential exists yet).:is-authenticated remains the independent usability bit: a degraded or
unverified entry can still route only when it is true; rejection forces it false.
Never throws.
PROBES the provider, so it belongs off the render path. Every verdict is
remembered for provider-status-cached.
Auth/liveness status for a CONFIGURED provider map, with one explicit `:auth-state` every channel paints: - `:verified` — a live provider check accepted the credential, - `:rejected` — the credential/config was explicitly refused, - `:degraded` — the credential remains usable but its live check failed, - `:unverified` — a credential exists but no live check can prove it (or no credential exists yet). `:is-authenticated` remains the independent usability bit: a degraded or unverified entry can still route only when it is true; rejection forces it false. Never throws. PROBES the provider, so it belongs off the render path. Every verdict is remembered for `provider-status-cached`.
(provider-status-cached provider)The same verdict WITHOUT any upstream call: the one a live provider-status
reached within status-memo-ms, else what config alone proves — :unverified
wherever only a probe could decide.
Fleet MUTATIONS answer with this. Adding a provider used to re-probe every
configured provider's auth and quota endpoints before its response, so the tap
that should open an API-key box waited seconds on OTHER providers' networks.
GET /v1/router still reads live.
The same verdict WITHOUT any upstream call: the one a live `provider-status` reached within `status-memo-ms`, else what config alone proves — `:unverified` wherever only a probe could decide. Fleet MUTATIONS answer with this. Adding a provider used to re-probe every configured provider's auth and quota endpoints before its response, so the tap that should open an API-key box waited seconds on OTHER providers' networks. `GET /v1/router` still reads live.
(rebuild-shared-router!)Drop the configured-provider cache, then fire the registered router-rebuild
hook best-effort so the shared router (and every cached session env that
snapshotted it) rebuilds from current config. No-op before loop registers
(early boot) or when the router was never built — reload-router! guards the
latter on router-initialized?.
PUBLIC because AUTH needs it too: a provider whose credential cannot be resolved is skipped at router build, so signing in must rebuild or the daemon keeps routing as though that provider did not exist.
Drop the configured-provider cache, then fire the registered router-rebuild hook best-effort so the shared router (and every cached session env that snapshotted it) rebuilds from current config. No-op before `loop` registers (early boot) or when the router was never built — `reload-router!` guards the latter on `router-initialized?`. PUBLIC because AUTH needs it too: a provider whose credential cannot be resolved is skipped at router build, so signing in must rebuild or the daemon keeps routing as though that provider did not exist.
(refresh-models! provider-id)(refresh-models! provider-id source)Refresh existing model metadata and append new live ids, preserving explicit config
and order, except that saved models svar leaves out of this provider's model lists
are dropped (svar/provider-model-visible?: stealth models, previews and outdated
versions). Managed providers cannot add undeclared models. The learned snapshot
is account/endpoint-scoped and stored separately.
Partial replies retain last good fields for that identity; failure changes nothing.
Returns appended names, [] for metadata-only/no change, nil for a failed probe.
Refresh existing model metadata and append new live ids, preserving explicit config and order, except that saved models svar leaves out of this provider's model lists are dropped (`svar/provider-model-visible?`: stealth models, previews and outdated versions). Managed providers cannot add undeclared models. The learned snapshot is account/endpoint-scoped and stored separately. Partial replies retain last good fields for that identity; failure changes nothing. Returns appended names, [] for metadata-only/no change, nil for a failed probe.
(refresh-models-async! provider-id)(refresh-models-async! provider-id source)Background refresh-models!: the caller answers now, the catalog lands after.
Router startup/rebuild and UI actions schedule discovery without joining the
response that triggered it. One probe
per provider at a time, at most one per models-refresh-window-ms, and errors
are swallowed: a catalog one build old is a poor picker, never a failed
request. Returns nil immediately.
Background [[refresh-models!]]: the caller answers now, the catalog lands after. Router startup/rebuild and UI actions schedule discovery without joining the response that triggered it. One probe per provider at a time, at most one per `models-refresh-window-ms`, and errors are swallowed: a catalog one build old is a poor picker, never a failed request. Returns nil immediately.
(remove-provider! provider-id)(remove-provider! provider-id source)Remove a provider from the persisted fleet AND run the registered
extension's logout when present. Invalidates the fleet snapshot.
Returns true when config changed. Extension-managed providers are rejected
before logout or any config mutation with :type :provider/managed.
Remove a provider from the persisted fleet AND run the registered extension's logout when present. Invalidates the fleet snapshot. Returns true when config changed. Extension-managed providers are rejected before logout or any config mutation with `:type :provider/managed`.
(reprioritize-providers provider-entries)Renumber :priority from vector position; returns a vector.
svar bakes :priority at make-router time from the DECLARED index, and every
candidate sort reads that NUMBER rather than vector order (see
svar…router/candidate-sort-key). Reordering a router's :providers vector
alone therefore promotes a provider in NAME only: the health gate below, a
session pin and a coordinator's models preference each looked applied while
svar kept routing to the original head. Every Vis reorder ends here.
Renumber `:priority` from vector position; returns a vector. svar bakes `:priority` at `make-router` time from the DECLARED index, and every candidate sort reads that NUMBER rather than vector order (see `svar…router/candidate-sort-key`). Reordering a router's `:providers` vector alone therefore promotes a provider in NAME only: the health gate below, a session pin and a coordinator's `models` preference each looked applied while svar kept routing to the original head. Every Vis reorder ends here.
(resolve-default-selection cfg fleet)PURE: the valid PRIMARY provider/model pair cfg's tags name within fleet.
Explicit config wins; an untagged config falls back to the first provider and
its first model, so a fleet always HAS a primary root while it has a provider.
Pure because every surface must resolve the tag the way the router does — channels hold the config they just read, and a channel that re-read global state here would answer for a different machine's config in a test and for a stale one in a race.
PURE: the valid PRIMARY provider/model pair `cfg`'s tags name within `fleet`. Explicit config wins; an untagged config falls back to the first provider and its first model, so a fleet always HAS a primary root while it has a provider. Pure because every surface must resolve the tag the way the router does — channels hold the config they just read, and a channel that re-read global state here would answer for a different machine's config in a test and for a stale one in a race.
(router-rebuild-hook-val)Current registered router-rebuild hook fn (or nil) — inspection/test accessor.
Current registered router-rebuild hook fn (or nil) — inspection/test accessor.
(safe-provider-status provider)Status of a REGISTERED provider descriptor via its :provider/status-fn
(falling back to :provider/detect-fn). Never throws, and never runs longer
than probe-timeout-ms: a callback that is still going by then answers
{:is-authenticated false :error "…timed out…"} — an honest verdict the
surface can paint — instead of parking the thread that asked.
Status of a REGISTERED provider descriptor via its `:provider/status-fn`
(falling back to `:provider/detect-fn`). Never throws, and never runs longer
than `probe-timeout-ms`: a callback that is still going by then answers
`{:is-authenticated false :error "…timed out…"}` — an honest verdict the
surface can paint — instead of parking the thread that asked.(save-default-selection! provider-id model)(save-default-selection! provider-id model source)Persist exactly one PRIMARY provider/model pair — the router root every turn starts on. A fallback tag naming the same provider is dropped, so the two tags always name two providers.
Persist exactly one PRIMARY provider/model pair — the router root every turn starts on. A fallback tag naming the same provider is dropped, so the two tags always name two providers.
(save-fallback-selection! provider-id model)(save-fallback-selection! provider-id model source)Persist exactly one FALLBACK provider/model pair — the router's SECOND root, used when the primary provider cannot serve the turn. Throws when it names the primary's provider, an unknown provider, or a model that provider does not expose.
Persist exactly one FALLBACK provider/model pair — the router's SECOND root, used when the primary provider cannot serve the turn. Throws when it names the primary's provider, an unknown provider, or a model that provider does not expose.
(save-provider-api-key! provider-id api-key)(save-provider-api-key! provider-id api-key source)Persist api-key for provider-id in THIS process' config — the headless
twin of a channel's API-key dialog, so a phone (or a TUI attached to a remote
gateway) never writes provider credentials on the wrong machine. Adds the
provider from its preset when the fleet does not carry it yet.
Persist `api-key` for `provider-id` in THIS process' config — the headless twin of a channel's API-key dialog, so a phone (or a TUI attached to a remote gateway) never writes provider credentials on the wrong machine. Adds the provider from its preset when the fleet does not carry it yet.
(save-providers! providers)(save-providers! providers source)Replace the provider vector in the global string-keyed config while preserving unrelated keys, then refresh runtime provider state.
Replace the provider vector in the global string-keyed config while preserving unrelated keys, then refresh runtime provider state.
(set-router-rebuild-hook! f)Register the function loop calls to rebuild the shared router from current
config and reseed every cached session env. Idempotent; latest wins; nil clears.
Register the function `loop` calls to rebuild the shared router from current config and reseed every cached session env. Idempotent; latest wins; nil clears.
(status-entry-label k)Human label for one provider-status key — the SAME spelling wherever a status map is painted (CLI table, dialog, markdown card), so one map cannot read three ways.
Human label for one provider-status key — the SAME spelling wherever a status map is painted (CLI table, dialog, markdown card), so one map cannot read three ways.
(status-md provider)(status-md provider status limits)The provider status + limits report as MARKDOWN — one rich canonical
form every channel renders natively: the web through its markdown
pipeline and the TUI through its transient Markdown layout walker. The same
facts as status-text, structured instead of flat.
The provider status + limits report as MARKDOWN — one rich canonical form every channel renders natively: the web through its markdown pipeline and the TUI through its transient Markdown layout walker. The same facts as [[status-text]], structured instead of flat.
How long the last live verdict stands in for the probe a non-probing caller refuses to pay. Short on purpose: it only has to bridge the seconds between the fleet a client is showing and the mutation it just made.
How long the last live verdict stands in for the probe a non-probing caller refuses to pay. Short on purpose: it only has to bridge the seconds between the fleet a client is showing and the mutation it just made.
(status-text provider)(status-text provider status limits)Multi-line human status + limits report for a configured provider. The single source for the TUI 'Show Status + Limits' dialog and the web status view.
Multi-line human status + limits report for a configured provider. The single source for the TUI 'Show Status + Limits' dialog and the web status view.
Status keys the Authenticated: line already speaks for. No channel repeats
them as a detail row -- the CLI, the TUI dialog and the web view hide the
same three.
Status keys the `Authenticated:` line already speaks for. No channel repeats them as a detail row -- the CLI, the TUI dialog and the web view hide the same three.
(update-config-provider! provider-id f)(update-config-provider! provider-id f source)Apply f to one persisted provider and save the resulting fleet.
Apply `f` to one persisted provider and save the resulting fleet.
(update-providers! f)(update-providers! f source)Read-modify-write the persisted provider fleet under the machine-store lock:
f receives the CURRENT provider vector and answers the next one.
The read has to happen inside the lock. Reading the fleet, adding one provider and writing the whole vector back is a lost update the moment a second session (or the gateway's own toggle writer) does the same: both saw the same fleet and the last write silently dropped the other's provider.
Read-modify-write the persisted provider fleet under the machine-store lock: `f` receives the CURRENT provider vector and answers the next one. The read has to happen inside the lock. Reading the fleet, adding one provider and writing the whole vector back is a lost update the moment a second session (or the gateway's own toggle writer) does the same: both saw the same fleet and the last write silently dropped the other's provider.
(url-host url)Extract host from URL for display. 'https://llm.blockether.com/v1' -> 'llm.blockether.com'.
Extract host from URL for display. 'https://llm.blockether.com/v1' -> 'llm.blockether.com'.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |