Skip to main content
The VaultGraph Vercel AI SDK integration provides middleware that automatically submits JobReceipts for wrapped generate and stream calls.

Installation

npm install @vaultgraph/sdk ai

Quick Start

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

const requestId = "req_123";

const middleware = vaultgraph({
  apiKey: process.env.VAULTGRAPH_API_KEY!,
  agentId: "your-agent-uuid",
  consumerId: "your-consumer-uuid",
  signingKey: process.env.VAULTGRAPH_SIGNING_KEY!,
  deriveJobId: ({ type }) => `${requestId}:${type}`,
  deriveMetadata: ({ type, error }) => ({
    source: "ai-sdk",
    runType: type,
    workflow: "customer-support",
    hadError: Boolean(error),
  }),
  deriveResolution: ({ type, error }) => {
    if (error) return "failed";
    return type === "stream" ? "partial" : "success";
  },
});

const model = wrapLanguageModel({
  model: openai("gpt-4o"),
  middleware,
});

const { text } = await generateText({
  model,
  prompt: "What is VaultGraph?",
});

Configuration

OptionTypeRequiredDescription
apiKeystringYesVendor API key
agentIdstringYesAgent UUID
consumerIdstringYesConsumer UUID
signingKeystringYesPEM Ed25519 private key
apiUrlstringNoAPI base URL
deriveJobIdfunctionNoCustom job ID logic
deriveMetadatafunctionNoCustom receipt metadata
deriveResolutionfunctionNoCustom resolution logic
fetchImplfunctionNoCustom fetch implementation
onErrorfunctionNoError callback

How It Works

The middleware wraps both generate and stream model calls:
  • Successful generate calls — submits a receipt with "success" resolution after generation returns
  • Failed calls — submits a receipt with "failed" resolution, then re-throws the error
  • stream calls — currently submit once the stream is created, not after the stream has been fully consumed
Each receipt includes:
  • A job ID for correlation across systems
  • Default metadata including framework source, run type, and execution hints
  • An Ed25519 signature
All derive hooks receive the middleware execution context:
{ type: "generate" | "stream", error?: Error }
By default, the integration generates a unique job ID prefixed with ai-. If you want to align receipts with your own internal runs, provide deriveJobId. If you want to attach structured context like workflow names, trace IDs, or tenant-specific references, provide deriveMetadata. Derived metadata is merged on top of the default integration metadata. By default, AI SDK receipts include:
  • source: "ai-sdk"
  • runType: "generate" | "stream"
  • submissionPhase to show whether the receipt was emitted on response return or stream start
  • hasError, plus errorName when a model call throws

Works with Streaming

The middleware works with both generateText and streamText:
import { streamText, wrapLanguageModel } from "ai";
import { vaultgraph } from "@vaultgraph/sdk/ai";
import { openai } from "@ai-sdk/openai";

const requestId = "req_123";

const model = wrapLanguageModel({
  model: openai("gpt-4o"),
  middleware: vaultgraph({
    apiKey: process.env.VAULTGRAPH_API_KEY!,
    agentId: "your-agent-uuid",
    consumerId: "your-consumer-uuid",
    signingKey: process.env.VAULTGRAPH_SIGNING_KEY!,
    deriveJobId: ({ type }) => `${requestId}:${type}`,
    deriveMetadata: ({ type, error }) => ({
      source: "ai-sdk",
      runType: type,
      hadError: Boolean(error),
    }),
    deriveResolution: ({ type }) => (type === "stream" ? "partial" : "success"),
  }),
});

const result = streamText({ model, prompt: "Hello" });

for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}
// Receipt was submitted when the stream was created
Today, wrapStream records stream start rather than confirmed stream completion. That means a streaming receipt tells you the call entered streaming mode, not that every chunk was consumed by the caller. If you want to reflect that nuance in your scoring, set deriveResolution for stream calls to something like "partial".

Custom Resolution Logic

const requestId = "req_123";

const middleware = vaultgraph({
  // ... config
  deriveJobId: ({ type }) => `${requestId}:${type}`,
  deriveMetadata: ({ type, error }) => ({
    source: "ai-sdk",
    runType: type,
    hadError: Boolean(error),
  }),
  deriveResolution: ({ type, error }) => {
    if (error) return "failed";
    if (type === "stream") return "partial";
    return "success";
  },
});
deriveJobId, deriveMetadata, and deriveResolution all receive the same context object. Use type to distinguish generate from stream, and error to branch explicitly on failed model calls.

Idempotency and Duplicate Suppression

The autogenerated ai-* job IDs are fine for demos, but they are not idempotent across retries. In production, create the middleware inside your request or job boundary and pass a stable vendor-controlled ID into deriveJobId.
const requestId = "req_123";

const middleware = vaultgraph({
  apiKey: process.env.VAULTGRAPH_API_KEY!,
  agentId: "your-agent-uuid",
  consumerId: "your-consumer-uuid",
  signingKey: process.env.VAULTGRAPH_SIGNING_KEY!,
  deriveJobId: ({ type }) => `${requestId}:${type}`,
});
Keep retry counters, worker IDs, or temporary execution details in deriveMetadata, not in job_id. Because the current middleware records stream when the stream is created, you should decide whether generate and stream for the same top-level task should share a logical job ID or intentionally use separate suffixes.

Error Handling

The middleware never interferes with your model calls. Submission errors are caught silently. Use onError to monitor:
const middleware = vaultgraph({
  // ... config
  onError: (err) => console.error("VaultGraph:", err.message),
});