Single source of truth for provider-error presentation.
Both the turn engine's answer IR (provider-error-ir, rendered by the
Web channel and the TUI's final-answer bubble) and the TUI's
per-iteration trace error rows derive their wording and facts from
THIS namespace, so a provider failure reads IDENTICALLY everywhere it
surfaces — no more divergent PROVIDER_ERROR HTTP 400 vs the polished
WHAT HAPPENED: banner.
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.
Both the turn engine's answer IR (`provider-error-ir`, rendered by the
Web channel and the TUI's final-answer bubble) and the TUI's
per-iteration trace error rows derive their wording and facts from
THIS namespace, so a provider failure reads IDENTICALLY everywhere it
surfaces — no more divergent `PROVIDER_ERROR HTTP 400` vs the polished
`WHAT HAPPENED:` banner.
`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`.
(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-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 (Telegram) can render compactly without parsing the IR back out.
Structured echo of the facts a chat surface (Telegram) can render compactly without parsing the IR back out.
(provider-error-ir err)Canonical answer-IR for a provider failure. Rendered verbatim by the Web
channel and the TUI final-answer bubble; the TUI trace rows mirror its
wording via the helpers above. Blocks in reading order: TITLE → what happened
→ the actionable NEXT STEP → the bare facts → (optional) raw provider body.
The root attrs carry :vis/provider-error + the full :vis/provider-error-data
so a surface can paint a styled CARD instead of walking the generic nodes.
The WHAT HAPPENED: / NEXT STEP: LABELS are split out of the prose and
wrapped in [:strong], so a surface renders the label BOLD while the body
sentence stays plain — instead of the whole line being one undifferentiated
bold/plain span. The helpers still return the label-led prose string (used by
the TUI trace + tests), so the split happens here at the IR boundary.
Canonical answer-IR for a provider failure. Rendered verbatim by the Web channel and the TUI final-answer bubble; the TUI trace rows mirror its wording via the helpers above. Blocks in reading order: TITLE → what happened → the actionable NEXT STEP → the bare facts → (optional) raw provider body. The root attrs carry `:vis/provider-error` + the full `:vis/provider-error-data` so a surface can paint a styled CARD instead of walking the generic nodes. The `WHAT HAPPENED:` / `NEXT STEP:` LABELS are split out of the prose and wrapped in `[:strong]`, so a surface renders the label BOLD while the body sentence stays plain — instead of the whole line being one undifferentiated bold/plain span. The helpers still return the label-led prose string (used by the TUI trace + tests), so the split happens here at the IR boundary.
(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 the answer IR, 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 the answer IR, 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.
(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 |