samai-sdk
Core concepts

Guardrails & approval

Built-in guardrails cover the common cases — PII, prompt injection, budget caps, schema validation — and a custom one is just a function you write.

Built-in guardrails

guardrails.ts
import {
  createClient,
  openai,
  createPiiInputGuardrail,
  createPromptInjectionGuardrail,
  createBudgetGuardrail,
} from "samai-sdk";

const budget = createBudgetGuardrail({ maxCostUsd: 5.0 });

const client = createClient({
  provider: openai({ apiKey: "..." }),
  inputGuardrails: [
    createPiiInputGuardrail({ mode: "redact" }),       // scrub PII before it's sent
    createPromptInjectionGuardrail({ mode: "block" }),  // reject jailbreak attempts
    budget.inputGuardrail,                              // reject once budget is spent
  ],
  outputGuardrails: [
    budget.outputGuardrail, // records cost after every call
  ],
});

console.log(budget.getStats()); // { totalTokens, totalCostUsd }

Guardrails run on every call made through the client — nothing extra to remember to add per request. Input guardrails can reject a call before it reaches the model; output guardrails run after, and can also just observe (like budget.outputGuardrail recording cost).

Writing a custom guardrail

An InputGuardrail or OutputGuardrail is just an async function matching a small signature — no base class, no registration step:

custom-guardrail.ts
const client = createClient({
  provider: openai({ apiKey: "..." }),
  inputGuardrails: [
    async ({ messages }) => {
      const last = messages.at(-1);
      const text = typeof last?.content === "string" ? last.content : "";
      if (text.includes("secret-password")) {
        return { allowed: false, reason: "contains sensitive term" };
      }
      return { allowed: true };
    },
  ],
});

Validating structured output

createSchemaGuardrail() checks the model's output against a schema — zod, or any Standard Schema V1 validator — and attaches the parsed, typed result to result.object:

schema-guardrail.ts
import { createSchemaGuardrail } from "samai-sdk";
import { z } from "zod";

const client = createClient({
  provider: anthropic({ apiKey: "..." }),
  outputGuardrails: [
    createSchemaGuardrail(z.object({ summary: z.string(), score: z.number() })),
  ],
});

const result = await client.generate({
  model: "claude-sonnet-4-6",
  messages: [{ role: "user", content: "Return JSON: {summary, score} for this review: ..." }],
});

console.log(result.object); // typed, validated object

Fail closed by default

A tool marked requiresApproval is rejected if no onApprovalRequest handler is configured — see Tools & schemas for the approval workflow itself.