Skip to content

Vercel AI SDK Integration ​

Package: @agentpolicyspecification/ai-sdk

Wrap any Vercel AI SDK language model with APS policy enforcement. The wrapper is a transparent LanguageModelV3 — it can be used anywhere a model is accepted, including Mastra and other frameworks built on the AI SDK.

Installation ​

bash
npm install @agentpolicyspecification/ai-sdk @agentpolicyspecification/core

How it works ​

withAps wraps doGenerate and doStream on the model:

User prompt
    │
    ▼
evaluateInput()     ← Input interception
    │
    ▼
model.doGenerate()  ← Original model call
    │
    ├─ tool calls → evaluateToolCall()  ← Tool call interception
    │
    ▼
evaluateOutput()    ← Output interception
    │
    ▼
Response

A denied decision throws PolicyDenialError and the call is aborted at that point.

Basic usage ​

typescript
import { generateText } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
import { withAps } from "@agentpolicyspecification/ai-sdk";
import { ApsEngine } from "@agentpolicyspecification/core";

const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });

const engine = new ApsEngine({
  policySet: {
    input: [new NoSsnPolicy()],
  },
});

const model = withAps(openai("gpt-4o-mini"), {
  engine,
  metadata: { agent_id: "my-agent", session_id: "session-123" },
});

const { text } = await generateText({ model, prompt: "What is the capital of France?" });

The metadata object is forwarded to every policy context and audit record.

Tool call interception ​

When the model returns tool calls, each one is evaluated via engine.evaluateToolCall() before the result is returned to the caller. This works for both doGenerate and doStream.

typescript
import { ApsEngine } from "@agentpolicyspecification/core";
import type { ToolCallPolicy, ToolCallContext, PolicyDecision } from "@agentpolicyspecification/core";

const BLOCKED_TOOLS = new Set(["delete_file", "execute_shell"]);

class NoBlockedToolsPolicy implements ToolCallPolicy {
  readonly id = "no-blocked-tools";

  evaluate(ctx: ToolCallContext): PolicyDecision {
    return BLOCKED_TOOLS.has(ctx.tool_name)
      ? { decision: "deny", reason: `Tool "${ctx.tool_name}" is not permitted.` }
      : { decision: "allow" };
  }
}

const engine = new ApsEngine({
  policySet: {
    input:     [new NoSsnPolicy()],
    tool_call: [new NoBlockedToolsPolicy()],
    output:    [new NoConfidentialOutputPolicy()],
  },
});

Streaming ​

withAps transparently wraps the stream. Input is evaluated before the stream starts; tool calls are evaluated as they arrive in the stream; output is evaluated when the stream ends.

typescript
import { streamText } from "ai";

const result = streamText({
  model: withAps(openai("gpt-4o-mini"), { engine }),
  prompt: "Explain quantum entanglement.",
});

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

With an audit handler ​

Combine with @agentpolicyspecification/langfuse or @agentpolicyspecification/otel by passing onAudit to the engine:

typescript
import { createLangfuseAuditHandler } from "@agentpolicyspecification/langfuse";

const engine = new ApsEngine({
  policySet: { ... },
  onAudit: createLangfuseAuditHandler({
    publicKey: process.env.LANGFUSE_PUBLIC_KEY,
    secretKey: process.env.LANGFUSE_SECRET_KEY,
  }),
});

const model = withAps(openai("gpt-4o-mini"), {
  engine,
  metadata: { agent_id: "my-agent", session_id: "session-abc" },
});

API reference ​

withAps(model, options) ​

Returns a LanguageModelV3 that wraps the provided model with APS enforcement.

ParameterTypeDescription
modelLanguageModelV3Any Vercel AI SDK language model
options.engineApsEngineThe APS engine to evaluate policies with
options.metadataPartial<Metadata>Optional metadata forwarded to all policy contexts

Returns: LanguageModelV3

Released under the Apache 2.0 License.