npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tailrace/ai-sdk

v0.2.2

Published

Tailrace middleware for the Vercel AI SDK: model wrapper + tool wrapper.

Readme

Tailrace - Agent data governance for TypeScript. Docs · All packages · @tailrace/core

@tailrace/ai-sdk

Policy enforcement for the Vercel AI SDK (ai@^5). Wrap a language model so prompts and completions pass through Tailrace before they reach a provider or your application. Wrap tools so arguments and return values are governed at the tool boundary.

Tailrace runs entirely in-process. There is no proxy, no sidecar, and no network call on the request hot path.

Install

Add Tailrace and the AI SDK to your project:

pnpm add @tailrace/core @tailrace/ai-sdk ai
pnpm add @ai-sdk/openai   # or your provider package

ai@^5 is a peer dependency of @tailrace/ai-sdk. Install it in the same package that imports the middleware.

Create a Tailrace instance

Tailrace works with zero configuration. The default policy blocks secret-class entities and tokenizes common structured PII (email, phone, credit card, and others).

import { createTailrace } from "@tailrace/core";
import { withAiSdk } from "@tailrace/ai-sdk";

const tailrace = withAiSdk(createTailrace());

Use withAiSdk to attach fluent helpers (tailrace.model, tailrace.tools). Core stays free of AI SDK types. Prefer standalone functions when you want explicit imports:

import { wrapModel, wrapTools } from "@tailrace/ai-sdk";

Wrap a model

Pass the wrapped model anywhere you would pass the original - generateText, streamText, agents, and other AI SDK APIs.

import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";

const model = tailrace.model(openai("gpt-4o"), {
  agent: "support-bot",
  workflowId: "session-abc",
});

const { text } = await generateText({
  model,
  prompt: "Summarize the ticket.",
});

What gets scanned

| Hook | When it runs | Boundary | | ----------------- | -------------------------------- | ----------------------------- | | transformParams | Before the provider call | { kind: "model", provider } | | wrapGenerate | After a non-streaming completion | Same model boundary | | wrapStream | On each streaming chunk | Same model boundary |

Tailrace scans text parts in system, user, assistant, and tool-result messages. Image, file, and other non-text parts pass through unchanged in v0.1.

The provider string is encoded as ${providerId}/${modelId} (for example openai/gpt-4o). Gateway-style model IDs that already contain / are used as-is. This encoding drives policy keys such as openai/*.

Wrap tools

import { tool } from "ai";
import { z } from "zod";

const tools = tailrace.tools(
  {
    crm: tool({
      description: "Look up a customer",
      inputSchema: z.object({ email: z.string() }),
      execute: async ({ email }) => fetchCustomer(email),
    }),
  },
  { agent: "support-bot", workflowId: "session-abc" },
);

Each tool with an execute function is wrapped:

  • Arguments are checked at { kind: "tool", name, direction: "out" }.
  • Return values are checked at { kind: "tool", name, direction: "in" }.

When policy blocks a tool call, the wrapper throws an error string the model can read and self-correct against:

Blocked by data policy: api_key may not be sent to tool:fetch:out (rule: entities.api_key)

Tools without execute are returned unchanged.

Restore at egress

Tokenization is reversible, but detokenization only happens at trusted egress boundaries. The model wrapper tokenizes PII in prompts; your route handler restores values before sending a response to the user:

const result = await generateText({ model, prompt });

const restored = await tailrace.restore(result.text, {
  boundary: { kind: "egress", sink: "ui" },
  identity: { agent: "support-bot" },
  workflowId: "session-abc",
});

return Response.json({ text: restored.output });

Calling restore at a model or tool boundary throws InvariantViolationError regardless of policy.

Streaming output

For streamText, configure how a policy block on model output is translated:

const model = tailrace.model(openai("gpt-4o"), {
  streamBlockBehavior: "abort", // default
});

| Mode | Behavior | Fail-closed | | --------------------- | -------------------------------------------------------------------------------------- | ----------- | | abort (default) | Hold-back scan with a carry buffer; cancel the stream and throw PolicyViolationError | Yes | | buffer | Accumulate the full response, check once at end, throw on block | Yes | | redact | Hold-back scan; replace blocked spans with [ENTITY] labels and continue | No: opt-in |

Non-streaming generateText always throws on block. There is no streaming mode that emits text before scanning (partial secret leakage).

For very long single-line secrets (large JWTs, PEM keys) that exceed the 128-character carry window, use streamBlockBehavior: "buffer".

Options

interface AiSdkWrapOptions {
  agent?: string;
  workflowId?: string | (() => string);
  streamBlockBehavior?: "abort" | "buffer" | "redact";
  onDecision?: (decisions: Decision[]) => void;
}

| Option | Default | Description | | --------------------- | ----------- | ------------------------------------------------------------------------------------ | | agent | "default" | Identity for policy resolution (identities overrides in your policy document). | | workflowId | "default" | Scopes vault tokens. Same value across steps yields the same token for the same PII. | | streamBlockBehavior | "abort" | How streaming output translates policy block. | | onDecision | - | Called with audit decisions after each check. Never includes raw values. |

Error handling

| Surface | On block | | ---------------------------------------- | ------------------------------------------------------ | | Model input (transformParams) | Throws PolicyViolationError - provider is not called | | Model output (wrapGenerate) | Throws PolicyViolationError | | Model output stream (abort / buffer) | Throws PolicyViolationError | | Model output stream (redact) | Stream continues; blocked spans become [ENTITY] | | Tool execute | Throws Error with the formatted policy message |

Catch PolicyViolationError from @tailrace/core in route handlers. The error message names the entity class and rule path, never the detected value.

Example app

The monorepo includes a runnable Next.js demo:

pnpm --filter example-nextjs-ai-sdk dev
pnpm --filter example-nextjs-ai-sdk demo:3

See examples/nextjs-ai-sdk.

Further reading