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

agentid-vercel-sdk

v0.1.35

Published

AgentID wrapper for Vercel AI SDK guardrails, masking, workflow telemetry, and audit logging.

Readme

agentid-vercel-sdk

agentid-vercel-sdk is the official AgentID wrapper for the Vercel AI SDK.

It wraps an existing model with:

  • pre-flight AgentID /guard
  • optional prompt rewriting from guard verdicts
  • block-before-provider billing on denied requests
  • post-flight /ingest and /ingest/finalize telemetry

The client callsite still uses normal generateText() and streamText().

Default behavior stays backend-first:

  • AgentID /guard is the primary decision point
  • local deterministic checks only run when clientFastFail is explicitly enabled
  • in strictMode / fail_close, local deterministic checks are also used as the last fallback if the backend guard is temporarily unreachable

Install

Pick the provider package you actually use.

npm install ai agentid-vercel-sdk @ai-sdk/openai

or

npm install ai agentid-vercel-sdk @ai-sdk/anthropic

agentid-vercel-sdk depends on agentid-sdk@^0.1.38.

Local beta install from this monorepo

Before the packages are published publicly, install both local packages explicitly:

npm install ../../agentid-sdk ../../packages/vercel-sdk ai @ai-sdk/openai

There is also a runnable Next.js reference app in examples/vercel-ai-sdk-next.

Local tarball beta install

If you want to simulate a real consumer install before publish, pack both packages and install the tarballs:

cd agentid-sdk
npm pack

cd ../packages/vercel-sdk
npm pack

npm install ../../agentid-sdk/agentid-sdk-<version>.tgz ./agentid-vercel-sdk-<version>.tgz ai @ai-sdk/openai

Inside this monorepo you can run the automated consumer install smoke with:

npm run qa:vercel-sdk-install-smoke

Required env

AGENTID_SYSTEM_ID=00000000-0000-0000-0000-000000000000
AGENTID_API_KEY=sk_live_...
OPENAI_API_KEY=sk-proj-...

Swap OPENAI_API_KEY for your provider key when using Anthropic or another Vercel AI SDK provider.

generateText() example

import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { AgentIdSecurityError, withAgentId } from "agentid-vercel-sdk";

const secureModel = withAgentId(openai("gpt-4o"), {
  systemId: process.env.AGENTID_SYSTEM_ID!,
  apiKey: process.env.AGENTID_API_KEY!,
});

try {
  const result = await generateText({
    model: secureModel,
    prompt: "Write a short refund confirmation.",
  });

  console.log(result.text);
} catch (error) {
  if (error instanceof AgentIdSecurityError) {
    console.error("Blocked by AgentID:", error.reason);
    return;
  }

  throw error;
}

streamText() example

import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
import { AgentIdSecurityError, withAgentId } from "agentid-vercel-sdk";

const secureModel = withAgentId(openai("gpt-4o"), {
  systemId: process.env.AGENTID_SYSTEM_ID!,
  apiKey: process.env.AGENTID_API_KEY!,
});

try {
  const result = streamText({
    model: secureModel,
    prompt: "Stream a short answer.",
  });

  for await (const chunk of result.textStream) {
    process.stdout.write(chunk);
  }
} catch (error) {
  if (error instanceof AgentIdSecurityError) {
    console.error("Blocked by AgentID:", error.reason);
    return;
  }

  throw error;
}

Streaming telemetry is observed on a forked ReadableStream branch so the user-visible stream is not blocked by AgentID logging.

The wrapper is Edge-safe for normal Vercel AI SDK server usage. It does not require fs or native Node-only runtime APIs in the wrapper path.

Critical: masking and blocking before the LLM

AgentID only protects the LLM call that is actually routed through the AgentID wrapper. Logging a masked copy to AgentID is not enough.

For pre-LLM masking and blocking, the application must:

  1. create the provider model
  2. wrap that model with withAgentId(...)
  3. pass the wrapped model to generateText() or streamText()
  4. send the complete prompt/message history through that wrapped call
  5. render the returned wrapped result/stream, not a separate raw provider response

withAgentId(...) protects text parts across the full Vercel AI SDK prompt history before provider dispatch when SDK-side masking is enabled. It also marks telemetry with full_history_protected=true, messages_count, and protected_messages_count so the dashboard can distinguish a full wrapper integration from audit-only logging.

Correct:

const secureModel = withAgentId(openai("gpt-4o"), {
  systemId: process.env.AGENTID_SYSTEM_ID!,
  apiKey: process.env.AGENTID_API_KEY!,
});

const result = await generateText({
  model: secureModel,
  messages: conversationMessages,
});

Incorrect:

// This only creates a masked audit trail. It does not protect the LLM call below.
await agent.log({ input: maskedPrompt, output: maskedOutput, system_id: systemId });

// Raw prompt/history still goes directly to the provider.
const result = await generateText({
  model: openai("gpt-4o"),
  messages: rawConversationMessages,
});

For chat and agent applications, do not protect only the latest text box value. The provider usually receives the full message history. If any previous user, assistant, tool, retrieval, memory, or system-supplied message contains raw PII, the model can still see it and repeat it later.

In other words, this must be protected as one LLM input:

await streamText({
  model: secureModel,
  messages: [
    { role: "system", content: systemPrompt },
    ...conversationHistory,
    { role: "user", content: latestUserMessage },
  ],
});

Do not call an unwrapped provider model anywhere else in the same request path. Do not send raw chat history to the provider and then send only a masked copy to AgentID for logging. That pattern will make the dashboard look masked after refresh, but the LLM will already have seen the raw data.

Blocking and masking are separate controls:

  • masking enabled: sensitive values are rewritten before provider dispatch
  • blocking enabled: denied prompts throw AgentIdSecurityError before provider dispatch
  • both enabled: the guard can block high-risk prompts; allowed prompts still use protected text

Recommended chat UX

For privacy-first chat apps, show the protected user transcript immediately when local masking applies. Do not wait for a dashboard/log refresh to replace raw values in the UI.

Recommended pattern:

  • compute a local preview with PIIManager.anonymize(...) before adding the optimistic user bubble
  • render the protected user bubble, for example Moje jmeno je <PERSON_1>
  • call your server route with the user input/history and ensure the server route uses withAgentId(...)
  • render the wrapped generateText() result or wrapped streamText() stream
  • do not render a raw provider stream in parallel with the wrapped stream

Example client-side optimistic bubble:

import { PIIManager } from "agentid-sdk";

const pii = new PIIManager();
const preview = pii.anonymize(input, { pii: true, secrets: true }).maskedText;

setMessages((messages) => [
  ...messages,
  { role: "user", content: preview, piiProtected: preview !== input },
]);

If your product intentionally wants users to see their own raw text locally, make that an explicit product/security decision. It does not change the server rule: the provider call must still use the wrapped model and protected history.

Manual history protection helper

If you cannot use withAgentId(...) for a custom route yet, protect the complete message array before calling the provider. Do not protect only the latest input.

import { protectMessageHistory } from "agentid-vercel-sdk";

const protectedHistory = protectMessageHistory(body.messages, {
  pii: true,
  secrets: true,
});

const result = streamText({
  model: getCustomModel(),
  messages: protectedHistory.messages,
});

For manual integrations, include these telemetry fields when you call agent.log(...) or /ingest:

metadata: {
  full_history_protected: true,
  messages_count: body.messages.length,
  protected_messages_count: body.messages.length,
  transformed_prompt_text_parts_count: protectedHistory.transformedTextPartsCount,
}

Without that signal, AgentID treats the row as manual or legacy integration and the Activity detail can show an integration-risk warning.

Guidance for AI coding agents

When an AI coding agent integrates AgentID into an app, use this checklist:

  • Find the exact server-side function that calls the LLM provider.
  • Replace the model passed to generateText() / streamText() with a withAgentId(...) wrapped model.
  • If the app uses OpenAI directly, replace the client with agent.wrapOpenAI(...) and call the wrapped client.
  • Protect the full messages array, not just prompt or the latest user input.
  • Remove or refactor any parallel direct provider call in the same request.
  • Ensure the UI consumes the wrapped response or wrapped stream.
  • Add a regression test where the first message contains a name and the second asks the model to repeat it. The answer must not contain the raw name.

Minimum regression expectation:

User: Moje jmeno je Jan Kroupa
Assistant: ... <PERSON_1> ...
User: jake jmeno jsem poslal v minule zprave?
Assistant must not contain: Jan, Kroupa

If the model can answer Jan Kroupa, the raw history reached the LLM.

What the wrapper does

  1. calls AgentID /guard before the provider request
  2. throws AgentIdSecurityError when the guard blocks the request
  3. forwards allowed traffic to the wrapped provider model
  4. writes completion telemetry through AgentID /ingest
  5. finalizes streaming telemetry through AgentID /ingest/finalize

Fail-open / fail-close handling, capability fetch, retries, and finalize semantics are delegated to agentid-sdk, so the wrapper stays aligned with the current AgentID runtime contract.

Practical behavior:

  • default: backend-first, fail-open unless the effective system policy resolves to fail-close
  • clientFastFail: optional local preflight before /guard
  • strictMode or failureMode: "fail_close": fail-close oriented behavior with local deterministic fallback if backend guard is temporarily unreachable

Per-request metadata

You can override request context per call via providerOptions.agentid.

const result = await generateText({
  model: secureModel,
  prompt: "Summarize this customer ticket.",
  providerOptions: {
    agentid: {
      userId: "customer-123",
      requestIdentity: {
        tenantId: "acme",
        sessionId: "sess-42",
      },
      expectedLanguages: ["en"],
      clientEventId: "11111111-1111-4111-8111-111111111111",
    },
  },
});

Supported request-level overrides:

  • userId
  • requestIdentity
  • expectedLanguages
  • clientEventId
  • telemetry

Workflow and tool-step telemetry

withAgentId() logs the guarded LLM call. For the rest of an agent run, use the re-exported AgentID operation logger with the same workflowRunId.

import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import {
  AgentID,
  createAgentIdCorrelationId,
  createAgentIdTelemetryContext,
  withAgentId,
} from "agentid-vercel-sdk";

const systemId = process.env.AGENTID_SYSTEM_ID!;
const apiKey = process.env.AGENTID_API_KEY!;
const workflowRunId = createAgentIdCorrelationId();

const agent = new AgentID({ apiKey });
const secureModel = withAgentId(openai("gpt-4o"), {
  systemId,
  apiKey,
  telemetry: createAgentIdTelemetryContext({
    workflowRunId,
    workflowName: "Invoice follow-up",
  }),
});

await agent.logOperation({
  system_id: systemId,
  telemetry: createAgentIdTelemetryContext({
    workflowRunId,
    workflowStepName: "load_invoice",
    toolName: "finance.invoice_lookup",
    toolTargetType: "invoice",
  }),
  event_category: "tool",
  event_status: "completed",
});

await generateText({
  model: secureModel,
  prompt: "Draft a short payment reminder.",
  providerOptions: {
    agentid: {
      telemetry: createAgentIdTelemetryContext({
        workflowRunId,
        workflowStepName: "draft_email",
        toolName: "email.draft",
        toolTargetType: "email",
        eventCategory: "ai",
        eventSubtype: "payment_reminder_draft_generated",
      }),
    },
  },
});

await agent.logOperation({
  system_id: systemId,
  telemetry: createAgentIdTelemetryContext({
    workflowRunId,
    workflowStepName: "send_email",
    toolName: "email.send",
    toolTargetType: "email",
  }),
  event_category: "delivery",
  event_status: "completed",
});

Each event remains its own audit row. The dashboard groups rows that share workflow_run_id, so tool calls, delivery events, inbox events, guard checks, and LLM calls appear as one workflow timeline without treating non-LLM work as model spend. Do not reuse clientEventId for the whole workflow; keep workflow correlation in workflowRunId and let each event keep its own idempotency key.

Dashboard behavior:

  • the guarded prompt/LLM step remains inspectable as its own prompt/guard Activity row
  • the workflow summary row opens the step-by-step timeline for the run
  • non-LLM tool, delivery, inbox, and workflow lifecycle rows show Model: Not applicable
  • only spend-bearing model rows should contribute to model cost totals

Token, cost, and ROI dashboards require the spend-bearing model row to carry provider usage. The wrapper forwards Vercel AI SDK usage metadata when the provider exposes it. If a custom path logs completion manually, include usage.prompt_tokens, usage.completion_tokens, usage.total_tokens, the real model id, and latency. Without those fields, Activity can look correct while Total Spend, token charts, and ROI stay empty.

For tool execute() wrappers and multi-step agent runs, prefer the workflow trail helper:

import {
  AgentID,
  createAgentIdCorrelationId,
  createAgentIdTelemetryContext,
  createAgentIdWorkflowTrail,
  withAgentId,
} from "agentid-vercel-sdk";

const workflowRunId = createAgentIdCorrelationId();
const agent = new AgentID({ apiKey: process.env.AGENTID_API_KEY! });

const trail = createAgentIdWorkflowTrail({
  agent,
  system_id: process.env.AGENTID_SYSTEM_ID!,
  telemetry: createAgentIdTelemetryContext({
    workflowRunId,
    workflowName: "Invoice follow-up",
  }),
});

const searchInvoice = async () =>
  trail.runStep(
    {
      telemetry: createAgentIdTelemetryContext({
        workflowStepName: "lookup_invoice",
        toolName: "finance.invoice_lookup",
        toolTargetType: "invoice",
        eventCategory: "tool",
      }),
    },
    async () => lookupInvoice(),
    {
      complete: {
        metadata: { result_count: 1 },
      },
    }
  );

Current V1 limits

  • The wrapper expects a text user prompt.
  • File-only prompt payloads currently raise AgentIdPromptExtractionError.
  • If you use non-wrapped provider surfaces, call guard() and logOperation() / log() explicitly instead of assuming automatic telemetry.
  • The wrapper protects calls made through the wrapped model only. It cannot protect unrelated provider calls, custom fetches, browser-only LLM calls, or another deployed app that has not been updated to use the wrapper.

Masking and scanner coverage

Masking behavior is inherited from agentid-sdk and the AgentID runtime config. When SDK-side masking is enabled, protected traffic is rewritten before provider dispatch, before user-visible wrapper output, and before ingest.

Current scanner regression coverage includes:

  • multiline PEM, certificate, and PGP private key blocks
  • password disclosures such as my Password is Passwordk123
  • environment assignments such as DB_PASSWORD=...
  • suffix-safe secret values such as passwords ending in #
  • base64-like secret values with = / == padding
  • security-question answers after answer is, is, or localized equivalents

Provider coverage in this repo

The package has provider-level integration coverage for:

  • @ai-sdk/openai non-stream
  • @ai-sdk/openai stream
  • @ai-sdk/anthropic non-stream
  • @ai-sdk/anthropic stream

That is intentional. The wrapper should stay provider-agnostic at the Vercel AI SDK layer, so it is validated against more than one concrete provider contract.

Publishing Notes (NPM)

NPM renders this README.md from the package root during npm publish.

Before publishing from the monorepo, run:

npm run audit:all
npm run qa:production-gate

The production gate includes the package-local audit for packages/vercel-sdk, provider integration tests, typecheck, build, and a consumer install smoke path through the root qa:vercel-sdk-install-smoke script.