Attesta integrates with the Vercel AI SDK (ai package) through two mechanisms:
gatedVercelTool — wraps a single tool definition with Attesta approval before execute runs.
createAttestaMiddleware — returns middleware that intercepts all tool calls in generateText / streamText pipelines.
Both are TypeScript-only and ship with @kyberon/attesta.
Installation
npm install @kyberon/attesta ai
Wraps a Vercel AI SDK tool() definition. The wrapped tool runs the full Attesta evaluation pipeline before delegating to the original execute function.
API
gatedVercelTool < T extends VercelAIToolLike > (
name: string,
tool: T ,
options ?: GatedVercelToolOptions
): T
Parameter Type Description namestringAction name for the ActionContext toolTA Vercel AI SDK tool definition optionsGatedVercelToolOptionsConfiguration options (see below)
GatedVercelToolOptions:
Option Type Default Description agentIdstring— Agent ID attached to every action context sessionIdstring— Session ID for tracking environmentstring"development"Environment name metadataRecord<string, unknown>{}Extra metadata riskHintsRecord<string, unknown>— Risk hints for the scorer riskOverridestring— Force a specific risk level onDenied(result) => unknownthrows AttestaDenied Custom denial handler
Full Example
import { tool, generateText } from "ai" ;
import { z } from "zod" ;
import { openai } from "@ai-sdk/openai" ;
import { gatedVercelTool } from "@kyberon/attesta/integrations" ;
// Define a tool
const deleteFileTool = tool ({
description: "Delete a file from the filesystem" ,
parameters: z. object ({
path: z. string (). describe ( "Absolute file path to delete" ),
}),
execute : async ({ path }) => {
// This only runs if Attesta approves
await fs. unlink (path);
return `Deleted ${ path }` ;
},
});
// Wrap with Attesta
const safeDeleteTool = gatedVercelTool ( "deleteFile" , deleteFileTool, {
agentId: "file-manager" ,
environment: "production" ,
riskHints: { destructive: true },
});
// Use in generateText
const result = await generateText ({
model: openai ( "gpt-4o" ),
tools: { deleteFile: safeDeleteTool },
prompt: "Delete the old log file at /var/log/app.log" ,
});
Behavior on Denial
By default, gatedVercelTool throws an AttestaDenied error when a tool call is denied:
AttestaDenied : Action "deleteFile" denied by attesta. Risk: critical ( 0.92 ).
You can customize this with the onDenied callback:
const safeDeleteTool = gatedVercelTool ( "deleteFile" , deleteFileTool, {
onDenied : ( result ) => {
return {
error: `Denied (risk: ${ result . riskAssessment . level })` ,
suggestion: "Try a less destructive approach" ,
};
},
});
gatedVercelTool creates a shallow copy of the original tool with the wrapped execute function. The original tool object is not mutated. If the tool has no execute function, the original tool is returned unchanged.
Middleware: createAttestaMiddleware
Returns an object with experimental_onToolCall that can be spread into generateText or streamText options. This intercepts all tool calls in the pipeline, not just individually wrapped tools.
API
createAttestaMiddleware (options ?: AttestaMiddlewareOptions): {
experimental_onToolCall : ( params ) => Promise <void> ;
}
AttestaMiddlewareOptions:
Option Type Default Description agentIdstring— Agent ID sessionIdstring— Session ID environmentstring"development"Environment name metadataRecord<string, unknown>{}Extra metadata (includes vercelToolName automatically) riskHintsRecord<string, unknown>— Risk hints getActionName(toolName, args) => stringidentity Custom function to derive the action name from the tool name
Full Example
import { generateText, tool } from "ai" ;
import { z } from "zod" ;
import { openai } from "@ai-sdk/openai" ;
import { createAttestaMiddleware } from "@kyberon/attesta/integrations" ;
// Create middleware
const attesta = createAttestaMiddleware ({
agentId: "data-pipeline" ,
environment: "production" ,
riskHints: { pii: true },
});
// Define tools (not individually wrapped)
const tools = {
queryDatabase: tool ({
description: "Run a SQL query" ,
parameters: z. object ({ sql: z. string () }),
execute : async ({ sql }) => {
return await db. query (sql);
},
}),
exportData: tool ({
description: "Export data to CSV" ,
parameters: z. object ({ table: z. string (), path: z. string () }),
execute : async ({ table , path }) => {
return await exportToCsv (table, path);
},
}),
};
// Use middleware -- it intercepts ALL tool calls
const result = await generateText ({
model: openai ( "gpt-4o" ),
tools,
prompt: "Export the users table to /tmp/users.csv" ,
... attesta, // Spreads experimental_onToolCall
});
Behavior on Denial
The middleware throws AttestaDenied when a tool call is denied. Unlike gatedVercelTool, there is no onDenied callback — the error propagates to the caller:
try {
const result = await generateText ({
model: openai ( "gpt-4o" ),
tools,
prompt: "DROP TABLE users" ,
... attesta,
});
} catch (error) {
if (error instanceof AttestaDenied ) {
console. log ( "Tool call denied:" , error.message);
// "Tool call "queryDatabase" denied by attesta. Risk: critical (0.95)."
}
}
Streaming with streamText
The middleware works with streamText as well:
import { streamText } from "ai" ;
import { createAttestaMiddleware } from "@kyberon/attesta/integrations" ;
const attesta = createAttestaMiddleware ({
agentId: "chat-agent" ,
environment: "production" ,
});
const result = await streamText ({
model: openai ( "gpt-4o" ),
tools,
prompt: "Analyze and clean the dataset" ,
... attesta,
});
for await ( const chunk of result.textStream) {
process.stdout. write (chunk);
}
When using streamText, the experimental_onToolCall hook fires before the tool executes. If the tool call is denied, the AttestaDenied error interrupts the stream. Make sure your error handling accounts for stream interruption.
Custom Action Names
Use getActionName to derive meaningful action names from tool calls:
const attesta = createAttestaMiddleware ({
getActionName : ( toolName , args ) => {
// Prefix with the module for better audit trails
if (toolName. startsWith ( "db_" )) return `database.${ toolName }` ;
if (toolName. startsWith ( "fs_" )) return `filesystem.${ toolName }` ;
return toolName;
},
});
This is useful when tool names are generic (e.g., query, execute) but the risk depends on the context.
Feature gatedVercelToolcreateAttestaMiddlewareScope Single tool All tools in the pipeline Denial handling onDenied callback or AttestaDeniedAttestaDenied onlyPer-tool config Yes (different options per tool) No (same options for all tools) Tool mutation Creates shallow copy No mutation Best for Fine-grained per-tool policies Blanket approval across all tools
Do not combine gatedVercelTool and createAttestaMiddleware on the same tool. This would evaluate the tool call twice — once by the wrapper and once by the middleware. Use one or the other.
LangChain (TS) TypeScript LangChain integration
Integrations Overview Compare all framework integrations