Middleware Reference
Every option, default, and behavior -- checked against the source in src/middleware/.
retries(options?)
Exponential backoff with full jitter. Retries only 429 and 5xx by default (plus ECONNRESET/ETIMEDOUT/ECONNREFUSED network errors) -- a 400 or 401 is never retried, since burning a rate-limit budget on a call that will never succeed is worse than failing fast.
| Option | Type | Default | Notes |
|---|---|---|---|
| max | number | 3 | Total attempts including the first -- max: 3 means up to 2 retries. |
| backoff | "exponential" | "fixed" | "exponential" | Exponential doubles the delay each attempt; fixed uses baseDelayMs every time. |
| baseDelayMs | number | 500 | Base delay for the first retry. |
| maxDelayMs | number | 10000 | Caps exponential growth so it can't run away. |
| isRetryable | (error) => boolean | 429/5xx + network errors | Override to match a provider's own non-standard error shape. |
import { retries } from "@klaroshield/sdk";
const r = retries({ max: 3, backoff: "exponential" });budget(options)
Estimates cost from the provider's own usage field against a small built-in pricing table (gpt-4o-mini, gpt-4o, gpt-4-turbo, gpt-3.5-turbo, claude-3-5-sonnet, claude-3-5-haiku, claude-3-opus), persists spend to .klaro/budget.jsonl. Checked BEFORE each call using spend from prior calls -- it can't know the cost of the call about to happen, only enforce the cap based on everything so far.
| Option | Type | Default | Notes |
|---|---|---|---|
| maxMonthlyUsd | number | required | Monthly spend cap. |
| storage | "local" | "local" | Only "local" exists today -- Klaro Cloud can later sync budgets across machines. |
| onExceeded | (spentUsd, capUsd) => void | throws | Called instead of thrown when the cap is hit, so you decide what happens. |
import { budget } from "@klaroshield/sdk";
const b = budget({
maxMonthlyUsd: 50,
onExceeded: (spent, cap) => console.warn(`Over budget: $${spent}/${cap}`),
});secrets(options?)
Deep-scans every call for OpenAI/Anthropic/AWS/GitHub keys, JWTs, and generic bearer tokens -- deliberately narrow, high-confidence patterns only. A scanner with a high false-positive rate trains developers to ignore it, which is worse than not having one.
| Option | Type | Default | Notes |
|---|---|---|---|
| mode | "mask" | "block" | "mask" | mask replaces matches with a placeholder before the call goes out; block throws instead, so the call never sends at all. |
import { secrets } from "@klaroshield/sdk";
const s = secrets({ mode: "block" }); // throws instead of maskingpii(options?)
Deep-scans for email, phone (US format), SSN, and credit card numbers (real issuer prefix/length rules, not just "16 digits in a row").
| Option | Type | Default | Notes |
|---|---|---|---|
| mode | "mask" | "block" | "mask" | Same semantics as secrets(). |
| types | PiiType[] | all four | Restrict to specific types, e.g. types: ["email", "phone"]. |
import { pii } from "@klaroshield/sdk";
const p = pii({ mode: "mask", types: ["email", "phone"] });validation(options)
Re-runs the WHOLE call (not just re-parsing) if the response isn't valid JSON or fails your schema's safeParse -- LLM non-determinism is often fixed by a fresh attempt, not a re-parse of the same broken output. Duck-typed against Zod's shape, so Zod itself isn't a hard dependency.
| Option | Type | Default | Notes |
|---|---|---|---|
| schema | { safeParse(data): ... } | required | Any object with a Zod-shaped safeParse method. |
| extractText | (result) => string | undefined | tries choices[0].message.content, content[0].text, text | Override for a response shape the default doesn't recognize. |
| maxRetries | number | 2 | How many times to retry on parse/validation failure. |
import { validation } from "@klaroshield/sdk";
import { z } from "zod";
const v = validation({ schema: z.object({ total: z.number() }) });logging(options?)
Persisted to .klaro/logs.jsonl regardless of display format -- klaro stats/explain/dashboard read this file, so format: "silent" means "don't print to stdout," not "don't record anything."
| Option | Type | Default | Notes |
|---|---|---|---|
| format | "pretty" | "json" | "silent" | "pretty" | pretty is colored stdout; json prints one JSON object per line; silent prints nothing but still persists. |
import { logging } from "@klaroshield/sdk";
const l = logging({ format: "json" }); // pipe to your own log aggregator