TypeScript SDK
Typed client for the OGuardAI API with automatic session management, error handling, and full TypeScript type exports
The @oguardai/sdk package provides a typed client for the OGuardAI API with automatic session management.
Install
npm install @oguardai/sdk
# or
pnpm add @oguardai/sdkBasic Usage
Transform and Rehydrate
import { OGuardAIClient } from "@oguardai/sdk";
const client = new OGuardAIClient({
baseUrl: "http://localhost:3000",
apiKey: "your-api-key", // optional
});
// Transform: replace PII with semantic tokens
const result = await client.transform({
input: "Contact Julia Schneider at julia@example.com",
policy: "default",
});
console.log(result.safe_text);
// "Contact `{{person:p_001:083e771529f3}}` at `{{email:e_001:7b1068662ab5}}`"
// Send safe_text to your LLM
const llmResponse = await callYourLLM(result.safe_text, result.entity_context);
// Rehydrate: restore real values in LLM output
const restored = await client.rehydrate({
output: llmResponse,
session_state: result.session_state,
output_channel: "customer_email",
restore_mode: "formatted",
});
console.log(restored.restored_text);
// "Dear Frau Julia Schneider, ..."Detect Only
const detection = await client.detect({
input: "Email: test@example.com, SSN: 123-45-6789",
threshold: 0.5,
});
for (const entity of detection.entities) {
console.log(`${entity.type}: ${entity.value} (confidence: ${entity.confidence})`);
}Under a production auth mode entity.value is an empty string by default: raw values are returned only when you pass include_values: true AND your API key has the detect_values scope. Types, spans, and confidence are always present. This keeps raw PII out of a detect response unless a caller is explicitly authorized to receive it.
Health and Capabilities
const health = await client.health();
console.log(health.status); // "healthy"
const caps = await client.capabilities();
console.log(caps.entity_types.map(e => e.name));
// ["person", "email", "phone", "iban", "ssn", ...]Session Management
For multi-turn conversations, use OGuardAISession to automatically carry session state between calls:
import { OGuardAIClient, OGuardAISession } from "@oguardai/sdk";
const client = new OGuardAIClient({ baseUrl: "http://localhost:3000" });
const session = new OGuardAISession(client, "german-support");
// Turn 1
const turn1 = await session.transform("Ich bin Anna Mueller, Kundennummer 948221");
console.log(turn1.safe_text);
// "Ich bin `{{person:p_001:4c19d2a08b5e}}`, Kundennummer `{{customer_id:cid_001:9f27c31d64ab}}`"
const llmReply1 = "Guten Tag `{{person:p_001:4c19d2a08b5e}}`, wie kann ich helfen?";
const restored1 = await session.rehydrate(llmReply1, "customer_email");
// Turn 2: same session, entities are deduplicated
const turn2 = await session.transform("Meine E-Mail ist anna@example.com");
// Anna Mueller is still `{{person:p_001:4c19d2a08b5e}}`withSession Helper
import { OGuardAIClient, withSession } from "@oguardai/sdk";
const client = new OGuardAIClient({ baseUrl: "http://localhost:3000" });
await withSession(client, "default", async (session) => {
const result = await session.transform("Hello, I am John Smith");
// ... call LLM ...
const restored = await session.rehydrate(llmOutput, "user_output");
});Chat Messages
guardedChat tokenizes a full chat array in one call. The server scans every message field by field, tokenizing each string-bearing field (content, multimodal text parts, tool-call arguments) and returns safeMessages with the same shape. Token ids are shared across messages and one sealed session covers the whole conversation. Rehydrate the model's reply with rehydrateResponse:
const { safeMessages, sessionState } = await client.guardedChat(
[
{ role: "system", content: "You are a support agent." },
{ role: "user", content: "I am Julia Schneider, julia@example.com" },
],
{ policy: "german-support" },
);
const reply = await callYourLLM(safeMessages); // sees tokens only
const restored = await client.rehydrateResponse(reply, sessionState, "customer_email");guardedCall wraps any string -> string step (a LangChain chain, a raw provider call) so it transforms the input, runs your function on the tokenized text, and rehydrates the output. It fails closed if transform returns no session state:
const answer = await client.guardedCall(
(safeText) => callYourLLM(safeText),
"Draft a reply to Julia Schneider",
{ policy: "default", outputChannel: "customer_email" },
);Batch
Transform or detect many inputs in one call. Each item takes an optional per-item policy and language, so a single batch can mix languages. session_mode is independent (default) or shared.
const batch = await client.batchTransform({
items: [
{ text: "Ich bin Anna Mueller", language: "de" },
{ text: "Contact john@example.com", language: "en" },
],
session_mode: "independent",
});
for (const item of batch.results) {
console.log(item.index, item.safe_text, item.error);
}
const detected = await client.batchDetect({
items: [{ text: "SSN 123-45-6789", policy: "strict-pii" }],
});RAG
The RAG methods carry a corpus_id for cross-document and cross-session token identity: passing the same corpus_id on ingest, query, and context makes the same raw value tokenize to the same token id deterministically across documents and sessions, which is what lets a query entity align with an ingested document entity. The scope is that one corpus, not a global fingerprint.
// 1. Ingest: chunk + tokenize a document, store safe chunks in your vector DB
const ingest = await client.ragIngest({
text: documentText,
chunking_strategy: "paragraph",
corpus_id: "support-kb",
});
// Keep each chunk's chunk_session_state for step 3
const docSessions = ingest.chunks.map((c) => c.chunk_session_state);
// 2. Query: tokenize the user question for vector search
const query = await client.ragQuery({ query: userQuestion, corpus_id: "support-kb" });
const hits = await vectorSearch(query.safe_query);
// 3. Context: merge retrieved chunks into a safe LLM context
const ctx = await client.ragContext({
chunks: hits.map((h) => h.text),
session_state: query.session_state,
document_sessions: docSessions,
corpus_id: "support-kb",
});
const llmAnswer = await callYourLLM(ctx.safe_chunks.join("\n"));
// 4. Answer: validate tokens resolve; restore host-side
const answer = await client.ragAnswer({
answer: llmAnswer,
session_state: ctx.session_state,
output_channel: "user_output",
});
// GDPR erasure: forget every value in an ingest session
await client.ragDelete({ session_state: ingest.chunks[0].chunk_session_state });Files and Images
transformFile ingests a document (PDF, DOCX, TXT), extracts text, and tokenizes detected PII. transformImage runs OCR (Tesseract in the server image) and returns tokenized text plus bounding_boxes for client-side overlay. redactImage returns the redacted image bytes and deliberately over-redacts (it applies no policy) for visual safety; it accepts an optional language hint that is forwarded to OCR and detection.
const doc = await client.transformFile(pdfBytes, "contract.pdf", { policy: "legal-privilege" });
console.log(doc.safe_text);
const img = await client.transformImage(pngBytes, "scan.png", { language: "de" });
console.log(img.safe_text, img.bounding_boxes);
const redacted = await client.redactImage(pngBytes, "scan.png", { language: "de" }); // Uint8ArrayThe image route resolves the tenant/global default policy, so it accepts only language, not a policy field. Person and company names in a scan depend on the NER sidecar; without it those names are not detected and are not redacted, so treat image redaction as a data-minimization control, not an absolute guarantee.
Streaming
transformStream and rehydrateStream yield chunks over SSE. Transform runs the full pipeline, then streams safe_text; rehydrate streams restored_text:
for await (const chunk of client.transformStream("Contact Julia Schneider", { policy: "default" })) {
process.stdout.write(chunk);
}
for await (const chunk of client.rehydrateStream(llmOutput, sessionState, { outputChannel: "user_output" })) {
process.stdout.write(chunk);
}
// Typed stream events carry sessionState on the terminal "complete" event
let capturedState: string | undefined;
for await (const event of client.transformStreamEvents("Contact Julia Schneider", { policy: "default" })) {
if (event.text) process.stdout.write(event.text);
if (event.sessionState) capturedState = event.sessionState;
}
// rehydrateStreamEvents() yields the same StreamEvent shapeRevocation and Admin
// Revoke a value so future rehydrates return [DELETED]
await client.revoke("email", "julia@example.com");
await client.bulkRevoke([{ entityType: "email", value: "a@b.com" }]);
// Preview policy decisions without transforming
const decision = await client.evaluatePolicy({
entities: [{ type: "email", value: "julia@example.com" }],
policy: "default",
});
// Admin/ops (no PII); scoped server-side
const diag = await client.diagnostics();
const prom = await client.metrics();
const check = await client.validatePolicies({ directory: "/etc/oguardai/policies" });
const count = await client.revocationCount(); // active revocation count
const reloaded = await client.reloadPolicies(); // hot-reload the policy directory (fail-closed)
// Session lifecycle: identify a session by exactly one of session_id or session_state.
// The sealed backend holds no server-side state, so deleteSession reports deleted: false.
const status = await client.sessionStatus({ session_state: result.session_state });
await client.deleteSession({ session_state: result.session_state });Error Handling
The SDK throws typed errors for different failure modes:
import {
OGuardAIClient,
OGuardAIError,
ValidationError,
AuthError,
SessionExpiredError,
PolicyDeniedError,
TimeoutError,
OutputBlockedError,
TokenRepairError,
RateLimitError,
} from "@oguardai/sdk";
const client = new OGuardAIClient({ baseUrl: "http://localhost:3000" });
try {
await client.transform({ input: "some text" });
} catch (error) {
if (error instanceof ValidationError) {
// 400: malformed request
console.error("Invalid request:", error.message);
} else if (error instanceof AuthError) {
// 401/403: authentication failed
console.error("Auth error:", error.message);
} else if (error instanceof SessionExpiredError) {
// 410: session TTL elapsed
console.error("Session expired, create a new one");
} else if (error instanceof PolicyDeniedError) {
// 403: only when a redacted entity's rule sets on_redact: reject.
// Routine redaction succeeds with the value redacted in safe_text.
console.error("Policy rejected the request:", error.message);
} else if (error instanceof OutputBlockedError) {
// 422: the output guard blocked newly generated PII in the model output
console.error("Output blocked:", error.message);
} else if (error instanceof TokenRepairError) {
// 422: a malformed {{type:id:cap}} token could not be repaired for restoration
console.error("Token repair failed:", error.message);
} else if (error instanceof RateLimitError) {
// 429: rate limit exceeded; error.retryAfter carries the Retry-After header
console.error("Rate limited, retry after:", error.retryAfter);
} else if (error instanceof TimeoutError) {
// Request timed out
console.error("Request timed out");
} else if (error instanceof OGuardAIError) {
// Other API error
console.error(`Error [${error.code}]: ${error.message}`);
}
}Configuration
const client = new OGuardAIClient({
baseUrl: "http://localhost:3000", // Required
apiKey: "your-api-key", // Optional: sent as X-API-Key header
timeout: 30000, // Optional: request timeout in ms (default: 30000)
});Types
All request/response types are exported:
import type {
TransformRequest,
TransformResponse,
RehydrateRequest,
RehydrateResponse,
DetectRequest,
DetectResponse,
HealthResponse,
CapabilitiesResponse,
EntityContext,
EntityInfo,
RestoreMode,
OutputChannel,
BatchTransformRequest,
BatchTransformResponse,
BatchDetectRequest,
BatchDetectResponse,
RagIngestRequest,
RagIngestResponse,
RagQueryRequest,
RagQueryResponse,
RagContextRequest,
RagContextResponse,
RagAnswerRequest,
RagAnswerResponse,
RagDeleteRequest,
RagDeleteResponse,
FileTransformResponse,
ImageTransformResponse,
BoundingBox,
RevokeResponse,
EvaluatePolicyRequest,
EvaluatePolicyResponse,
DiagnosticsResponse,
ValidatePoliciesRequest,
ValidatePoliciesResponse,
} from "@oguardai/sdk";