Single source of truth for provider-error presentation.
Typed provider-error content and per-iteration trace rows derive their wording and facts from this namespace, so a failure reads identically everywhere.
err is the error map carried on a trace entry / ex-info:
{:message .. :data {:status .. :body .. :request-id ..} ..}. Every
helper tolerates the bare ex-info shape too (via ex-message).
Single source of truth for provider-error presentation.
Typed provider-error content and per-iteration trace rows derive their wording
and facts from this namespace, so a failure reads identically everywhere.
`err` is the error map carried on a trace entry / ex-info:
`{:message .. :data {:status .. :body .. :request-id ..} ..}`. Every
helper tolerates the bare ex-info shape too (via `ex-message`).(attempt->line {:keys [provider model status reason]})One attempt → a compact provider/model: <status> <reason> line, e.g.
anthropic/claude-opus-4: 429 rate-limit.
One attempt → a compact `provider/model: <status> <reason>` line, e.g. `anthropic/claude-opus-4: 429 rate-limit`.
(context-overflow-error? err)True only for the canonical typed context-window failure.
True only for the canonical typed context-window failure.
(empty-content-error? err)True when the failure is svar's TYPED :svar.llm/empty-content — the
provider ACCEPTED the request and answered a normal HTTP-200 stream that
carried no text and no tool call. Dispatches on the typed ex-data
(attached verbatim under :data on trace-entry error maps) — NEVER on
message text.
Emphatically NOT a rejection: the model ran and emitted nothing. Presenting it as 'the provider rejected the request' is false and points the user at auth/quota — the wrong place.
True when the failure is svar's TYPED `:svar.llm/empty-content` — the provider ACCEPTED the request and answered a normal HTTP-200 stream that carried no text and no tool call. Dispatches on the typed `ex-data` (attached verbatim under `:data` on trace-entry error maps) — NEVER on message text. Emphatically NOT a rejection: the model ran and emitted nothing. Presenting it as 'the provider rejected the request' is false and points the user at auth/quota — the wrong place.
(generic-wrapper-message? message)True when the svar wrapper message is just a status echo (e.g.
Exceptional status code: 400) that adds nothing beyond the HTTP row.
True when the svar wrapper message is just a status echo (e.g. `Exceptional status code: 400`) that adds nothing beyond the HTTP row.
(provider-error-attempts err)The per-provider failure records svar accumulates on an all-providers-exhausted
error — [{:provider :model :status :reason :error} …], one per provider tried.
Empty when svar didn't attach them (older svar / a non-routing failure).
The per-provider failure records svar accumulates on an `all-providers-exhausted`
error — `[{:provider :model :status :reason :error} …]`, one per provider tried.
Empty when svar didn't attach them (older svar / a non-routing failure).(provider-error-attempts-summary err)The attempts joined into ONE scannable line (a: 429 rate-limit · b: 401 auth),
or nil when there are none. The at-a-glance 'why each provider bowed out'.
The attempts joined into ONE scannable line (`a: 429 rate-limit · b: 401 auth`), or nil when there are none. The at-a-glance 'why each provider bowed out'.
(provider-error-content err)Canonical typed content for a provider failure. The error remains data; channels decide how to present it and Markdown is not used as an envelope.
Canonical typed content for a provider failure. The error remains data; channels decide how to present it and Markdown is not used as an envelope.
(provider-error-explanation err)The WHAT HAPPENED: prose line — the single canonical human sentence for this
failure, shared by every surface. The actionable step lives in
provider-error-next-step (a separate block), so this is JUST the diagnosis.
The `WHAT HAPPENED:` prose line — the single canonical human sentence for this failure, shared by every surface. The actionable step lives in `provider-error-next-step` (a separate block), so this is JUST the diagnosis.
(provider-error-facts err)Ordered [label value] rows of the bare facts (no prose). Same set the
IR renders as a <ul> and the TUI renders as plain rows.
Ordered `[label value]` rows of the bare facts (no prose). Same set the IR renders as a `<ul>` and the TUI renders as plain rows.
(provider-error-info err)Structured echo of the facts a chat surface can render compactly without parsing the IR back out.
Structured echo of the facts a chat surface can render compactly without parsing the IR back out.
(provider-error-next-step err)The actionable NEXT STEP: line — what the user should DO — by kind. Kept
SEPARATE from the diagnosis so surfaces can make it prominent.
The actionable `NEXT STEP:` line — what the user should DO — by kind. Kept SEPARATE from the diagnosis so surfaces can make it prominent.
(provider-error-raw-body err)Truncated raw upstream body — surfaced ONLY when no structured provider message could be extracted (HTML pages, bare 5xx). nil otherwise so the readable message isn't echoed twice.
Truncated raw upstream body — surfaced ONLY when no structured provider message could be extracted (HTML pages, bare 5xx). nil otherwise so the readable message isn't echoed twice.
(provider-error-title err)A SHORT headline for the failure, by kind — the card title on every surface.
A SHORT headline for the failure, by kind — the card title on every surface.
(provider-structured-message body)Human-readable message a provider put in its error envelope, if any.
Covers Anthropic/OpenAI {:error {:message}}, bare {:message}, and
Codex/ChatGPT {:detail}. nil when the body has no message field (HTML
page, bare 5xx) — the caller then surfaces the raw body instead.
Human-readable message a provider put in its error envelope, if any.
Covers Anthropic/OpenAI `{:error {:message}}`, bare `{:message}`, and
Codex/ChatGPT `{:detail}`. nil when the body has no message field (HTML
page, bare 5xx) — the caller then surfaces the raw body instead.(split-error-label s)Split a provider-error prose line into [label body] at the first :.
provider-error-explanation / provider-error-next-step always lead with
an ALL-CAPS label (WHAT HAPPENED: / NEXT STEP:). This returns
["<LABEL>: " "<body sentence>"] so a surface can render the label
distinctly (bold in final prose, SGR/bold sentinels in the TUI trace)
while the body stays plain — ONE convention, shared by every surface, so
the label/body split never diverges between the IR and the TUI recap.
Returns [nil s] (whole string as the body, no label) when s does not
follow the ALL-CAPS: convention, so callers can treat the label as
optional without a separate nil-check.
Split a provider-error prose line into `[label body]` at the first `: `. `provider-error-explanation` / `provider-error-next-step` always lead with an ALL-CAPS label (`WHAT HAPPENED: ` / `NEXT STEP: `). This returns `["<LABEL>: " "<body sentence>"]` so a surface can render the label distinctly (bold in final prose, SGR/bold sentinels in the TUI trace) while the body stays plain — ONE convention, shared by every surface, so the label/body split never diverges between the IR and the TUI recap. Returns `[nil s]` (whole string as the body, no label) when `s` does not follow the `ALL-CAPS:` convention, so callers can treat the label as optional without a separate nil-check.
(stream-timeout-error? err)True when the failure is one of svar's TYPED stream watchdogs firing:
:svar.core/stream-semantic-timeout (transport alive, zero model/progress
events inside the budget) or :svar.core/stream-idle-timeout (no bytes at
all). Dispatches on typed ex-data — NEVER on message text.
Emphatically NOT a rejection and NOT an outage: the provider accepted the request, the model was (as far as anyone knows) still working, and VIS hung up. Presenting it as 'the provider rejected the request before the model ran' is false and points the user at auth/quota — the wrong place.
True when the failure is one of svar's TYPED stream watchdogs firing: `:svar.core/stream-semantic-timeout` (transport alive, zero model/progress events inside the budget) or `:svar.core/stream-idle-timeout` (no bytes at all). Dispatches on typed `ex-data` — NEVER on message text. Emphatically NOT a rejection and NOT an outage: the provider accepted the request, the model was (as far as anyone knows) still working, and VIS hung up. Presenting it as 'the provider rejected the request before the model ran' is false and points the user at auth/quota — the wrong place.
(tool-schema-rejection-message? message)True when a provider rejected a native tool because its input-schema root uses a JSON Schema union that provider tool APIs forbid.
True when a provider rejected a native tool because its input-schema root uses a JSON Schema union that provider tool APIs forbid.
(transport-throwable? t)True when Throwable t is a CONNECTION/transport failure — the SAME
classification transport-error? makes from a parsed provider error, but
taken straight off a Throwable so the RETRY gate and the human message agree.
Walks the whole cause chain for the message text. Idempotent by definition:
a transport failure means the request never reached the model (the socket
closed with no bytes, was reset/refused/timed out, DNS/TLS died), so it is
ALWAYS safe to retry — regardless of whether a response STREAM had started
(a pre-response failure carries no :stream?, yet is the safest retry of
all). A real REJECTION carries an HTTP status, so transport-error?'s
nil status guard keeps 4xx/5xx out of this path.
True when Throwable `t` is a CONNECTION/transport failure — the SAME classification `transport-error?` makes from a parsed provider error, but taken straight off a Throwable so the RETRY gate and the human message agree. Walks the whole cause chain for the message text. Idempotent by definition: a transport failure means the request never reached the model (the socket closed with no bytes, was reset/refused/timed out, DNS/TLS died), so it is ALWAYS safe to retry — regardless of whether a response STREAM had started (a pre-response failure carries no `:stream?`, yet is the safest retry of all). A real REJECTION carries an HTTP status, so `transport-error?`'s `nil status` guard keeps 4xx/5xx out of this path.
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 |