Vercel AI SDK

The Vercel AI SDK builds a provider object and hands models from it to generateText, streamText, and the rest. You create that provider with createOpenAI instead of importing the default one, giving it the gateway’s baseURL and your Vulnetix key. Nothing downstream changes.

Warning The Vercel AI SDK does not read OPENAI_BASE_URL. The environment variables written by vulnetix ai-firewall install shell have no effect here. Without createOpenAI({ baseURL }) the SDK calls the provider directly and the firewall never sees the request — no error, no warning, just unfirewalled traffic.

Install

npm install ai @ai-sdk/openai

Configure

Have the CLI print the snippet with your organisation UUID already in it:

vulnetix ai-firewall snippet --lang ts --sdk vercel-ai --provider openai

Or by hand — note the custom provider, createOpenAI, rather than the default openai export:

import { createOpenAI } from '@ai-sdk/openai'
import { generateText } from 'ai'

const org = process.env.VULNETIX_ORG_UUID

const vulnetix = createOpenAI({
  baseURL: `https://guardrails.vulnetix.com/openai/${org}/v1`,
  apiKey: process.env.VULNETIX_API_KEY!, // Vulnetix key, not an OpenAI key
})

const { text } = await generateText({
  model: vulnetix('gpt-4o-mini'),
  prompt: 'Hello',
})
console.log(text)

Define vulnetix once and export it; every generateText, streamText, generateObject, and streamObject call in the app takes its model from that provider and is firewalled by construction. A stray import { openai } from '@ai-sdk/openai' anywhere else in the codebase is a request that bypasses the gateway — worth a lint rule.

For a non-OpenAI provider, keep createOpenAI (the wire format is OpenAI-compatible), change the slug in the path, and pass that provider’s own model string: vulnetix('llama-3.3-70b-versatile') against /groq/{orgUuid}/v1.

Streaming

Unchanged. streamText works exactly as before, including in a Next.js route handler returning result.toDataStreamResponse().

const result = streamText({
  model: vulnetix('gpt-4o-mini'),
  prompt: 'Hello',
})

for await (const delta of result.textStream) {
  process.stdout.write(delta)
}

Handling a guardrail block

A request your policy refuses never reaches the provider. It arrives as an HTTP 403, which the AI SDK surfaces as an APICallError carrying the status code and the raw response body — the body is where code and blocked_by live.

import { APICallError } from 'ai'

type FirewallError = { code?: string; blocked_by?: string; message?: string }

try {
  const { text } = await generateText({ model: vulnetix('gpt-4o-mini'), prompt })
} catch (err) {
  if (APICallError.isInstance(err) && err.statusCode === 403) {
    const body = JSON.parse(err.responseBody ?? '{}') as { error?: FirewallError }
    const code = body.error?.code           // request_blocked, model_denied, …
    const rule = body.error?.blocked_by     // the rule that stopped it

    if (code === 'request_blocked') {
      throw new Error(`AI firewall blocked this request: rule "${rule}"`)
    }
  }
  throw err
}
codeMeaning
request_blockedA content guardrail matched. blocked_by names the rule; violations lists every rule that matched.
provider_deniedPolicy does not allow this provider.
model_denied / model_not_allowedPolicy does not allow this model.
provider_key_missingNo provider key in the vault for this provider.

In a chat route handler, decide deliberately what the user sees. A request_blocked is not a server fault and should not surface as a 500 — return the rule name, or a sanitised “your organisation’s policy does not allow this”, rather than a generic failure the user will simply retry.

Note Redaction is silent — nothing is thrown. A pii_redact rule rewrites the matched spans in your prompt to the literal [REDACTED] and forwards the request. Your stream starts normally and the model answers about redacted text.

Tool / function calling

Tools defined with tool() and multi-step maxSteps loops pass through untouched. Guardrails inspect each request the SDK sends, and in a multi-step loop every step is a fresh request that includes the previous tool results — so a tool that returns a secret is caught on the next step, before the provider sees it.

Gotchas

  • This is the SDK that most often ends up unfirewalled. It ignores OPENAI_BASE_URL entirely, so the only thing routing traffic through the gateway is createOpenAI({ baseURL }) in your code. Grep for @ai-sdk/openai imports before you assume you are covered.
  • apiKey is your Vulnetix key, not an OpenAI key.
  • Using @ai-sdk/anthropic instead? Its provider already appends /v1 internally, and the gateway’s Anthropic route is https://guardrails.vulnetix.com/anthropic/{orgUuid}. Whatever you configure, the request must land on …/{orgUuid}/v1/messages — check the URL before you trust it.
  • Edge runtimes are fine: the gateway is plain HTTPS, and streams relay as SSE.