Skip to content

Architecture Overview

shieldkit is a library middleware layer for the Vercel AI SDK. It does not run as a standalone server. You wrap a LanguageModelV3 once and pass per-request options via providerOptions.aiShield.

Core entry point

shield(model, config?) in src/shield.ts:

  1. Resolves configuration via resolveConfig() (see Configuration)
  2. Creates a shared ShieldRuntime with config, session store, and audit emitter
  3. Chains five custom middleware layers plus AI SDK's extractJsonMiddleware
  4. Returns a wrapped model compatible with generateText, streamText, and related AI SDK APIs
ts
const middleware = [
  createInputGuardrailMiddleware(runtime),
  createCostTrackingMiddleware(runtime),
  createAuditLoggingMiddleware(runtime),
  createOutputGuardrailMiddleware(runtime),
  extractJsonMiddleware(),
];

return wrapLanguageModel({ model, middleware });

ShieldRuntime

Every middleware shares a ShieldRuntime object:

FieldPurpose
configFully resolved ResolvedShieldConfig
sessionStoreIn-memory Map<string, SessionState> for cost budgets
emitAuditFunction that writes to console and/or custom sink

Middleware chain

mermaid
flowchart TD
    caller[generateText / streamText] --> wrap[shield wrapped model]
    wrap --> inputMW[Input Guardrails<br/>transformParams]
    inputMW --> costMW[Cost Tracking<br/>wrapGenerate / wrapStream]
    costMW --> auditMW[Audit Logging]
    auditMW --> outputMW[Output Guardrails<br/>repair + output guards]
    outputMW --> jsonMW[extractJsonMiddleware]
    jsonMW --> baseModel[Base LanguageModelV3]
    baseModel --> jsonMW
    jsonMW --> outputMW
    outputMW --> auditMW
    auditMW --> costMW
    costMW --> caller

Middleware responsibilities

LayerAI SDK hookWhen it runsWhat it does
Input guardrailstransformParamsBefore model callInjection, PII, keyword checks on prompt
Cost trackingwrapGenerate, wrapStreamBefore and after model callPre-call budget check; post-call usage recording
Audit loggingwrapGenerate, wrapStreamAround model callrequest.start, request.complete, request.blocked
Output guardrailswrapGenerate, wrapStreamAfter model callJSON/schema repair loop; output PII/keyword guards
extractJsonAI SDK built-inAfter generationStrips JSON from markdown fences

Middleware order matters: input guards run first (via transformParams), cost and audit wrap the inner call, and output guardrails (including repair) process the model response before it returns to the caller.

Public API surface

Exports from src/index.ts:

CategorySymbols
Coreshield, shieldGenerateText, shieldStreamText
ToolsguardTools
ConfigresolveConfig
SessioncreateShieldContext, createRequestContext, getOrCreateSession, resetSession, sessionStore
ErrorsShieldBlockedError, ShieldBudgetError, ShieldRepairError, ShieldToolError
TypesShieldConfig, AuditLog, SessionState, etc.

See API reference for full details.

Tool guards (separate from model middleware)

guardTools() wraps individual tool execute() functions with allow/deny policies, call limits, and approval gates. It does not use the model middleware chain but shares audit correlation via requestId and sessionId.

See Tool guards.

Mental model

Your app
  └── AI SDK (generateText / streamText)
        └── shield(model)  ← middleware chain
              └── base model (OpenAI / Ollama / …)
        └── guardTools(tools)  ← optional, parallel layer