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)How a provider id authenticates: :oauth (interactive flow owned by
the provider extension), :none (local, no credentials), or
:api-key.
How a provider id authenticates: `:oauth` (interactive flow owned by the provider extension), `:none` (local, no credentials), or `:api-key`.
(authenticated-preset-providers)Registered OAuth / auto-detect providers whose credentials are present
(each provider's LOCAL :provider/detect-fn, no network) but which are
NOT in the persisted fleet. Shaped as minimal picker rows —
{:id … :models …} carrying the preset's default catalog models — so a
channel's model picker lists creds that live OUTSIDE config (OAuth token
files / keychain). Providers with no detect-fn (local no-auth) or no
default models are skipped.
Registered OAuth / auto-detect providers whose credentials are present
(each provider's LOCAL `:provider/detect-fn`, no network) but which are
NOT in the persisted fleet. Shaped as minimal picker rows —
`{:id … :models …}` carrying the preset's default catalog models — so a
channel's model picker lists creds that live OUTSIDE config (OAuth token
files / keychain). Providers with no detect-fn (local no-auth) or no
default models are skipped.(available-presets)Provider presets not yet in the configured fleet — the 'Add Provider' picker contents.
Provider presets not yet in the configured fleet — the 'Add Provider' picker contents.
(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. 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. 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. The list-building below sorts,
so order here is irrelevant.
Union of model names already on the provider map plus the preset / provider `:default-models`, deduped. The list-building below sorts, so order here is irrelevant.
(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.(fetch-models provider)List models for a vis provider via svar/models!.
Returns vec of chat model id strings, or nil on failure. Filters out TTS / embedding / speech / image and provider-excluded models.
Routing through svar means the call automatically picks up
provider-specific OAuth headers (anthropic-version,
anthropic-beta for the Anthropic Claude subscription;
chatgpt-account-id for OpenAI Codex; bare Bearer for everyone
else).
provider is a vis-shaped provider map. We coerce to svar shape
(resolving OAuth tokens via the provider's :provider/get-token-fn
when :api-key is absent) and ask svar.
List models for a vis provider via `svar/models!`. Returns vec of chat model id strings, or nil on failure. Filters out TTS / embedding / speech / image and provider-excluded models. Routing through svar means the call automatically picks up provider-specific OAuth headers (`anthropic-version`, `anthropic-beta` for the Anthropic Claude subscription; `chatgpt-account-id` for OpenAI Codex; bare Bearer for everyone else). `provider` is a vis-shaped provider map. We coerce to svar shape (resolving OAuth tokens via the provider's `:provider/get-token-fn` when `:api-key` is absent) and ask svar.
(format-limit-row {:keys [label scope kind is-unlimited used limit remaining
note window]})GitHub Copilot preset id -> device-flow account type.
GitHub Copilot preset id -> device-flow account type.
(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.
Placeholder status while a real probe runs in the background.
(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).
Local OpenAI-compatible providers that need no credentials.
Local OpenAI-compatible providers that need no credentials.
(model-options provider)(model-options provider default-models show-all?)Selectable model ids for a provider: live-fetched + defaults,
deduped, sorted, env default pinned first. When show-all? is
false, dated snapshot variants (gpt-4o-2024-08-06) are hidden.
Returns {:models [id ...] :hidden-count n} — channels render
their own 'show all' affordance from :hidden-count.
Selectable model ids for a provider: live-fetched + defaults,
deduped, sorted, env default pinned first. When `show-all?` is
false, dated snapshot variants (gpt-4o-2024-08-06) are hidden.
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, then authenticated-preset-providers
(authenticated-but-unconfigured OAuth providers whose creds live outside
config) appended. 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, then `authenticated-preset-providers` (authenticated-but-unconfigured OAuth providers whose creds live outside config) appended. 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.(provider-config-with-models preset models)Minimal persistable provider config for a preset + chosen models.
Minimal persistable provider config for a preset + chosen models.
(provider-limits-safe provider)Normalized limits report for a provider id; an error report instead of a throw.
Normalized limits report for a provider id; an error report instead of a throw.
(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. Local providers
are probed for real; an explicit :api-key is trusted; otherwise
the registered extension's status/detect fns answer. Never throws.
Auth/liveness status for a CONFIGURED provider map. Local providers are probed for real; an explicit `:api-key` is trusted; otherwise the registered extension's status/detect fns answer. Never throws.
(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.
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.
(safe-provider-status provider)Status of a REGISTERED provider descriptor via its :provider/status-fn
(falling back to :provider/detect-fn). Never throws.
Status of a REGISTERED provider descriptor via its `:provider/status-fn` (falling back to `:provider/detect-fn`). Never throws.
(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.
(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.
(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.
(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.
(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 |