11. Vercel AI SDK Wrapper
Purpose
agentid-vercel-sdk is the official AgentID wrapper for the Vercel AI SDK.
Use it when the application already relies on:
generateText()streamText()@ai-sdk/openai@ai-sdk/anthropic
The goal is to keep the client callsite unchanged while still enforcing AgentID guardrails and telemetry.
import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { withAgentId } from "agentid-vercel-sdk";
const secureModel = withAgentId(openai("gpt-4o"), {
systemId: process.env.AGENTID_SYSTEM_ID!,
apiKey: process.env.AGENTID_API_KEY!,
});
const result = await generateText({
model: secureModel,
prompt: "Write a short refund confirmation.",
});
The important integration rule is that secureModel must be the model passed to
the real generateText() or streamText() call. Sending raw messages to
openai("gpt-4o") and logging a masked copy to AgentID afterwards only creates
a masked audit trail. It does not protect the LLM.
For chat and agent apps, route the complete messages array through the
wrapped model, including previous user messages, assistant messages, tool
results, retrieval context, and the latest user message. Protecting only the
latest input field is not enough because the provider receives the full context.
Protect the full history still means one model invocation, not many guard
calls. Build the exact messages array for the provider call, protect that
array once, run one pre-flight decision for that provider call, then send the
protected history to the provider.
Current V1 scope note: full-history protection here refers to the provider
payload after local masking/protection. The wrapper's pre-flight guard request
is currently derived from the last user message text plus supported inline
attachments on that last user turn, not the exact full multi-turn history.
If you need pre-flight evaluation over the assembled history today, use an
explicit guard -> protected provider call -> ingest flow for that route.
When SDK-side masking is enabled, withAgentId(...) protects text parts across
the full Vercel AI SDK prompt history before provider dispatch. Telemetry is
marked with full_history_protected=true, messages_count, and
protected_messages_count so operators can verify that the integration is not
just logging a masked copy.
Runtime Flow
The wrapper keeps the same backend-first contract as agentid-sdk:
- extract the user prompt from the Vercel AI SDK request
- call AgentID
/guard - block early when guard denies
- optionally apply
transformed_inputbefore provider execution - call the wrapped provider model
- persist
/ingesttelemetry - finalize SDK timing with
/ingest/finalize
This means denied prompts are blocked before the provider is billed.
Enterprise Rollout Gate
The biggest rollout risk is not usually a weak prompt-injection classifier. It
is a provider path that never uses withAgentId(...).
Do not call a Vercel AI SDK deployment enterprise-ready until all of these are true:
- every server-side
generateText()/streamText()call that reaches a model uses awithAgentId(...)wrapped model - no route performs a parallel raw provider call outside the wrapped path
- the wrapped call carries the exact full message history that reaches the provider
- manual fallbacks use
guard -> protected provider call -> ingest, notraw provider call -> masked log - Activity confirms wrapper coverage with
full_history_protected=trueor equivalent manual metadata
Token, Cost, and ROI Telemetry
The wrapper forwards Vercel AI SDK usage metadata to AgentID completion telemetry when the provider exposes usage. This is what powers:
- token usage KPIs
cost_usd- Total Spend
- cost charts
- projected savings and ROI graphs
Do not strip, replace, or ignore the wrapped result before it is observed by the wrapper. For streams, make sure the stream is consumed or closed normally so the final telemetry branch can complete.
If cost or ROI is empty after integration, verify the Activity detail row has:
event_type: completeModelset to the actual provider model, notNot applicable- non-zero
input_tokensandoutput_tokens cost_usdpopulated for priced models- system business context configured in Settings (
human_hourly_rateandhuman_time_per_task_min) for ROI
Manual History Protection Helper
Prefer withAgentId(...). If a custom route cannot use the wrapper yet, protect
the complete messages array before calling the provider:
- Build the exact
messagesarray for this provider call. - Run
protectMessageHistory(...)once on that full array. - If you are doing manual guard telemetry, issue one
guard()call for that provider call. - Send
protectedHistory.messagesto the provider.
Do not iterate over old messages and call guard() once per message. That
creates multiple guard events for one LLM request and can look like duplicate or
stale conversation logging.
import { protectMessageHistory } from "agentid-vercel-sdk";
const protectedHistory = protectMessageHistory(body.messages, {
pii: true,
secrets: true,
});
const result = streamText({
model: getCustomModel(),
messages: protectedHistory.messages,
});
Manual integrations should include full_history_protected=true,
messages_count, protected_messages_count, and
transformed_prompt_text_parts_count in ingest metadata. Otherwise the
dashboard treats the row as an unverified manual integration.
Failure Behavior
Default behavior is backend-first and fail-open unless the effective system policy says otherwise.
/guardis the primary authorityclientFastFailis optional and disabled by defaultstrictModeorfailureMode: "fail_close"activates fail-close behavior- fail-open dependency fallback keeps local deterministic PII and secret masking
enabled in
agentid-sdk@0.1.40+ - if backend guard is temporarily unreachable and the effective mode is fail-close, the wrapper can apply the same local deterministic fallback contract as
agentid-sdk
This keeps the wrapper aligned with agentid-sdk instead of inventing a separate security model.
Edge Runtime Compatibility
The wrapper is designed for Vercel AI SDK and Edge-safe server runtimes.
- no
fs - no native Node crypto dependency in the wrapper path
- streaming telemetry is observed on a forked
ReadableStreambranch so the response stream stays non-blocking for the caller
Provider Coverage
The package is provider-agnostic at the Vercel AI SDK layer, but this repo validates concrete provider behavior with integration tests for:
@ai-sdk/openainon-stream@ai-sdk/openaistream@ai-sdk/anthropicnon-stream@ai-sdk/anthropicstream
Per-request Overrides
Request-scoped identity can be passed through 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:
userIdrequestIdentityexpectedLanguagesclientEventIdtelemetry
Workflow Timeline Behavior
When telemetry.workflowRunId is provided, the dashboard groups matching rows
into a workflow timeline. The guarded prompt still remains visible as a
standalone prompt/guard Activity row for forensic inspection.
Recommended model:
- use
workflowRunIdto group the whole run - use
workflowStepName/workflowStepIdto identify the current step - use event-specific
clientEventIdonly for a single guarded provider call or operation event - log surrounding tool/delivery/inbox events with
AgentID.logOperation()or the workflow trail helper
This preserves prompt-level auditability while still showing the operational sequence around the LLM call.
Masking and Secret Coverage
Masking follows the base agentid-sdk runtime configuration. With SDK-side
masking enabled, prompt text can be rewritten before /guard, provider
dispatch, and /ingest. Output text from the wrapped result or wrapped stream
is also protected before it is returned to the app caller and before telemetry
is stored.
This only applies to calls made through withAgentId(...). A raw provider call
in the same route can still leak PII even if the dashboard later shows a masked
AgentID row.
Regression coverage includes:
- multiline PEM, certificate, and PGP private key blocks
- natural-language password disclosures such as
my Password is Passwordk123 - environment-style assignments such as
DB_PASSWORD=... - suffix-safe secret values such as values ending in
# - base64-like secret values with
=/==padding - security-question answers after
answer is,is, or localized equivalents
Operational Notes
- The wrapper expects a text user prompt. File-only prompt payloads are not a supported V1 surface and currently raise
AgentIdPromptExtractionError. - Pre-flight guard extraction is centered on the last user message text plus supported inline attachments on that last user turn, not the exact full provider prompt history.
- The wrapper delegates capability fetch, retry behavior, guard correlation, and finalize semantics to
agentid-sdk. - If you are not using Vercel AI SDK, prefer the Node.js / TypeScript SDK (
agentid-sdk) or call/guard+/ingestexplicitly.
Package QA
Before publishing the npm package from this monorepo, run:
npm run audit:all
npm run qa:production-gate
The gate includes the package-local audit, provider integration tests, typecheck, build, and the root Next production build.