Project-scoped routers and per-request provider routing.
Builds and rebuilds each project's svar router from configuration, hydrates provider credentials and model metadata, recovers from a rejected credential by refreshing or rerouting, resolves the effective model and its context budget, and estimates request cost, including a provider's fast mode.
Project-scoped routers and per-request provider routing. Builds and rebuilds each project's svar router from configuration, hydrates provider credentials and model metadata, recovers from a rejected credential by refreshing or rerouting, resolves the effective model and its context budget, and estimates request cost, including a provider's fast mode.
(apply-auth-recovery-routing routing)Hand an iteration's auth recovery to Svar. While provider_fallback is on, a
provider whose credentials stay rejected after the credential-refresher
re-sends is left for the next candidate (:on-auth-error :fallback-provider),
and the providers still serving an auth cooldown are excluded up front, so a
dead credential is skipped BEFORE the request instead of being rediscovered with
another 401.
A PIN does not outrank the cooldown. EVERY main turn is pinned — prepare-turn-context
forces the active provider+model into :routing so a provider failure surfaces as
an error the user acts on — so exempting a pinned provider exempted every real
turn: vis logged a five-minute cooldown and then re-probed, re-minted and
re-fell-back on the very next iteration, ~12-16s later (issue #114). A COOLED pin
is released the way Svar releases a pin after an auth rejection. A pin on a
HEALTHY provider is left alone, and the provider's own accepted request re-admits
it immediately.
No-op while provider_fallback is off: with nowhere to route, a rejected
credential surfaces as the provider's own error.
Hand an iteration's auth recovery to Svar. While `provider_fallback` is on, a provider whose credentials stay rejected after the [[credential-refresher]] re-sends is left for the next candidate (`:on-auth-error :fallback-provider`), and the providers still serving an auth cooldown are excluded up front, so a dead credential is skipped BEFORE the request instead of being rediscovered with another 401. A PIN does not outrank the cooldown. EVERY main turn is pinned — `prepare-turn-context` forces the active provider+model into `:routing` so a provider failure surfaces as an error the user acts on — so exempting a pinned provider exempted every real turn: vis logged a five-minute cooldown and then re-probed, re-minted and re-fell-back on the very next iteration, ~12-16s later (issue #114). A COOLED pin is released the way Svar releases a pin after an auth rejection. A pin on a HEALTHY provider is left alone, and the provider's own accepted request re-admits it immediately. No-op while `provider_fallback` is off: with nowhere to route, a rejected credential surfaces as the provider's own error.
(ask-code! opts)One-shot routed svar/ask-code! against the global router.
Plain-text completion + Markdown-code-block extraction — returns the
svar map {:blocks :raw :reasoning :tokens :cost :duration-ms :assistant-message :provider-state}. :blocks is a vec of
{:lang :source} (one entry per Markdown code block); concatenate
yourself with svar.internal.codes/concat-sources if you need a
single string. ask! (JSON-spec) is gone; every Vis caller uses
ask-code!.
One-shot routed `svar/ask-code!` against the global router.
Plain-text completion + Markdown-code-block extraction — returns the
svar map `{:blocks :raw :reasoning :tokens :cost :duration-ms
:assistant-message :provider-state}`. `:blocks` is a vec of
`{:lang :source}` (one entry per Markdown code block); concatenate
yourself with `svar.internal.codes/concat-sources` if you need a
single string. `ask!` (JSON-spec) is gone; every Vis caller uses
`ask-code!`.(build-router config)Build a router, retaining network policy and account-scoped model metadata provenance.
Build a router, retaining network policy and account-scoped model metadata provenance.
(casual-reasoning-level resolved-model user-request reasoning-level)Return the reasoning level Vis sends for user-request to resolved-model.
Only casual chat to a model that chooses its own thinking depth
(:adaptive-reasoning-models in its provider's policy) is special-cased: a
bare greeting names no depth, and the model's adaptive thinking then decides
for itself whether the turn is worth thinking about. Every other request keeps
reasoning-level.
Return the reasoning level Vis sends for `user-request` to `resolved-model`. Only casual chat to a model that chooses its own thinking depth (`:adaptive-reasoning-models` in its provider's policy) is special-cased: a bare greeting names no depth, and the model's adaptive thinking then decides for itself whether the turn is worth thinking about. Every other request keeps `reasoning-level`.
(context-fold-budget context-limit model)Soft folding threshold for the model named model: its family budget
(ctx-engine/prompt-budget-tokens), capped at 90% of a known input window so a
provider rejection always keeps a 10% reserve. An unknown window keeps the
historical 200K advisory budget.
Soft folding threshold for the model named `model`: its family budget (`ctx-engine/prompt-budget-tokens`), capped at 90% of a known input window so a provider rejection always keeps a 10% reserve. An unknown window keeps the historical 200K advisory budget.
(credential-refresher environment)Svar's :refresh-credentials hook for one request: answers the provider that
rejected its credentials with freshly hydrated ones, or nil when Vis cannot
produce new ones for it.
Only OAuth, managed-login and api_key_command providers qualify. On the first
re-send it adopts a peer's newer token or forces one refresh through
try-refresh-provider-token!. Later re-sends, and a rejection inside the
propagation window of a recent refresh, only re-read storage: minting again
would issue another token that is not yet valid. Svar spaces the re-sends by its
:auth-retry-delays-ms schedule and then applies :on-auth-error. Session
headers are merged back because hydration can replace :llm-headers.
Svar's `:refresh-credentials` hook for one request: answers the provider that rejected its credentials with freshly hydrated ones, or nil when Vis cannot produce new ones for it. Only OAuth, managed-login and `api_key_command` providers qualify. On the first re-send it adopts a peer's newer token or forces one refresh through [[try-refresh-provider-token!]]. Later re-sends, and a rejection inside the propagation window of a recent refresh, only re-read storage: minting again would issue another token that is not yet valid. Svar spaces the re-sends by its `:auth-retry-delays-ms` schedule and then applies `:on-auth-error`. Session headers are merged back because hydration can replace `:llm-headers`.
(estimate-token-cost model input-tokens output-tokens)(estimate-token-cost model input-tokens output-tokens opts)Estimate cost from provider usage while preserving cached/non-cached input split.
:cost-multiplier scales every monetary component after svar prices the
canonical usage; token counts remain untouched.
Estimate cost from provider usage while preserving cached/non-cached input split. `:cost-multiplier` scales every monetary component after svar prices the canonical usage; token counts remain untouched.
(fast-mode-cost-multiplier extra-body turn-features provider)The price multiplier of provider's fast mode when this turn requested it,
else 1.0. The multiplier stays provider-gated so fast intent cannot change a
fallback provider's pricing.
The price multiplier of `provider`'s fast mode when this turn requested it, else 1.0. The multiplier stays provider-gated so fast intent cannot change a fallback provider's pricing.
(fast-mode-router router extra-body turn-features)Put a requested fast service tier only on the router entry of the provider that declares it, so Svar fallback cannot carry it to another provider. The router's request bodies are Svar's, spelled with keyword keys.
Put a requested fast service tier only on the router entry of the provider that declares it, so Svar fallback cannot carry it to another provider. The router's request bodies are Svar's, spelled with keyword keys.
(get-router)Get or create the bound project's LLM router from its merged configuration.
Get or create the bound project's LLM router from its merged configuration.
(hydrate-environment-router environment)(hydrate-environment-router environment provider-id)Hydrate only the router snapshot used by this provider attempt. The two-arity form is the real request boundary: it may run managed first-use authentication for the provider the router already resolved, never for unrelated fleet entries.
Hydrate only the router snapshot used by this provider attempt. The two-arity form is the real request boundary: it may run managed first-use authentication for the provider the router already resolved, never for unrelated fleet entries.
(hydrate-request-model-metadata environment routing)Resolve missing or invalid model limits from the selected provider before preflight. Discovery uses the current credential and Svar's account-scoped cache. Explicit configuration wins; only this request's router snapshot changes. A failed or incomplete catalog leaves Svar's metadata diagnostic intact, never a guessed limit.
Resolve missing or invalid model limits from the selected provider before preflight. Discovery uses the current credential and Svar's account-scoped cache. Explicit configuration wins; only this request's router snapshot changes. A failed or incomplete catalog leaves Svar's metadata diagnostic intact, never a guessed limit.
(initiator-llm-headers resolved-model initiator){header initiator} when resolved-model's provider names an
:initiator-header in its policy and initiator is "user" or "agent";
nil otherwise. The provider bills by who started the call.
`{header initiator}` when `resolved-model`'s provider names an
`:initiator-header` in its policy and `initiator` is "user" or "agent";
nil otherwise. The provider bills by who started the call.(iteration-context-limit max-context-tokens
served-model
pinned-model
&
[request-budget])Input ceiling shared by CTX and folding. The routed Svar budget already accounts for the requested output and independent input cap; never subtract output again. An optional caller ceiling may only reduce it. Without a resolved request, retain the served-model, pinned-model, then historical advisory fallback.
Input ceiling shared by CTX and folding. The routed Svar budget already accounts for the requested output and independent input cap; never subtract output again. An optional caller ceiling may only reduce it. Without a resolved request, retain the served-model, pinned-model, then historical advisory fallback.
(iteration-context-model served-model pinned-model & [request-budget])Name of the model whose family prices the soft budget: the routed request's
model, else the served model, else the pinned model — the same precedence that
picks the window in iteration-context-limit.
Name of the model whose family prices the soft budget: the routed request's model, else the served model, else the pinned model — the same precedence that picks the window in `iteration-context-limit`.
(iteration-fold-budget max-context-tokens
served-model
pinned-model
&
[request-budget])Soft folding threshold for the window iteration-context-limit resolves, priced
for iteration-context-model. The historical advisory fallback is not a known
window, so it keeps the advisory budget instead of a reserve below it.
Soft folding threshold for the window `iteration-context-limit` resolves, priced for `iteration-context-model`. The historical advisory fallback is not a known window, so it keeps the advisory budget instead of a reserve below it.
(iteration-initiator iteration)Who started iteration iteration of a turn: the person for the first, Vis
for every tool-call continuation.
Who started iteration `iteration` of a turn: the person for the first, Vis for every tool-call continuation.
(kickoff-session-providers environment)Run provider kickoff hooks against every provider in this session's router.
Preparing the whole fleet before Svar sees it covers internal fallbacks as well as the selected root. The result is recomputed whenever a router is seated, so providers added or reconfigured during a live session receive current metadata.
Run provider kickoff hooks against every provider in this session's router. Preparing the whole fleet before Svar sees it covers internal fallbacks as well as the selected root. The result is recomputed whenever a router is seated, so providers added or reconfigured during a live session receive current metadata.
(llm-text! {:keys [messages system prompt reasoning temperature routing]
:as opts})Fast helper LLM call for extensions.
Uses svar routing (:routing {:optimize :cost}) instead of Vis-side model
name heuristics. The call still goes through svar/ask-code! because Vis no
longer uses the retired ask! structured-output path; :lang "text",
:reasoning :off, and :code-tail-pointer? true make the return a plain
text string under :text. Callers may pass either :messages or :system +
:prompt.
Fast helper LLM call for extensions.
Uses svar routing (`:routing {:optimize :cost}`) instead of Vis-side model
name heuristics. The call still goes through `svar/ask-code!` because Vis no
longer uses the retired `ask!` structured-output path; `:lang "text"`,
`:reasoning :off`, and `:code-tail-pointer? true` make the return a plain
text string under :text. Callers may pass either :messages or :system +
:prompt.(model-routing-status displayed-provider displayed-model)(model-routing-status router displayed-provider displayed-model)Live routing health for the model a channel is DISPLAYING (displayed-provider
displayed-model — the per-session pick or the config default the picker
shows).svar opens a circuit breaker on a provider after repeated transient failures
(5xx / 'Overloaded' 529 / dropped streams) and routes turns to the next
AVAILABLE provider so work keeps flowing. The displayed model is computed
from config ORDER and is NOT breaker-aware, so during an outage the picker
says opus while turns actually land on zai. This reconciles the two: when
the displayed provider's breaker is open/half-open, it reports what svar is
actually serving so the channel can surface
⚠ <displayed> overloaded — routing to <serving>.
Returns nil when the displayed provider is healthy, else
{:overloaded-provider <kw> :overloaded-model <str> :serving-provider <kw> :serving-model <str>}. serving-* is nil if every
provider is down.
Live routing health for the model a channel is DISPLAYING (`displayed-provider`
+ `displayed-model` — the per-session pick or the config default the picker
shows).
svar opens a circuit breaker on a provider after repeated transient failures
(5xx / 'Overloaded' 529 / dropped streams) and routes turns to the next
AVAILABLE provider so work keeps flowing. The displayed model is computed
from config ORDER and is NOT breaker-aware, so during an outage the picker
says `opus` while turns actually land on `zai`. This reconciles the two: when
the displayed provider's breaker is open/half-open, it reports what svar is
actually serving so the channel can surface
`⚠ <displayed> overloaded — routing to <serving>`.
Returns nil when the displayed provider is healthy, else
`{:overloaded-provider <kw> :overloaded-model <str>
:serving-provider <kw> :serving-model <str>}`. `serving-*` is nil if every
provider is down.(normalize-reasoning-level v)Coerce a reasoning level to svar's canonical :low, :balanced or :deep,
or nil. The reasoning_level setting spells the lowest level quick.
Coerce a reasoning level to svar's canonical `:low`, `:balanced` or `:deep`, or nil. The `reasoning_level` setting spells the lowest level `quick`.
(note-auth-rejections! trace auth-failed)Start the auth cooldown for every provider Svar left in one request because it
rejected its credentials, so later iterations skip it up front instead of paying
another 401, refresh and fallback. trace is the request's routing trace, whose
authentication fallbacks name the providers Svar left; auth-failed is the set
Svar attaches when every candidate refused. Each provider is noted once per
request, and only the first trip of a cooldown logs a warning. Call it before
note-provider-request-ok! and reseat-pick-after-auth-rescue!, which read
the cooldown. Answers the noted provider ids.
Start the auth cooldown for every provider Svar left in one request because it rejected its credentials, so later iterations skip it up front instead of paying another 401, refresh and fallback. `trace` is the request's routing trace, whose authentication fallbacks name the providers Svar left; `auth-failed` is the set Svar attaches when every candidate refused. Each provider is noted once per request, and only the first trip of a cooldown logs a warning. Call it before [[note-provider-request-ok!]] and [[reseat-pick-after-auth-rescue!]], which read the cooldown. Answers the noted provider ids.
(note-provider-request-ok! resolved-model iteration-result)Clear the just-refreshed propagation marker AND any auth cooldown for the provider
that ACCEPTED this iteration's request. Keeps credential-refresher's recency
window scoped to the post-refresh settling burst, so a real credential rotation
later is treated as a fresh 401 (re-mint), never misread as propagation lag, and
lets a re-authenticated provider re-enter routing immediately instead of waiting
out [[auth-health/AUTH_COOLDOWN_MS]].
iteration-result's :llm-provider is the provider that actually SERVED the
request; resolved-model is only Vis' pre-call guess — resolve-effective-model
reads the router HEAD, which the turn's pin hoists — so noting the guess let a
turn RESCUED on a peer re-admit the dead credential, and the next iteration
re-probed it (issue #114). No-op when the provider has neither marker.
Clear the just-refreshed propagation marker AND any auth cooldown for the provider that ACCEPTED this iteration's request. Keeps [[credential-refresher]]'s recency window scoped to the post-refresh settling burst, so a real credential rotation later is treated as a fresh 401 (re-mint), never misread as propagation lag, and lets a re-authenticated provider re-enter routing immediately instead of waiting out [[auth-health/AUTH_COOLDOWN_MS]]. `iteration-result`'s `:llm-provider` is the provider that actually SERVED the request; `resolved-model` is only Vis' pre-call guess — `resolve-effective-model` reads the router HEAD, which the turn's pin hoists — so noting the guess let a turn RESCUED on a peer re-admit the dead credential, and the next iteration re-probed it (issue #114). No-op when the provider has neither marker.
(pick-move-event {:keys [from to]})The routing-trace event for a session pick that moved off a dead credential.
Rides the turn's OWN :llm-routing-trace, which every surface already carries end
to end (CLI bracket, TUI bubble footer, companion, read_session usage), so the
note under the answer can say why the model chip changed without a new wire key.
:scope :session-pick marks it as a SESSION-level change rather than this turn's
route, which the summary and the note both anchor on separately.
The routing-trace event for a session pick that moved off a dead credential. Rides the turn's OWN `:llm-routing-trace`, which every surface already carries end to end (CLI bracket, TUI bubble footer, companion, `read_session` usage), so the note under the answer can say why the model chip changed without a new wire key. `:scope :session-pick` marks it as a SESSION-level change rather than this turn's route, which the summary and the note both anchor on separately.
(pick-moved-chunk iteration-position {:keys [from to] :as move})Live-progress chunk announcing that a session pick moved off a dead credential. The
:provider-fallback shape every channel already draws, so the CLI trace names the
swap and the gateway forwards the routing event unchanged.
Live-progress chunk announcing that a session pick moved off a dead credential. The `:provider-fallback` shape every channel already draws, so the CLI trace names the swap and the gateway forwards the routing event unchanged.
(pin-routing-to-model routing resolved-model)Routing svar cannot walk away from once the human turned provider_fallback off.
Fallback ON returns routing untouched — today's rescue ladder decides. Fallback OFF
stamps the model THIS call already resolved to as an explicit :provider/:model pin,
so svar's own provider walk has nowhere to land and a failure comes back as the pinned
provider's own error instead of a peer's answer. A half-named resolution (no provider,
or a blank name) is left alone: pinning half a route names a different model, not this
one.
Routing svar cannot walk away from once the human turned `provider_fallback` off. Fallback ON returns `routing` untouched — today's rescue ladder decides. Fallback OFF stamps the model THIS call already resolved to as an explicit `:provider`/`:model` pin, so svar's own provider walk has nowhere to land and a failure comes back as the pinned provider's own error instead of a peer's answer. A half-named resolution (no provider, or a blank name) is left alone: pinning half a route names a different model, not this one.
(pin-routing-to-provider routing resolved-model)Routing a REFUSAL switch cannot walk out of.
svar re-asks a declined request by replacing :routing {:model …} and keeping the
rest of that map, so a routing which never named a provider lets the router resolve
the fallback name wherever it is cheapest — a peer credential answering for a
content decision the original provider made. Stamping the provider this call already
resolved to keeps the switch inside it. A resolution with no provider is left alone:
half a pin routes to a different model, not this one.
Routing a REFUSAL switch cannot walk out of.
svar re-asks a declined request by replacing `:routing {:model …}` and keeping the
rest of that map, so a routing which never named a provider lets the router resolve
the fallback name wherever it is cheapest — a peer credential answering for a
content decision the original provider made. Stamping the provider this call already
resolved to keeps the switch inside it. A resolution with no provider is left alone:
half a pin routes to a different model, not this one.(provider-extra-body extra-body)Remove every fast service tier a provider declares from caller-level options
after fast-mode-router scoped it to that provider's router entry. Other
tiers and unrelated fields remain.
Remove every fast service tier a provider declares from caller-level options after [[fast-mode-router]] scoped it to that provider's router entry. Other tiers and unrelated fields remain.
(provider-fallback-allowed?)True while a failed turn may be rescued on a DIFFERENT provider or model.
OFF makes the session's pick a contract: a dead credential, an exhausted rate limit or a broken wire ends the turn with that provider's own error instead of quietly answering from somewhere else. Fallback is never free — the peer's prompt cache is cold (~4x input spend for the rest of the session, issue #154) and the answer arrives from a model the human did not choose — so whether to pay that is theirs to decide.
Reads the VALUE rather than enabled?: an unregistered id (a JVM that never
loaded the toggle defaults) must keep TODAY's rescue behaviour, while enabled?
is deliberately fail-CLOSED, which here would strand every turn on one provider.
True while a failed turn may be rescued on a DIFFERENT provider or model. OFF makes the session's pick a contract: a dead credential, an exhausted rate limit or a broken wire ends the turn with that provider's own error instead of quietly answering from somewhere else. Fallback is never free — the peer's prompt cache is cold (~4x input spend for the rest of the session, issue #154) and the answer arrives from a model the human did not choose — so whether to pay that is theirs to decide. Reads the VALUE rather than `enabled?`: an unregistered id (a JVM that never loaded the toggle defaults) must keep TODAY's rescue behaviour, while `enabled?` is deliberately fail-CLOSED, which here would strand every turn on one provider.
(provider-network-policy router resolved-model)Provider defaults baked into the current router, resolved at the request boundary.
Provider defaults baked into the current router, resolved at the request boundary.
(provider-watchdog-timeouts provider-network)Gateway backstops for one provider attempt, kept outside the deadlines Svar
enforces for it, so Svar always names and recovers a silent request first.
:first-output-timeout-ms covers an attempt with no output yet: the response
header wait plus the longest body watchdog. :stall-timeout-ms covers a quiet
stream after output: the longer of the idle and semantic watchdogs. A
whole-request :timeout-ms caps both. Svar announces each re-send, and the
gateway starts a new attempt window there.
Gateway backstops for one provider attempt, kept outside the deadlines Svar enforces for it, so Svar always names and recovers a silent request first. `:first-output-timeout-ms` covers an attempt with no output yet: the response header wait plus the longest body watchdog. `:stall-timeout-ms` covers a quiet stream after output: the longer of the idle and semantic watchdogs. A whole-request `:timeout-ms` caps both. Svar announces each re-send, and the gateway starts a new attempt window there.
(rebuild-router! config)Rebuild the router from the given config. Used when provider settings change.
Forwards :router opts so live config edits (e.g. tuning
:same-provider-delays-ms) take effect on the next set-provider!
without restarting the JVM.
Rebuild the router from the given config. Used when provider settings change. Forwards `:router` opts so live config edits (e.g. tuning `:same-provider-delays-ms`) take effect on the next `set-provider!` without restarting the JVM.
(refusal-fallbacks-for router resolved-model)The refusal-fallback chain for resolved-model WITHIN its own provider, or nil.
The provider's policy names the models whose safety classifier can decline a
request (stop_reason: refusal) and the ordered models to retry it on
(:refusal-fallback); svar owns the actual client-side switch. The current
model is dropped — an identical retry earns the identical decline.
Every candidate is checked against the models router says that provider actually
serves. svar switches by handing the name back as :routing {:model …}, which the
router turns into :force-model: a name this provider does not serve either dies
as a routing failure naming no credential, or resolves on ANOTHER provider — a
content decision quietly moving the session's billing and its cache. No router, or
no sibling on that provider, therefore means no chain and the refusal surfaces as
itself.
nil while refusal_fallback is off.
The refusal-fallback chain for `resolved-model` WITHIN its own provider, or nil.
The provider's policy names the models whose safety classifier can decline a
request (`stop_reason: refusal`) and the ordered models to retry it on
(`:refusal-fallback`); svar owns the actual client-side switch. The current
model is dropped — an identical retry earns the identical decline.
Every candidate is checked against the models `router` says that provider actually
serves. svar switches by handing the name back as `:routing {:model …}`, which the
router turns into `:force-model`: a name this provider does not serve either dies
as a routing failure naming no credential, or resolves on ANOTHER provider — a
content decision quietly moving the session's billing and its cache. No router, or
no sibling on that provider, therefore means no chain and the refusal surfaces as
itself.
nil while `refusal_fallback` is off.(reseat-pick-after-auth-rescue! env iteration-result)Repoint the session's model pick onto the provider that actually answered, once the
pinned provider's credentials are proven dead. Returns the move it made as
{:from {…} :to {…}} for the caller to announce, or nil when nothing moved.
The pick is what the TUI footer chip and the companion header show, and what
prepare-turn-context re-pins on EVERY later turn — so leaving it on a dead
provider is not cosmetic. The human reads a model the session is not running on,
and each lapsed cooldown buys another 401, another forced refresh and another
fallback before the same rescue lands again (issue #154). Moving the pick makes the
rescue hold for the whole session instead of being rediscovered per turn.
Prompt-cache continuity is already gone by the time this runs — the peer never saw
the pinned provider's cache — so the move does not restore it; it stops the session
from paying for the same discovery over and over. set-model! carries the reason,
which rides the session.model_updated broadcast to every attached surface.
Repoint the session's model pick onto the provider that actually answered, once the
pinned provider's credentials are proven dead. Returns the move it made as
`{:from {…} :to {…}}` for the caller to announce, or nil when nothing moved.
The pick is what the TUI footer chip and the companion header show, and what
`prepare-turn-context` re-pins on EVERY later turn — so leaving it on a dead
provider is not cosmetic. The human reads a model the session is not running on,
and each lapsed cooldown buys another 401, another forced refresh and another
fallback before the same rescue lands again (issue #154). Moving the pick makes the
rescue hold for the whole session instead of being rediscovered per turn.
Prompt-cache continuity is already gone by the time this runs — the peer never saw
the pinned provider's cache — so the move does not restore it; it stops the session
from paying for the same discovery over and over. `set-model!` carries the reason,
which rides the `session.model_updated` broadcast to every attached surface.(resolve-effective-model router)(resolve-effective-model router _routing-overrides)Best-effort root model descriptor from router config.
The returned map carries :name (model id, e.g. "gpt-4o") AND
:provider (provider id keyword, e.g. :openai) so every caller
can persist BOTH alongside the model. Earlier versions returned
just the model map and the provider id was silently dropped on
the way to the DB - leaving the meta layer with no way to render
provider/model.
Best-effort root model descriptor from router config. The returned map carries `:name` (model id, e.g. "gpt-4o") AND `:provider` (provider id keyword, e.g. `:openai`) so every caller can persist BOTH alongside the model. Earlier versions returned just the model map and the provider id was silently dropped on the way to the DB - leaving the meta layer with no way to render `provider/model`.
(resolve-model-info router provider-id model-name)Resolved model map for the model a SESSION actually routes to.
resolve-effective-model answers a different question — the router's GLOBAL
root — and a channel that asks it about a session's capabilities describes
the wrong model whenever the session picked something else (which is the
normal case: Ctrl+T and the web picker both write a per-session preference).
provider-id/model-name come from that preference; either may be nil, and
the first provider/model that matches what IS given wins. Falls back to the
root model so a session with no preference still gets an answer.
Resolved model map for the model a SESSION actually routes to. `resolve-effective-model` answers a different question — the router's GLOBAL root — and a channel that asks it about a session's capabilities describes the wrong model whenever the session picked something else (which is the normal case: Ctrl+T and the web picker both write a per-session preference). `provider-id`/`model-name` come from that preference; either may be nil, and the first provider/model that matches what IS given wins. Falls back to the root model so a session with no preference still gets an answer.
(resolved-context-budget environment resolved-model routing extra-body)Resolve the same routed generation controls Svar will use for preflight.
Resolve the same routed generation controls Svar will use for preflight.
(router-for-model router prefs)Return a router variant whose provider/model ORDER reflects a model PREFERENCE,
so svar's router picks + falls back accordingly — WE don't pick one model, we
express the preference and let the inner router decide (no svar change: it
already routes by the router's order). prefs is a model name OR an ORDERED
coll of names; matching models are hoisted to the front in preference order
(within each provider AND across providers), and the rest of the router follows
UNCHANGED as fallback. Blank/unknown prefs → the router as-is (child inherits the
parent's order).
Vector order alone is DECORATION to svar, which selects by provider :priority
and then by the provider's :root model name. So the hoist is also written into
both fields: a matched provider's :root becomes its preferred model and the
whole fleet is renumbered from its new position. Without that, a coordinator's
models list changed the turn card and the cost row while every child turn
still ran the default provider's root model.
Return a router variant whose provider/model ORDER reflects a model PREFERENCE, so svar's router picks + falls back accordingly — WE don't pick one model, we express the preference and let the inner router decide (no svar change: it already routes by the router's order). `prefs` is a model name OR an ORDERED coll of names; matching models are hoisted to the front in preference order (within each provider AND across providers), and the rest of the router follows UNCHANGED as fallback. Blank/unknown prefs → the router as-is (child inherits the parent's order). Vector order alone is DECORATION to svar, which selects by provider `:priority` and then by the provider's `:root` model name. So the hoist is also written into both fields: a matched provider's `:root` becomes its preferred model and the whole fleet is renumbered from its new position. Without that, a coordinator's `models` list changed the turn card and the cost row while every child turn still ran the default provider's root model.
(router-initialized?)True once the bound project's router has been built. Lets a frontend defer the FIRST build to lazy first-use instead of forcing it at startup — so OAuth token fetches (Copilot/Codex) never run at TUI boot.
True once the bound project's router has been built. Lets a frontend defer the FIRST build to lazy first-use instead of forcing it at startup — so OAuth token fetches (Copilot/Codex) never run at TUI boot.
(setting-turn-features snapshot)The provider fast-mode turn features a resolved setting snapshot switches
on, JSON-keyed like a caller's turn_features, or nil. Each provider policy
names its own feature; fast-mode-router keeps its tier on that provider.
The provider fast-mode turn features a resolved setting `snapshot` switches on, JSON-keyed like a caller's `turn_features`, or nil. Each provider policy names its own feature; [[fast-mode-router]] keeps its tier on that provider.
(token-limit v)One context-window candidate as a usable ceiling, or nil.
Candidates come from provider catalogs and from config a human edits, so a window
can arrive as "128000", as 0, or as something that is not a number at all. A
ceiling that is not a positive number is not a smaller budget — it is a reading
that would carry nonsense into every saturation the session prints — so it is
skipped in favour of the next source rather than trusted.
One context-window candidate as a usable ceiling, or nil. Candidates come from provider catalogs and from config a human edits, so a window can arrive as `"128000"`, as 0, or as something that is not a number at all. A ceiling that is not a positive number is not a smaller budget — it is a reading that would carry nonsense into every saturation the session prints — so it is skipped in favour of the next source rather than trusted.
(try-refresh-provider-token! pid rejected)Recover a refreshable auth rejection of provider pid without mutating any router.
rejected is the exact token the refused request carried. Before spending refresh
budget, resolve current storage once: if a peer already installed a different
token, simply adopt it. Otherwise force one persisted refresh. The caller re-reads
storage for the re-send; no global rebuild or cached-environment reseat is involved.
A command-backed provider has no OAuth hook at all: its refresh is dropping
the memoized api_key_command token so the next hydration re-runs the
helper. Same budget, same contract.
Recover a refreshable auth rejection of provider `pid` without mutating any router. `rejected` is the exact token the refused request carried. Before spending refresh budget, resolve current storage once: if a peer already installed a different token, simply adopt it. Otherwise force one persisted refresh. The caller re-reads storage for the re-send; no global rebuild or cached-environment reseat is involved. A command-backed provider has no OAuth hook at all: its refresh is dropping the memoized `api_key_command` token so the next hydration re-runs the helper. Same budget, same contract.
(turn-served-model env)Model map for the provider/model that actually answered this turn's last request,
or nil before anything has. Falls back to the router's root through
resolve-model-info when the served pair is no longer in the fleet.
Model map for the provider/model that actually answered this turn's last request, or nil before anything has. Falls back to the router's root through `resolve-model-info` when the served pair is no longer in the fleet.
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 |