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

com.blockether.svar.internal.jsonish

Wrapper for the JsonishParser Java class.

Provides SAP (Schemaless Adaptive Parsing) for malformed JSON from LLMs. Handles unquoted keys/values, trailing commas, markdown code blocks, etc.

Wrapper for the JsonishParser Java class.

Provides SAP (Schemaless Adaptive Parsing) for malformed JSON from LLMs.
Handles unquoted keys/values, trailing commas, markdown code blocks, etc.
raw docstring

com.blockether.svar.internal.llm

LLM client layer: HTTP transport, message construction, and all LLM interaction functions (ask!, abstract!, eval!, refine!, models!, sample!).

LLM client layer: HTTP transport, message construction, and all LLM interaction
functions (ask!, abstract!, eval!, refine!, models!, sample!).
raw docstring

com.blockether.svar.internal.modelsdev

models.dev catalog loader.

Reads the bundled resources/models.dev.json snapshot (refreshed via make refresh-models) and exposes a normalized view that downstream router code merges with KNOWN_PROVIDERS wire/policy overlay.

Catalog wins for: pricing, context, modalities, capability flags, release dates, family. svar overlay wins for: api-style, reasoning-style, llm-headers, env-keys, base-url, paths, extra-body, exclude-models, rate budgets, default-models.

Plan-vs-retail pricing — per-provider entries on models.dev already reflect plan zeros (e.g. github-copilot, zai-coding-plan ship {input:0, output:0}). For svar's :openai-codex and :anthropic-coding-plan we explicitly want retail pricing (the user pays at API rates once metered), so the overlay declares :pricing-source to redirect catalog lookup to the retail provider.

models.dev catalog loader.

Reads the bundled `resources/models.dev.json` snapshot (refreshed via
`make refresh-models`) and exposes a normalized view that downstream
router code merges with `KNOWN_PROVIDERS` wire/policy overlay.

Catalog wins for: pricing, context, modalities, capability flags,
release dates, family.
svar overlay wins for: api-style, reasoning-style, llm-headers,
env-keys, base-url, paths, extra-body, exclude-models, rate budgets,
default-models.

Plan-vs-retail pricing — per-provider entries on models.dev already
reflect plan zeros (e.g. `github-copilot`, `zai-coding-plan` ship
{input:0, output:0}). For svar's `:openai-codex` and
`:anthropic-coding-plan` we explicitly want **retail** pricing
(the user pays at API rates once metered), so the overlay declares
`:pricing-source` to redirect catalog lookup to the retail provider.
raw docstring

com.blockether.svar.internal.ratelimit

Provider rate-limit header parsing — single source of truth for the :rate-limit map surfaced on ask! / ask-code! results.

Providers send their quota-reset clock on EVERY response (success or 429) via headers; svar used to throw those headers away, so status renderers (vis footer, CLI) had no reset date to show. This ns parses the per-provider header families into ONE canonical shape:

{:resets-at-ms <epoch-ms> ;; soonest effective reset across windows :remaining <long> ;; requests remaining in the primary window :limit <long> ;; primary-window limit :windows {:requests {:resets-at-ms :remaining :limit} :tokens {:resets-at-ms :remaining :limit} :unified {:resets-at-ms :remaining :status}} :raw {<header> <value>}} ;; the rate-limit headers, verbatim

:resets-at-ms is the caller-facing 'effective date of reset' (epoch millis, UTC). Two provider dialects:

  • Anthropic (:anthropic — Claude API + coding-plan OAuth): reset values are ABSOLUTE — either unix epoch seconds (anthropic-ratelimit-unified-reset) or RFC-3339 timestamps (anthropic-ratelimit-{requests,tokens}-reset). The coding plan's 5h/weekly window rides on the -unified- family. CAVEAT: the Claude coding-plan OAuth proxy (sk-ant-oat* tokens) does NOT forward the anthropic-ratelimit-* headers, so parse returns nil there. For the coding plan callers must prefer the OAuth usage endpoint (/api/oauth/usage) as the source of truth and treat this header parse as best-effort fallback only.

  • OpenAI / Codex (:openai-compatible-*): reset values are RELATIVE Go-style durations (x-ratelimit-reset-requests = "6m0s", "1s", "100ms", "1h2m3s"); resets-at-ms = now + duration.

Returns nil when no rate-limit headers are present (never throws — a quirky header must not break the LLM call).

Provider rate-limit header parsing — single source of truth for the
`:rate-limit` map surfaced on `ask!` / `ask-code!` results.

Providers send their quota-reset clock on EVERY response (success or
429) via headers; svar used to throw those headers away, so status
renderers (vis footer, CLI) had no reset date to show. This ns parses
the per-provider header families into ONE canonical shape:

  {:resets-at-ms <epoch-ms>       ;; soonest effective reset across windows
   :remaining <long>              ;; requests remaining in the primary window
   :limit     <long>              ;; primary-window limit
   :windows   {:requests {:resets-at-ms :remaining :limit}
               :tokens   {:resets-at-ms :remaining :limit}
               :unified  {:resets-at-ms :remaining :status}}
   :raw       {<header> <value>}} ;; the rate-limit headers, verbatim

`:resets-at-ms` is the caller-facing 'effective date of reset' (epoch
millis, UTC). Two provider dialects:

- Anthropic (`:anthropic` — Claude API + coding-plan OAuth): reset
  values are ABSOLUTE — either unix epoch seconds
  (`anthropic-ratelimit-unified-reset`) or RFC-3339 timestamps
  (`anthropic-ratelimit-{requests,tokens}-reset`). The coding plan's
  5h/weekly window rides on the `-unified-` family. CAVEAT: the
  Claude *coding-plan* OAuth proxy (sk-ant-oat* tokens) does NOT
  forward the `anthropic-ratelimit-*` headers, so `parse` returns
  nil there. For the coding plan callers must prefer the OAuth
  usage endpoint (`/api/oauth/usage`) as the source of truth and
  treat this header parse as best-effort fallback only.

- OpenAI / Codex (`:openai-compatible-*`): reset values are RELATIVE
  Go-style durations (`x-ratelimit-reset-requests` = "6m0s",
  "1s", "100ms", "1h2m3s"); resets-at-ms = now + duration.

Returns nil when no rate-limit headers are present (never throws — a
quirky header must not break the LLM call).
raw docstring

com.blockether.svar.internal.spec

Structured output specification system for LLM responses.

This namespace provides a DSL for defining expected output structures, converting specs to LLM prompts, and parsing LLM responses back to Clojure data.

Primary functions:

  • field - Define a field with name, type, cardinality, and description
  • spec - Create a spec from field definitions
  • build-ref-registry - Build a registry of referenced specs for nested types
  • spec->prompt - Generate LLM prompt text from a spec (sent to LLM)
  • str->data - Parse LLM response string to Clojure data (schemaless)
  • str->data-with-spec - Parse LLM response with spec-based type coercion
  • validate-data - Validate parsed data against a spec
  • data->str - Serialize Clojure data to JSON string

Data Flow:

  1. Define spec with spec and field functions
  2. Generate prompt with spec->prompt (sent to LLM)
  3. Parse response with str->data-with-spec (LLM response -> typed Clojure map)
  4. Optionally validate with validate-data
  5. Optionally serialize with data->str
Structured output specification system for LLM responses.

This namespace provides a DSL for defining expected output structures,
converting specs to LLM prompts, and parsing LLM responses back to Clojure data.

Primary functions:
- `field` - Define a field with name, type, cardinality, and description
- `spec` - Create a spec from field definitions
- `build-ref-registry` - Build a registry of referenced specs for nested types
- `spec->prompt` - Generate LLM prompt text from a spec (sent to LLM)
- `str->data` - Parse LLM response string to Clojure data (schemaless)
- `str->data-with-spec` - Parse LLM response with spec-based type coercion
- `validate-data` - Validate parsed data against a spec
- `data->str` - Serialize Clojure data to JSON string

Data Flow:
1. Define spec with `spec` and `field` functions
2. Generate prompt with `spec->prompt` (sent to LLM)
3. Parse response with `str->data-with-spec` (LLM response -> typed Clojure map)
4. Optionally validate with `validate-data`
5. Optionally serialize with `data->str`
raw docstring

com.blockether.svar.internal.usage

Canonical token-usage shape — single source of truth across providers.

Phase A of svar 0.6.0. Replaces the hybrid pre-0.6 shape that emitted :prompt_tokens with provider-dependent semantics (Anthropic additive, OpenAI inclusive) under the same key. Every provider normalizer now produces the SAME shape; downstream consumers read one set of keys regardless of which model served the call.

Canonical shape — INVARIANT: regular + cache-write + cache-read = input-tokens:

{:input-tokens <long> ;; TOTAL prompt tokens (always inclusive) :output-tokens <long> ;; TOTAL completion tokens :input-tokens-details {:regular <long> ;; not from cache, not written :cache-write <long> ;; written this request (1.25× input rate, anthropic; 0 else) :cache-read <long>} ;; served from cache (0.1× input rate, anthropic; ~10-50% off, openai) :output-tokens-details {:reasoning <long>} ;; subset of output-tokens :total-tokens <long> ;; convenience = input-tokens + output-tokens :raw <map>} ;; original provider envelope (debug / forensics)

Provider differences:

  • Anthropic Messages API (:anthropic api-style): RAW input_tokens excludes cached AND cache-creation. Canonical :input-tokens adds all three so the value is TOTAL.

  • OpenAI Chat / Responses (:openai-compatible-* api-styles): RAW prompt_tokens / input_tokens IS the total. Cached subset lives under prompt_tokens_details.cached_tokens / input_tokens_details.cached_tokens. No native cache-write concept (server-managed implicit caching), so :cache-write is always 0 here UNLESS the provider proxies Anthropic via OpenRouter and surfaces cache_creation_input_tokens as a pydantic extra field.

  • Z.ai (GLM coding-plan / OpenAI-compatible-chat): same as OpenAI.

Industry alignment (May 2026):

  • Vercel AI SDK V3 spec (vercel/ai#9921): inputTokens always TOTAL with inputTokensDetails {regular, cacheWrite, cacheRead}.
  • OpenTelemetry gen_ai.usage.input_tokens (≥ v1.37): SHOULD be inclusive (all kinds of input tokens).
  • Claude Code official statusline JSON: context_window.total_input_tokens = input + cache_creation + cache_read.

Reject: the additive convention (e.g. litellm PR #23342) leaves total_tokens inconsistent with prompt_tokens and breaks naive aggregation downstream.

Canonical token-usage shape — single source of truth across providers.

Phase A of svar 0.6.0. Replaces the hybrid pre-0.6 shape that emitted
`:prompt_tokens` with provider-dependent semantics (Anthropic
additive, OpenAI inclusive) under the same key. Every provider
normalizer now produces the SAME shape; downstream consumers read
one set of keys regardless of which model served the call.

Canonical shape — INVARIANT: `regular + cache-write + cache-read = input-tokens`:

  {:input-tokens          <long>   ;; TOTAL prompt tokens (always inclusive)
   :output-tokens         <long>   ;; TOTAL completion tokens
   :input-tokens-details  {:regular     <long>   ;; not from cache, not written
                           :cache-write <long>   ;; written this request (1.25× input rate, anthropic; 0 else)
                           :cache-read  <long>}  ;; served from cache (0.1× input rate, anthropic; ~10-50% off, openai)
   :output-tokens-details {:reasoning   <long>}  ;; subset of output-tokens
   :total-tokens          <long>   ;; convenience = input-tokens + output-tokens
   :raw                   <map>}   ;; original provider envelope (debug / forensics)

Provider differences:

- Anthropic Messages API (`:anthropic` api-style): RAW
  `input_tokens` excludes cached AND cache-creation. Canonical
  `:input-tokens` adds all three so the value is TOTAL.

- OpenAI Chat / Responses (`:openai-compatible-*` api-styles): RAW
  `prompt_tokens` / `input_tokens` IS the total. Cached subset lives
  under `prompt_tokens_details.cached_tokens` /
  `input_tokens_details.cached_tokens`. No native `cache-write`
  concept (server-managed implicit caching), so `:cache-write` is
  always 0 here UNLESS the provider proxies Anthropic via OpenRouter
  and surfaces `cache_creation_input_tokens` as a pydantic extra
  field.

- Z.ai (GLM coding-plan / OpenAI-compatible-chat): same as OpenAI.

Industry alignment (May 2026):

- Vercel AI SDK V3 spec (vercel/ai#9921): `inputTokens` always TOTAL
  with `inputTokensDetails {regular, cacheWrite, cacheRead}`.
- OpenTelemetry `gen_ai.usage.input_tokens` (≥ v1.37): SHOULD be
  inclusive (all kinds of input tokens).
- Claude Code official statusline JSON:
  `context_window.total_input_tokens = input + cache_creation + cache_read`.

Reject: the additive convention (e.g. litellm PR #23342) leaves
`total_tokens` inconsistent with `prompt_tokens` and breaks naive
aggregation downstream.
raw docstring

com.blockether.svar.internal.util

Shared internal utilities.

Shared internal utilities.
raw 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