Recipes
Copy-paste ready. Every snippet here is checked against the shipped @klaroshield/sdk API -- not written from memory, not aspirational.
← Back to KlaroShield
Providers
OpenAI + exponential backoff
Wrap chat.completions.create with retries({ max: 3, backoff: "exponential" }) so transient 429s never bubble up to your users.
View recipe →Anthropic Claude with retries + secret masking
Same pattern as OpenAI -- klaro.wrap() doesn't care which provider's method it's wrapping.
View recipe →Claude fallback when GPT-5 is rate limited
Wrap each provider in its own Klaro instance, then catch a retry-exhausted OpenAI call and fail over to Claude.
View recipe →Google Gemini
Wrap @google/generative-ai's generateContent the same way -- retries and redaction apply before the request ever reaches Google.
View recipe →OpenRouter (OpenAI-compatible endpoint)
OpenRouter uses the OpenAI SDK shape with a different baseURL and key -- the wrap is identical to the direct OpenAI recipe.
View recipe →Vercel AI SDK's generateText
generateText is a plain function, not a method -- wrap it directly, no .bind() needed.
View recipe →Agent frameworks
Frameworks
Express route handler
klaro.wrap() works on any async function -- it doesn't care whether the caller is a server action, a route handler, or a plain script.
View recipe →Fastify route handler
Same wrap, Fastify's handler signature instead of Express's.
View recipe →NestJS injectable service
Construct the Klaro instance once as a private field on the service -- every method on the class shares the same retry/redaction/budget config.
View recipe →Next.js App Router API route
The same klaro instance can be shared across every route in your app -- construct it once, import it everywhere.
View recipe →Mask PII in a Next.js server action
Wrap a server action's model call with pii({ mode: "mask" }) so request logs never contain a real email or phone number.
View recipe →Cloudflare Workers fetch handler
Klaro is pure JS with no Node-only APIs in its call path other than local storage (which reads/writes relative to process.cwd() -- skip logging()'s file persistence on Workers and use format: "silent" or "json" piped to your own sink instead.
View recipe →Validation & structured output
Force structured JSON output with Zod
validation() re-runs the whole call (not just re-parsing) when the model's output fails your schema -- LLM non-determinism is often fixed by a fresh attempt.
View recipe →Validate without Zod
validation() only needs a safeParse() method matching Zod's shape -- write your own if you don't want the dependency.
View recipe →Retries and validation together
Put validation() closest to the call (last .use()) so a transient network error retries via retries() first, and a schema failure retries separately via validation()'s own maxRetries.
View recipe →Budgeting & cost
Handle a budget cap without crashing
budget() throws once the monthly cap is hit by default -- pass onExceeded to decide what happens instead (log it, alert, degrade gracefully).
View recipe →Separate budgets for staging vs production
budget() tracks spend from .klaro/budget.jsonl in the current working directory -- run staging and prod as separate processes/deployments and each gets its own independent cap.
View recipe →Secrets & PII
Block a call outright if it contains a secret
secrets({ mode: "block" }) throws instead of masking -- for pipelines where a leaked key means the call should never go out at all.
View recipe →Only scan for the PII types you actually handle
pii({ types }) lets you skip categories that don't apply -- a support bot that never touches payments doesn't need the credit_card regex running on every call.
View recipe →Retries & reliability
Custom retry predicate
The default only retries 429/5xx and a few network error codes -- pass isRetryable to match a provider's own non-standard error shape.
View recipe →Fixed backoff for a strict internal SLA
Exponential backoff is the default -- switch to fixed delay when you need predictable retry timing instead of growing gaps.
View recipe →Logging & observability
Ship structured logs to your own observability stack
logging({ format: "json" }) prints one JSON object per call instead of the pretty-printed default -- pipe it straight into your log aggregator.
View recipe →Silence console output but keep local history
format: "silent" still writes every call to .klaro/logs.jsonl for klaro stats/explain -- it just doesn't print to stdout.
View recipe →