Adapters
Protected and restored values across inline, OpenAI, AI SDK, and TanStack adapters.
Adapters implement the same protection flow: Detection → anonymizer → privacy session → Generation adapter or inline callback → restored result. They protect semantic user content while preserving protocol control fields.
Inline calls — local-pii/inline
Use inline for any in-process Generation model. It protects, forwards the same
AbortSignal, and restores in a finally-safe lifecycle:
import { runInlineText } from "local-pii/inline"
const answer = await runInlineText({
session: conversation,
input: "Email ana@acme.com",
signal,
call: (protectedContent, { signal }) =>
generationModel.generate(protectedContent, { signal }),
})runInlineJson deep-protects string leaves without mutating the input. Inline
resolution priority is session, then anonymizer, then the default
anonymizer. An adapter-created session is cleaned up; a supplied session is
borrowed and never cleared. Cleanup failures do not replace a primary model or
abort failure.
Generic inline callbacks and caller-supplied Detection models execute as
trusted caller code. local-pii protects their Generation boundary; it does
not sandbox them.
OpenAI / Grok — local-pii/openai
import OpenAI from "openai"
import { withPiiOpenAI } from "local-pii/openai"
const client = withPiiOpenAI(new OpenAI({ apiKey }))
const result = await client.chat.completions.create({
model: "gpt-4o-mini",
messages,
tools,
})| Contract | Proven behavior |
|---|---|
| Protected semantic values | Message text, system text, tool arguments, and tool results are protected before the client sees them. |
| Restored semantic values | Response text, streamed deltas, tool arguments delivered to your tool, and tool results are restored at their protocol boundary. |
| Preserved control / opaque values | Roles, IDs, model/options, tool names, request metadata, and opaque provider fields are preserved; fields containing personal information remain caller responsibility. |
| Unsupported variants / errors | Unsupported client shapes fail instead of silently falling back. Abort and provider errors retain their exact primary error; use token() when JSON or tools may mangle brackets. |
The wrapper is structural and has no openai dependency, so it also covers
Grok/xAI, Groq, Together, OpenRouter, and Ollama-compatible clients. An
implicit session is created with the wrapper and reused across its calls. Use
one wrapper per private conversation, or pass { session } to make ownership
explicit. createPiiChat, rehydrateToolArgs, and
createStreamingRehydrator are available for a manual loop.
Vercel AI SDK — local-pii/ai-sdk
import { openai } from "@ai-sdk/openai"
import { streamText } from "ai"
import { withPii } from "local-pii/ai-sdk"
const model = withPii(openai("gpt-5.2"), { session: conversation })
const result = streamText({ model, prompt })| Contract | Proven behavior |
|---|---|
| Protected semantic values | System/user/assistant text, tool-call input, tool-result output, and structured JSON are protected before the provider. Reasoning is opaque and remains caller responsibility. |
| Restored semantic values | Text stream parts, tool arguments before tool execution, and tool results at the next model step are restored. Reasoning parts pass through unchanged. |
| Preserved control / opaque values | Part types, IDs, roles, reasoning, tool names, approval state, provider options, and stream control markers remain unchanged; caller-owned metadata is not inspected. |
| Unsupported variants / errors | Unsupported semantic content or malformed streams fail explicitly. Aborts and provider failures preserve the exact error; an abort observed before the provider boundary prevents the provider call. |
withPii is middleware: no tool wrapper is required. Supplied sessions are
borrowed; without one the middleware owns a session scoped to that middleware
instance and reuses it across calls. Create a new wrapper for a new private
conversation. The browser playground pins ai@7.0.57,
@ai-sdk/react@4.0.61, and @browser-ai/core@3.0.0.
TanStack AI — local-pii/tanstack
import { createAnonymizer, token } from "local-pii"
import { piiConnection } from "local-pii/tanstack"
const session = createAnonymizer({ placeholders: token() }).createSession()
const connection = piiConnection(browserConnection, { session })
// useChat({ connection })| Contract | Proven behavior |
|---|---|
| Protected semantic values | Text messages, supported structured content, tool inputs/results, and each stream boundary are protected without mutating caller messages. |
| Restored semantic values | Text deltas, structured values, tool arguments, and tool results are restored at the TanStack boundary. |
| Preserved control / opaque values | Roles, message/run/thread IDs, event types, finish/error markers, and opaque data are preserved. Hydration is passed through. |
| Unsupported variants / errors | Unsupported semantic parts throw UnsupportedTanStackSemanticContentError; abort, cleanup, and overlapping-run failures preserve their primary error. |
The contract is pinned to @tanstack/ai@0.43.1,
@tanstack/ai-client@0.23.1, and @tanstack/ai-react@0.19.1. A caller must
provide one session per private conversation. Hydration and joinRun work only
with the same live session. Persistence, full-reload restoration, cross-tab
restoration, and joining with a new session are unsupported. Since 0.1.0,
UnsupportedTanStackSemanticContentError replaces guessing: migrate unsupported
semantic parts to text or a supported structured value.
Choose a placeholder strategy
Use readable sequential() for ordinary text, keyed hashed({ secret }) when
stable equality is required, and opaque token() for tools or machine-parsed
JSON. A private mapping is never a protocol field and must stay inside the
caller's trust boundary.