local-pii

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,
})
ContractProven behavior
Protected semantic valuesMessage text, system text, tool arguments, and tool results are protected before the client sees them.
Restored semantic valuesResponse text, streamed deltas, tool arguments delivered to your tool, and tool results are restored at their protocol boundary.
Preserved control / opaque valuesRoles, IDs, model/options, tool names, request metadata, and opaque provider fields are preserved; fields containing personal information remain caller responsibility.
Unsupported variants / errorsUnsupported 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 })
ContractProven behavior
Protected semantic valuesSystem/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 valuesText 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 valuesPart types, IDs, roles, reasoning, tool names, approval state, provider options, and stream control markers remain unchanged; caller-owned metadata is not inspected.
Unsupported variants / errorsUnsupported 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 })
ContractProven behavior
Protected semantic valuesText messages, supported structured content, tool inputs/results, and each stream boundary are protected without mutating caller messages.
Restored semantic valuesText deltas, structured values, tool arguments, and tool results are restored at the TanStack boundary.
Preserved control / opaque valuesRoles, message/run/thread IDs, event types, finish/error markers, and opaque data are preserved. Hydration is passed through.
Unsupported variants / errorsUnsupported 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.

On this page