Toolnexus.ProviderError exception (toolnexus v0.20.0)

Copy Markdown View Source

A provider (LLM or classifier) answered with a non-success status (ADR 0027 / D5).

The failure is carried as a VALUE — status, body, retry_after — so a host can decide what to log without parsing a sentence. The message is the redacted, capped rendering; body keeps the response intact for a host that genuinely wants it.

Redaction policy, identical to the classifier's and now shared with the §8 client path:

  • 401/403 bodies are never echoed (a gateway reflects the Authorization header)
  • known account-identifier keys — user_id, account_id, org_id, organization — are replaced with «redacted», never dropped, so the shape of the body survives
  • the rendered cause is capped at 200 bytes, with an ellipsis

A cap is NOT redaction: the cap runs LAST, after the keys are replaced.

retry_after is the raw Retry-After header verbatim, or nil when the response did not send one. It is deliberately NOT pre-parsed: an HTTP-date, fractional or out-of-range value is information the response really supplied, and a numeric field would have to null it out. The library's own waiting rule (delay-seconds only, falling back to backoff) is separate and unchanged.

Summary

Functions

The account-identifier keys replaced with «redacted» before interpolation.

Render an ALREADY-REDACTED body for an error message: blank on 401/403, then capped at 200 bytes (message-only). Returns "" or ": " <> text.

Replace every known account-identifier value with «redacted», at any depth. Structured bodies are walked; a string body is rewritten with a key-aware regex so a JSON blob that never reached a decoder is redacted too.

The replacement token. Kept as a constant so every port spells it identically.

Types

t()

@type t() :: %Toolnexus.ProviderError{
  __exception__: true,
  body: term(),
  message: String.t(),
  retry_after: String.t() | nil,
  status: non_neg_integer() | nil
}

Functions

account_keys()

@spec account_keys() :: [String.t()]

The account-identifier keys replaced with «redacted» before interpolation.

cause(status, body)

@spec cause(non_neg_integer() | nil, term()) :: String.t()

Render an ALREADY-REDACTED body for an error message: blank on 401/403, then capped at 200 bytes (message-only). Returns "" or ": " <> text.

redact(body)

@spec redact(term()) :: term()

Replace every known account-identifier value with «redacted», at any depth. Structured bodies are walked; a string body is rewritten with a key-aware regex so a JSON blob that never reached a decoder is redacted too.

redacted()

@spec redacted() :: String.t()

The replacement token. Kept as a constant so every port spells it identically.