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!,
  deploymentId: process.env.VAULTGRAPH_DEPLOYMENT_ID!,
  privateKey: process.env.VAULTGRAPH_PRIVATE_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
deploymentIdstringYesDeployment short ID (dep_...)
privateKeystringYesPEM Ed25519 private key
apiUrlstringNoAPI base URL
deriveJobIdfunctionNoCustom job ID logic
deriveMetadatafunctionNoCustom receipt metadata
deriveResolutionfunctionNoCustom resolution logic
fetchImplfunctionNoCustom fetch implementation
onReceiptSignedfunctionNoSigned receipt callback with exact hashed payload
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
  • The exact normalized payload and hash used for context_hash via onReceiptSigned
  • 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
If you need to persist the exact payload that VaultGraph hashed for later verification, use onReceiptSigned:
const middleware = vaultgraph({
  // ... config
  onReceiptSigned: ({ contextPayload, contextHash }) => {
    console.log("Persist alongside your logs", { contextPayload, contextHash });
  },
});

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!,
    deploymentId: process.env.VAULTGRAPH_DEPLOYMENT_ID!,
    privateKey: process.env.VAULTGRAPH_PRIVATE_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!,
  deploymentId: process.env.VAULTGRAPH_DEPLOYMENT_ID!,
  privateKey: process.env.VAULTGRAPH_PRIVATE_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),
});