Liking cljdoc? Tell your friends :D

com.blockether.svar.internal.failure

Single source of truth for provider/gateway failure classification.

Why this namespace exists: llm (low-level HTTP retry) and router (provider fallback) each grew their own copy of the same substring/status heuristics, so a newly observed transient had to be added twice and the two layers could disagree about whether the very same exception was retryable. Everything about "what kind of failure is this and is it safe to retry" now lives here, and both layers delegate.

The driving deployment is a self-hosted LiteLLM gateway in front of many upstreams (Bedrock, Azure, vLLM). Under load it produces a steady drizzle of failures that are NOT the caller's fault and NOT the model's answer:

litellm.Timeout: BedrockException: Timeout Error - litellm.Timeout: Connection timed out. Timeout passed=Timeout(connect=5.0, read=600.0 ...) Received Model Group=claude-sonnet-5 Available Model Group Fallbacks=None

HTTP/1.1 header parser received no bytes

No deployments available for selected model group

Every one of those clears on a retry seconds later. Agents that feel "bulletproof" against such a gateway are not doing anything clever: they simply classify the whole transient family as retryable and back off with jitter, instead of surfacing the first blip as a hard failure.

Classification is deliberately EVIDENCE-ORDERED, most specific first: account state (quota/billing) and definitive client errors win over any transient-looking wording, so a 400 whose body merely contains the word "timeout" is never retried.

All predicates are pure: they take an already-lower-cased haystack plus an optional status, or a Throwable, and return data. No IO, no sleeping.

Single source of truth for provider/gateway failure classification.

Why this namespace exists: `llm` (low-level HTTP retry) and `router`
(provider fallback) each grew their own copy of the same substring/status
heuristics, so a newly observed transient had to be added twice and the two
layers could disagree about whether the very same exception was retryable.
Everything about "what kind of failure is this and is it safe to retry"
now lives here, and both layers delegate.

The driving deployment is a self-hosted LiteLLM gateway in front of many
upstreams (Bedrock, Azure, vLLM). Under load it produces a steady drizzle of
failures that are NOT the caller's fault and NOT the model's answer:

  litellm.Timeout: BedrockException: Timeout Error - litellm.Timeout:
  Connection timed out. Timeout passed=Timeout(connect=5.0, read=600.0 ...)
  Received Model Group=claude-sonnet-5 Available Model Group Fallbacks=None

  HTTP/1.1 header parser received no bytes

  No deployments available for selected model group

Every one of those clears on a retry seconds later. Agents that feel
"bulletproof" against such a gateway are not doing anything clever: they
simply classify the whole transient family as retryable and back off with
jitter, instead of surfacing the first blip as a hard failure.

Classification is deliberately EVIDENCE-ORDERED, most specific first:
account state (quota/billing) and definitive client errors win over any
transient-looking wording, so a 400 whose body merely contains the word
"timeout" is never retried.

All predicates are pure: they take an already-lower-cased haystack plus an
optional status, or a Throwable, and return data. No IO, no sleeping.
raw docstring

auth-error?clj

(auth-error? hay)

True when provider text identifies unusable credentials, including expiry. These are hard failures regardless of an incorrect transient HTTP status.

True when provider text identifies unusable credentials, including expiry.
These are hard failures regardless of an incorrect transient HTTP status.
sourceraw docstring

backoff-msclj

(backoff-ms base max-ms)
(backoff-ms base max-ms rand-fn)

Exponential backoff with FULL JITTER, the AWS-recommended shape.

base is the un-jittered delay for this attempt (already multiplied), and the returned sleep is uniform in [base/2, base]. Jitter matters precisely in the failure mode this namespace targets: when one gateway hiccups, every in-flight agent retries at the same instant and re-creates the overload. rand-fn is injectable so tests stay deterministic.

Exponential backoff with FULL JITTER, the AWS-recommended shape.

`base` is the un-jittered delay for this attempt (already multiplied), and
the returned sleep is uniform in [base/2, base]. Jitter matters precisely in
the failure mode this namespace targets: when one gateway hiccups, every
in-flight agent retries at the same instant and re-creates the overload.
`rand-fn` is injectable so tests stay deterministic.
sourceraw docstring

CATEGORIESclj

Stable, user-facing failure families. :retryable? is svar's own retry verdict; :reached-model? answers the question a user actually asks after a failure — did anything run, and can I safely send this again?

Stable, user-facing failure families. `:retryable?` is svar's own retry
verdict; `:reached-model?` answers the question a user actually asks after a
failure — did anything run, and can I safely send this again?
sourceraw docstring

classifyclj

(classify e)

Classifies a provider/gateway failure into a stable, actionable shape.

Returns: {:category one of CATEGORIES :retryable? safe for svar to re-send automatically :reached-model? true / false / nil (unknown) :status upstream HTTP status, when any :request-id correlation id, when the gateway printed one :attempt-categories categories of retained router attempts, when any :all-attempts-category? whether every retained attempt has this category :summary one-line human explanation :next-step one-line human guidance}

Order matters: account state and definitive client errors are decided before any transient wording, so a 400 that merely mentions "timeout" stays a hard failure.

Classifies a provider/gateway failure into a stable, actionable shape.

Returns:
  {:category        one of `CATEGORIES`
   :retryable?      safe for svar to re-send automatically
   :reached-model?  true / false / nil (unknown)
   :status          upstream HTTP status, when any
   :request-id      correlation id, when the gateway printed one
   :attempt-categories categories of retained router attempts, when any
   :all-attempts-category? whether every retained attempt has this category
   :summary         one-line human explanation
   :next-step       one-line human guidance}

Order matters: account state and definitive client errors are decided before
any transient wording, so a 400 that merely mentions "timeout" stays a hard
failure.
sourceraw docstring

CONNECT_HEALTH_WINDOW_MSclj

How long ONE successful connection keeps a host classified as 'healthy'. A message-less java.net.ConnectException (JDK 25 collapses ECONNREFUSED, host-down and transient blips into one indistinguishable, message-free shape) is retried ONLY when THIS host connected successfully inside this window — recent proof the network path works, so the failure is a transient blip. A host never reached (LM Studio/Ollama down, wrong port) is absent from the registry and fails fast instead of eating retry backoff.

How long ONE successful connection keeps a host classified as 'healthy'.
A message-less `java.net.ConnectException` (JDK 25 collapses ECONNREFUSED,
host-down and transient blips into one indistinguishable, message-free
shape) is retried ONLY when THIS host connected successfully inside this
window — recent proof the network path works, so the failure is a transient
blip. A host never reached (LM Studio/Ollama down, wrong port) is absent
from the registry and fails fast instead of eating retry backoff.
sourceraw docstring

connection-error->ex-infoclj

(connection-error->ex-info e url)

Wrap a connect-phase failure in an ex-info carrying a human-readable, provider-aware message plus the :url that could not be reached — the raw java.net.ConnectException message is often nil/terse with no hint of which provider/endpoint died. Keeps the original throwable as cause so transport-retryable? still walks the real java.net.* exception.

Wrap a connect-phase failure in an `ex-info` carrying a human-readable,
provider-aware message plus the `:url` that could not be reached — the raw
`java.net.ConnectException` message is often nil/terse with no hint of which
provider/endpoint died. Keeps the original throwable as cause so
`transport-retryable?` still walks the real `java.net.*` exception.
sourceraw docstring

connection-error-reasonclj

(connection-error-reason e)

Human phrase for a connect-phase failure. Prefers a real (non-blank) message from the cause chain, but on JDK 25 / java.net.http the ConnectException chain is frequently all-nil messages, so fall back to a class-derived phrase instead of leaking a raw classname to the user.

Human phrase for a connect-phase failure. Prefers a real (non-blank) message
from the cause chain, but on JDK 25 / `java.net.http` the `ConnectException`
chain is frequently all-nil messages, so fall back to a class-derived phrase
instead of leaking a raw classname to the user.
sourceraw docstring

connection-error?clj

(connection-error? e)

True when any link in the cause chain is a connect-phase network failure: the TCP/TLS connection to the provider could not be established at all (refused, host down/unreachable, DNS miss, connect-phase timeout). Distinct from mid-stream transport drops, which already carry an HTTP envelope and :stream? data and are classified by transport-retryable?.

True when any link in the cause chain is a connect-phase network failure:
the TCP/TLS connection to the provider could not be established at all
(refused, host down/unreachable, DNS miss, connect-phase timeout). Distinct
from mid-stream transport drops, which already carry an HTTP envelope and
`:stream?` data and are classified by `transport-retryable?`.
sourceraw docstring

DELIBERATE_STREAM_ABORT_TYPESclj

svar's OWN watchdog/caller stream aborts. Each is a DELIBERATE InputStream .close (idle/semantic watchdog fired, or caller cancel) — NOT a transient peer connection drop. The JDK surfaces the intentional close as an IOException whose message is 'Stream closed', which then trips the broad 'closed' substring heuristic. Left unguarded, the HTTP retry loop misreads a watchdog abort as a retryable connection blip and silently re-hammers the SAME provider. These belong to bounded, observable router-level fallback.

svar's OWN watchdog/caller stream aborts. Each is a DELIBERATE `InputStream`
`.close` (idle/semantic watchdog fired, or caller cancel) — NOT a transient
peer connection drop. The JDK surfaces the intentional close as an
`IOException` whose message is 'Stream closed', which then trips the broad
'closed' substring heuristic. Left unguarded, the HTTP retry loop misreads a
watchdog abort as a retryable connection blip and silently re-hammers the
SAME provider. These belong to bounded, observable router-level fallback.
sourceraw docstring

ex-chainclj

(ex-chain e)

Exception + its causes, capped so a self-referential chain cannot loop.

Exception + its causes, capped so a self-referential chain cannot loop.
sourceraw docstring

haystackclj

(haystack e)

Lower-cased message + body of an exception, the text every predicate here consumes. Body first: the provider's own words are more specific than the HTTP wrapper's Exceptional status code: 400.

Lower-cased message + body of an exception, the text every predicate here
consumes. Body first: the provider's own words are more specific than the
HTTP wrapper's `Exceptional status code: 400`.
sourceraw docstring

healthy-host-connect-blip?clj

(healthy-host-connect-blip? e)

True when a message-less ConnectException hit a host that connected successfully moments ago. The prior success is evidence the network is healthy, so this is a transient blip worth retrying; a host never reached stays non-retryable and fails fast.

True when a message-less ConnectException hit a host that connected
successfully moments ago. The prior success is evidence the network is
healthy, so this is a transient blip worth retrying; a host never reached
stays non-retryable and fails fast.
sourceraw docstring

host-connect-health*clj

host -> epoch-ms of its most recent successful connection. Drives the health gate for message-less ConnectExceptions (see CONNECT_HEALTH_WINDOW_MS).

host -> epoch-ms of its most recent successful connection. Drives the health
gate for message-less ConnectExceptions (see `CONNECT_HEALTH_WINDOW_MS`).
sourceraw docstring

host-connection-healthy?clj

(host-connection-healthy? url)

True when url's host connected successfully within CONNECT_HEALTH_WINDOW_MS — recent proof the network path to it works.

True when `url`'s host connected successfully within
`CONNECT_HEALTH_WINDOW_MS` — recent proof the network path to it works.
sourceraw docstring

item-affinity-error?clj

(item-affinity-error? e)

True when the failure says the request replayed a SERVER-MINTED item the answering backend does not own — Azure OpenAI stored-item affinity behind a load-balancing gateway (LiteLLM fronting several Azure resources) is the canonical case. The REQUEST has to change (stop sending those ids); a blind retry lands on another replica and fails identically. The transport layer uses this to fall back to a stateless replay, classify to say so.

True when the failure says the request replayed a SERVER-MINTED item the
answering backend does not own — Azure OpenAI stored-item affinity behind a
load-balancing gateway (LiteLLM fronting several Azure resources) is the
canonical case. The REQUEST has to change (stop sending those ids); a blind
retry lands on another replica and fails identically. The transport layer
uses this to fall back to a stateless replay, `classify` to say so.
sourceraw docstring

low-level-retry-decisionclj

(low-level-retry-decision e)
(low-level-retry-decision e {:keys [router-handles-rate-limit?]})

Canonical same-provider HTTP retry policy for llm/with-retry.

The returned map contains :retry?, a stable :reason, and the canonical :classification. Router policy (provider selection and whether it owns a 429 cooldown) is supplied as options; it never changes failure evidence. Deliberate stream watchdog aborts remain router-owned and are not retried here, even before output.

Canonical same-provider HTTP retry policy for `llm/with-retry`.

The returned map contains `:retry?`, a stable `:reason`, and the canonical
`:classification`. Router policy (provider selection and whether it owns a
429 cooldown) is supplied as options; it never changes failure evidence.
Deliberate stream watchdog aborts remain router-owned and are not retried
here, even before output.
sourceraw docstring

mark-connection-healthy!clj

(mark-connection-healthy! url)

Record that url's host just connected successfully (HTTP response/headers received).

Record that `url`'s host just connected successfully (HTTP response/headers
received).
sourceraw docstring

mark-stateless-items!clj

(mark-stateless-items! base-url)

Remember that base-url's host cannot resolve server-minted item ids.

Remember that `base-url`'s host cannot resolve server-minted item ids.
sourceraw docstring

message-less-connect-exception?clj

(message-less-connect-exception? e)

True when the cause chain holds a java.net.ConnectException with a blank message — the JDK-25 shape that collapses ECONNREFUSED, host-down and transient connectivity blips into one indistinguishable exception that cannot be classified by message substring alone.

True when the cause chain holds a `java.net.ConnectException` with a blank
message — the JDK-25 shape that collapses ECONNREFUSED, host-down and
transient connectivity blips into one indistinguishable exception that
cannot be classified by message substring alone.
sourceraw docstring

NON_RETRYABLE_PROVIDER_LIMIT_PATTERNSclj

Lower-cased substrings that mark provider subscription/quota/billing exhaustion — a hard account state, NOT a transient throttle. Even on HTTP 429 these must NOT retry (mirrors pi's NON_RETRYABLE_PROVIDER_LIMIT_ERROR pattern in packages/ai/src/utils/retry.ts).

Lower-cased substrings that mark provider subscription/quota/billing
exhaustion — a hard account state, NOT a transient throttle. Even on HTTP
429 these must NOT retry (mirrors pi's NON_RETRYABLE_PROVIDER_LIMIT_ERROR
pattern in packages/ai/src/utils/retry.ts).
sourceraw docstring

pre-response-drop?clj

(pre-response-drop? hay)

True when the failure text proves the connection died before any response bytes arrived — the request never reached the model.

True when the failure text proves the connection died before any response
bytes arrived — the request never reached the model.
sourceraw docstring

provider-limit-error?clj

(provider-limit-error? hay)

True when error text/body names a subscription/quota/billing exhaustion (account state) that must never be retried as a transient throttle.

True when error text/body names a subscription/quota/billing exhaustion
(account state) that must never be retried as a transient throttle.
sourceraw docstring

provider-limit-failure?clj

(provider-limit-failure? status hay)

The single hard-account-state question: does this status or body mean the account is out of quota/credit/budget? Never retryable, never re-routable.

The single hard-account-state question: does this status or body mean the
account is out of quota/credit/budget? Never retryable, never re-routable.
sourceraw docstring

PROVIDER_LIMIT_STATUS_CODESclj

HTTP statuses that ARE an account/billing wall on their own, whatever the body says. 402 Payment Required is the whole point of the code.

HTTP statuses that ARE an account/billing wall on their own, whatever the
body says. 402 Payment Required is the whole point of the code.
sourceraw docstring

request-idclj

(request-id e)

Extracts a correlation id from an exception's headers or body, or nil.

Extracts a correlation id from an exception's headers or body, or nil.
sourceraw docstring

retry-after-msclj

(retry-after-ms e)

Server-declared cooldown from a Retry-After header, in ms, or nil. Only the delta-seconds form is honored; an HTTP-date form is ignored rather than mis-parsed into a nonsense sleep.

Server-declared cooldown from a `Retry-After` header, in ms, or nil.
Only the delta-seconds form is honored; an HTTP-date form is ignored rather
than mis-parsed into a nonsense sleep.
sourceraw docstring

retry-sleep-planclj

(retry-sleep-plan e
                  {:keys [fallback-ms elapsed-ms budget-ms respect-retry-after?]
                   :or {respect-retry-after? true}})

How long to wait before the next same-provider attempt, or nil to stop.

A server-declared Retry-After is an INSTRUCTION: waking before the window the provider named is a guaranteed repeat refusal that spends an attempt for nothing (vis session 07d38cba: every retry slept the ladder's own ceiling and re-entered a 60 s cooldown). So a declared cooldown is honored IN FULL or not at all — when it does not fit the phase's remaining budget the plan is nil and the caller hands the request to the next provider instead of retrying into a closed window.

:fallback-ms is the caller's own wait when the server declared none — the jittered exponential of llm/with-retry, the configured ladder entry in the router — and IS clamped to the remaining budget. :elapsed-ms is what the phase has already spent and :budget-ms its cap (nil means RETRY_PHASE_BUDGET_MS, never 'unbounded'). :respect-retry-after? is the router's own policy switch and defaults to true.

How long to wait before the next same-provider attempt, or nil to stop.

A server-declared `Retry-After` is an INSTRUCTION: waking before the window
the provider named is a guaranteed repeat refusal that spends an attempt for
nothing (vis session 07d38cba: every retry slept the ladder's own ceiling and
re-entered a 60 s cooldown). So a declared cooldown is honored IN FULL or not
at all — when it does not fit the phase's remaining budget the plan is nil and
the caller hands the request to the next provider instead of retrying into a
closed window.

`:fallback-ms` is the caller's own wait when the server declared none — the
jittered exponential of `llm/with-retry`, the configured ladder entry in the
router — and IS clamped to the remaining budget. `:elapsed-ms` is what the
phase has already spent and `:budget-ms` its cap (nil means
`RETRY_PHASE_BUDGET_MS`, never 'unbounded'). `:respect-retry-after?` is the
router's own policy switch and defaults to true.
sourceraw docstring

retry-without-server-item-ids?clj

(retry-without-server-item-ids? e)

THE verdict for the stateless-replay self-heal: this exact failure is an item affinity rejection AND nothing was streamed yet, so re-sending the turn without server-minted ids cannot duplicate visible output.

THE verdict for the stateless-replay self-heal: this exact failure is an item
affinity rejection AND nothing was streamed yet, so re-sending the turn
without server-minted ids cannot duplicate visible output.
sourceraw docstring

RETRY_DELAY_LADDER_MSclj

The same budget as RETRY_MAX_ATTEMPTS/RETRY_INITIAL_DELAY_MS/ RETRY_MAX_DELAY_MS/RETRY_MULTIPLIER, spelled as the EXPLICIT sleep schedule the router's same-provider loop consumes (:same-provider-delays-ms).

Measured (vis gateway events, 2026-08-07): every real OpenAI/Codex overload arrives MID-STREAM as Provider stream failed (server_is_overloaded) with status 529, so it never reaches llm/with-retry at all — it is healed by the router loop, which used to own a private [2000 3000 6000] ladder worth 11 s. Two ladders for one failure family meant the pre-stream 529 got 45 s of healing and the far more common streamed 529 got eleven seconds. One vector, derived here, ends that.

The vector is spelled out rather than computed so it reads as the policy it is; llm-retry-visibility-test pins it against the four constants above.

The same budget as `RETRY_MAX_ATTEMPTS`/`RETRY_INITIAL_DELAY_MS`/
`RETRY_MAX_DELAY_MS`/`RETRY_MULTIPLIER`, spelled as the EXPLICIT sleep
schedule the router's same-provider loop consumes (`:same-provider-delays-ms`).

Measured (vis gateway events, 2026-08-07): every real OpenAI/Codex overload
arrives MID-STREAM as `Provider stream failed (server_is_overloaded)` with
status 529, so it never reaches `llm/with-retry` at all — it is healed by the
router loop, which used to own a private `[2000 3000 6000]` ladder worth 11 s.
Two ladders for one failure family meant the pre-stream 529 got 45 s of
healing and the far more common streamed 529 got eleven seconds. One vector,
derived here, ends that.

The vector is spelled out rather than computed so it reads as the policy it
is; `llm-retry-visibility-test` pins it against the four constants above.
sourceraw docstring

RETRY_INITIAL_DELAY_MSclj

Un-jittered delay for the first same-provider retry.

Un-jittered delay for the first same-provider retry.
sourceraw docstring

RETRY_MAX_ATTEMPTSclj

Attempts for ONE same-provider HTTP call, including the first. Sleeps are therefore RETRY_MAX_ATTEMPTS - 1.

Measured (vis, 2026-08-05): an Anthropic overloaded_error (HTTP 529) storm produced attempts 1-4 with 0,5 s → 6 s of jittered sleep, ~7 s of healing in total, and then surfaced as a hard turn failure. A provider overload window is tens of seconds, not seven, so the ladder gave up while the failure was still transient — the classification was never wrong, the BUDGET was. Six sleeps capped at RETRY_MAX_DELAY_MS spend at most ~45 s before the router is allowed to move the request to another provider.

Attempts for ONE same-provider HTTP call, including the first. Sleeps are
therefore `RETRY_MAX_ATTEMPTS - 1`.

Measured (vis, 2026-08-05): an Anthropic `overloaded_error` (HTTP 529) storm
produced attempts 1-4 with 0,5 s → 6 s of jittered sleep, ~7 s of healing in
total, and then surfaced as a hard turn failure. A provider overload window
is tens of seconds, not seven, so the ladder gave up while the failure was
still transient — the classification was never wrong, the BUDGET was. Six
sleeps capped at `RETRY_MAX_DELAY_MS` spend at most ~45 s before the router
is allowed to move the request to another provider.
sourceraw docstring

RETRY_MAX_DELAY_MSclj

Ceiling for one of OUR OWN sleeps — the jittered exponential this namespace invents when the server declared nothing. A Retry-After the provider sent is never clamped to it: waking before the window it named just earns another refusal, so retry-sleep-plan honors a declared cooldown in full or stops.

Ceiling for one of OUR OWN sleeps — the jittered exponential this namespace
invents when the server declared nothing. A `Retry-After` the provider sent
is never clamped to it: waking before the window it named just earns another
refusal, so `retry-sleep-plan` honors a declared cooldown in full or stops.
sourceraw docstring

RETRY_MULTIPLIERclj

Growth factor between attempts.

Growth factor between attempts.
sourceraw docstring

RETRY_PHASE_BUDGET_MSclj

Wall-clock cap for ONE same-provider retry phase. The low-level HTTP ladder (llm/with-retry) and the router's :fallback-after-ms are the SAME phase seen at two layers, so they spend one budget rather than two.

Measured (vis session 07d38cba, 2026-08-07): an anthropic-coding-plan 429 answered Retry-After: 60, again 60, then 42. The request that finally succeeded landed ~163 s after the first refusal — so a budget shorter than that turns a throttle the provider itself scheduled into a hard failure, which is exactly what a 60 s cap does to a one-minute cooldown.

Wall-clock cap for ONE same-provider retry phase. The low-level HTTP ladder
(`llm/with-retry`) and the router's `:fallback-after-ms` are the SAME phase
seen at two layers, so they spend one budget rather than two.

Measured (vis session 07d38cba, 2026-08-07): an `anthropic-coding-plan` 429
answered `Retry-After: 60`, again `60`, then `42`. The request that finally
succeeded landed ~163 s after the first refusal — so a budget shorter than
that turns a throttle the provider itself scheduled into a hard failure,
which is exactly what a 60 s cap does to a one-minute cooldown.
sourceraw docstring

retryable?clj

(retryable? e)

Convenience: svar's retry verdict for one exception.

Convenience: svar's retry verdict for one exception.
sourceraw docstring

RETRYABLE_TRANSIENT_MESSAGE_PATTERNclj

Regex over an error's already-lower-cased message+body marking a transient provider/transport failure that carries no mappable HTTP status.

Regex over an error's already-lower-cased message+body marking a transient
provider/transport failure that carries no mappable HTTP status.
sourceraw docstring

stateless-item-hosts*clj

Hosts proven unable to resolve replayed server-minted item ids. Sticky for the process: the affinity is a property of the deployment, not of one turn.

Hosts proven unable to resolve replayed server-minted item ids. Sticky for
the process: the affinity is a property of the deployment, not of one turn.
sourceraw docstring

stateless-items-host?clj

(stateless-items-host? base-url)

True once this host has rejected a replayed server-minted item id.

True once this host has rejected a replayed server-minted item id.
sourceraw docstring

stream-output-started?clj

(stream-output-started? e)

True when the provider already streamed visible content/reasoning before the failure. Such a call can NOT be silently retried — svar cannot rewind bytes the caller has already rendered.

True when the provider already streamed visible content/reasoning before the
failure. Such a call can NOT be silently retried — svar cannot rewind bytes
the caller has already rendered.
sourceraw docstring

STREAM_INCOMPLETE_TYPESclj

SSE ended before the provider's terminal marker (stream-truncated) or the provider explicitly said response.incomplete (stream-incomplete). Both are transport/provider failures rather than a model answer, and retry (as in the OpenAI Codex CLI) usually succeeds — but only before visible output.

SSE ended before the provider's terminal marker (`stream-truncated`) or the
provider explicitly said `response.incomplete` (`stream-incomplete`). Both
are transport/provider failures rather than a model answer, and retry (as in
the OpenAI Codex CLI) usually succeeds — but only before visible output.
sourceraw docstring

STREAM_WATCHDOG_ERROR_TYPESclj

Typed stream aborts safe to retry only before visible output. The low-level HTTP retry layer excludes these (it would wait one whole timeout per same-provider retry); the router instead performs observable provider fallback.

Typed stream aborts safe to retry only before visible output. The low-level
HTTP retry layer excludes these (it would wait one whole timeout per
same-provider retry); the router instead performs observable provider
fallback.
sourceraw docstring

transient-error?clj

(transient-error? e)
(transient-error? e opts)

The ROUTER's verdict: is this failure soft enough to try another provider / another attempt? Broader than transport-retryable? because the router may legitimately re-route a watchdog timeout or an HTTP status, but never a hard account/quota wall and never after visible output has been streamed.

opts accepts :transient-status-codes (defaults to TRANSIENT_STATUS_CODES).

The ROUTER's verdict: is this failure soft enough to try another provider /
another attempt? Broader than `transport-retryable?` because the router may
legitimately re-route a watchdog timeout or an HTTP status, but never a hard
account/quota wall and never after visible output has been streamed.

`opts` accepts `:transient-status-codes` (defaults to
`TRANSIENT_STATUS_CODES`).
sourceraw docstring

transient-message-error?clj

(transient-message-error? hay status)

True when a statusless / wrapper / gateway transient shows up only in the error TEXT. status gates out definitive client errors so a 400/404 whose body happens to contain transient wording is never retried.

True when a statusless / wrapper / gateway transient shows up only in the
error TEXT. `status` gates out definitive client errors so a 400/404 whose
body happens to contain transient wording is never retried.
sourceraw docstring

transient-network-error?clj

(transient-network-error? e)

True when any link in the cause chain looks like a transient OS/network connection error (see TRANSIENT_NETWORK_ERROR_SUBSTRINGS). Walks the whole chain because babashka.http-client and the router wrap the raw java.net.* exception several layers deep.

True when any link in the cause chain looks like a transient OS/network
connection error (see `TRANSIENT_NETWORK_ERROR_SUBSTRINGS`). Walks the whole
chain because babashka.http-client and the router wrap the raw `java.net.*`
exception several layers deep.
sourceraw docstring

TRANSIENT_NETWORK_ERROR_SUBSTRINGSclj

Lower-cased substrings of transient OS/network-layer connection errors. A brief connectivity blip (wifi handoff, VPN reconnect, captive portal, laptop sleep/wake) surfaces these while the local stack is momentarily down — they clear once the network returns, so they are safe to retry with backoff instead of failing the whole call.

Deliberately excludes "connection refused" (ECONNREFUSED): a RST from the peer usually means a wrong endpoint / down service, not a transient blip, and retrying just hammers it.

Lower-cased substrings of transient OS/network-layer connection errors.
A brief connectivity blip (wifi handoff, VPN reconnect, captive portal,
laptop sleep/wake) surfaces these while the local stack is momentarily
down — they clear once the network returns, so they are safe to retry with
backoff instead of failing the whole call.

Deliberately excludes "connection refused" (ECONNREFUSED): a RST from the
peer usually means a wrong endpoint / down service, not a transient blip,
and retrying just hammers it.
sourceraw docstring

TRANSIENT_STATUS_CODESclj

HTTP statuses that mean "try again", never "your request was wrong".

Beyond the classic 429/5xx set this includes: 408 — Request Timeout. LiteLLM maps litellm.Timeout (the Bedrock connect timeout in the issue above) onto 408, and a bare 408 was previously treated as a definitive 4xx client error and failed on the first blip. 425 — Too Early (TLS early-data replay refused by a fronting proxy). 520-527 — Cloudflare's origin-side family (unknown error, web server down, connection timed out, origin unreachable, timeout occurred, SSL handshake failed, invalid SSL certificate). Cloudflare fronts several model providers, and 522/524 are pure transport blips. 598/599 — non-standard network read/connect timeout codes emitted by some proxies (LiteLLM's httpx wrapper and nginx-based gateways).

Note 529 (Anthropic "overloaded") is standard here, and 429 is included but provider-limit-error? vetoes it when the body proves account exhaustion.

HTTP statuses that mean "try again", never "your request was wrong".

Beyond the classic 429/5xx set this includes:
  408 — Request Timeout. LiteLLM maps `litellm.Timeout` (the Bedrock connect
        timeout in the issue above) onto 408, and a bare 408 was previously
        treated as a definitive 4xx client error and failed on the first blip.
  425 — Too Early (TLS early-data replay refused by a fronting proxy).
  520-527 — Cloudflare's origin-side family (unknown error, web server down,
        connection timed out, origin unreachable, timeout occurred, SSL
        handshake failed, invalid SSL certificate). Cloudflare fronts several
        model providers, and 522/524 are pure transport blips.
  598/599 — non-standard network read/connect timeout codes emitted by some
        proxies (LiteLLM's httpx wrapper and nginx-based gateways).

Note 529 (Anthropic "overloaded") is standard here, and 429 is included but
`provider-limit-error?` vetoes it when the body proves account exhaustion.
sourceraw docstring

transport-retryable?clj

(transport-retryable? e)

SOFT transport failure: a truncated/reset/dropped connection with no HTTP status, safe for the low-level HTTP layer to retry against the SAME provider.

Excludes svar's own deliberate stream aborts (DELIBERATE_STREAM_ABORT_TYPES) — their 'Stream closed' message is indistinguishable from a peer drop, and retrying one silently re-hammers the provider for another full timeout.

SOFT transport failure: a truncated/reset/dropped connection with no HTTP
status, safe for the low-level HTTP layer to retry against the SAME provider.

Excludes svar's own deliberate stream aborts (`DELIBERATE_STREAM_ABORT_TYPES`)
— their 'Stream closed' message is indistinguishable from a peer drop, and
retrying one silently re-hammers the provider for another full timeout.
sourceraw docstring

url->hostclj

(url->host url)

Host component of a URL string, or nil when absent/unparseable.

Host component of a URL string, or nil when absent/unparseable.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close