The TypeScript SDK wraps your existing LLM client instances and automatically captures latency, token counts, costs, and errors for every call — with no changes to your application logic beyond initialization.
Installation
Install the package
All provider integrations are optional peer dependencies. Install only what you use:npm install openai # OpenAI
npm install @anthropic-ai/sdk # Anthropic
npm install @google/generative-ai # Google Generative AI
npm install @aws-sdk/client-bedrock-runtime # AWS Bedrock
npm install @mistralai/mistralai # Mistral
npm install groq-sdk # Groq
npm install @langchain/core # LangChain
Initialize Zespan
Call zespan.init() once at application startup, before any LLM calls are made.import { zespan } from "@zespan/sdk";
zespan.init({
apiKey: process.env.ZESPAN_API_KEY!,
environment: "production",
});
Wrap your LLM client
Pass your client instance through the appropriate wrapper function. The wrapper returns the same client — your existing code continues to work unchanged.import OpenAI from "openai";
import { zespan } from "@zespan/sdk";
const openai = zespan.wrapOpenAI(new OpenAI());
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello!" }],
});
Init options
zespan.init(options) accepts the following configuration:
Your Zespan API key. Must start with zsp_. Find this in your project settings.
environment
string
default:"production"
Environment label attached to every event. Use "staging" or "development" to separate traces by environment.
When true (default), prompt and completion text are stored alongside traces with PII redaction applied before transmission. Set to false to disable prompt storage entirely.
Fraction of events to send, between 0.0 and 1.0. Set to 0.1 to trace 10% of calls.
Keys whose values are redacted before any data is stored. Applied regardless of storePrompts.
Number of events to accumulate before flushing.
Milliseconds between automatic flushes. The SDK also flushes on process exit.
baseURL
string
default:"https://api.zespan.com"
Override the API base URL. Use this only for self-hosted deployments.
When true, also exports spans to an OpenTelemetry-compatible backend. Requires otelEndpoint.
When true, logs internal errors to the console. Enable during integration testing.
Provider wrappers
OpenAI
import OpenAI from "openai";
import { zespan } from "@zespan/sdk";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI());
// Non-streaming
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Summarize this article." }],
});
// Streaming — TTFT captured automatically
const stream = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Write a poem." }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
Anthropic
import Anthropic from "@anthropic-ai/sdk";
import { zespan } from "@zespan/sdk";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const anthropic = zespan.wrapAnthropic(new Anthropic());
const message = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Explain monads in plain English." }],
});
Google Generative AI
import { GoogleGenerativeAI } from "@google/generative-ai";
import { zespan } from "@zespan/sdk";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const google = zespan.wrapGoogle(new GoogleGenerativeAI(process.env.GOOGLE_API_KEY!));
const model = google.getGenerativeModel({ model: "gemini-2.5-flash" });
const result = await model.generateContent("What is quantum entanglement?");
AWS Bedrock
import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime";
import { wrapBedrock } from "@zespan/sdk";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const bedrock = wrapBedrock(new BedrockRuntimeClient({ region: "us-east-1" }));
const response = await bedrock.converse({
modelId: "amazon.nova-lite-v1:0",
messages: [{ role: "user", content: [{ text: "Summarize this document." }] }],
});
wrapBedrock patches client.converse. The converseStream method is not currently wrapped — streaming Bedrock calls are not traced.
Mistral
import { Mistral } from "@mistralai/mistralai";
import { wrapMistral } from "@zespan/sdk";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const mistral = wrapMistral(new Mistral({ apiKey: process.env.MISTRAL_API_KEY! }));
const response = await mistral.chat.complete({
model: "mistral-small-latest",
messages: [{ role: "user", content: "What is the capital of France?" }],
});
Groq
import Groq from "groq-sdk";
import { wrapGroq } from "@zespan/sdk";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const groq = wrapGroq(new Groq({ apiKey: process.env.GROQ_API_KEY! }));
const response = await groq.chat.completions.create({
model: "llama-3.3-70b-versatile",
messages: [{ role: "user", content: "Explain gradient descent." }],
});
OpenRouter
import OpenAI from "openai";
import { zespan } from "@zespan/sdk";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openrouter = zespan.wrapOpenRouter(
new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY!,
})
);
const response = await openrouter.chat.completions.create({
model: "anthropic/claude-sonnet-4-6",
messages: [{ role: "user", content: "Hello from OpenRouter!" }],
});
LiteLLM
import { wrapLiteLLM } from "@zespan/sdk";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const litellm = wrapLiteLLM({
baseURL: "http://localhost:4000",
apiKey: process.env.LITELLM_API_KEY,
});
const response = await litellm.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello from LiteLLM!" }],
});
Context enrichment
Use withLumiqtraceContext to attach a userId, sessionId, or custom tags to all traces generated within a function scope.
import { withLumiqtraceContext, zespan } from "@zespan/sdk";
import OpenAI from "openai";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI());
export async function POST(req: Request) {
const { userId, sessionId } = await getSession(req);
return withLumiqtraceContext(
{ userId, sessionId, tags: { feature: "chat", plan: "pro" } },
async () => {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: await req.text() }],
});
return Response.json(response);
}
);
}
Set userId and sessionId on every request that involves a logged-in user. This enables per-user cost breakdown and session replay in the Zespan dashboard.
Agent tracing
Use withAgent to trace a multi-step agent workflow. It creates an agent span and exposes methods to log plans, trace tool calls, and record handoffs.
import { withAgent, zespan } from "@zespan/sdk";
import OpenAI from "openai";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI());
await withAgent(
{
name: "CustomerSupportAgent",
role: "specialist",
framework: "custom",
tools: [{ name: "lookup_order", description: "Lookup order by id" }],
},
async (agent) => {
agent.logPlan(["Lookup order", "Check refund policy", "Draft response"]);
const order = await agent.traceTool(
"lookup_order",
{ orderId: "123" },
async () => ({ id: "123", status: "delivered", total: 49.99 })
);
agent.delegateTo("RefundPolicyAgent", "refund requested");
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: `Order: ${JSON.stringify(order)}` }],
});
return response.choices[0].message.content;
}
);
AgentContext methods:
agent.logPlan(steps: string[]) — records a planning span
agent.traceTool(name, args, fn) — wraps an async function, records args and result as a tool span
agent.delegateTo(targetName, reason) — records a handoff span
Manual spans
Use startSpan to instrument any function as a custom span and attach evaluation scores.
import { startSpan, zespan } from "@zespan/sdk";
import OpenAI from "openai";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI());
const span = startSpan({ name: "rag-pipeline" });
try {
const docs = await retrieveDocuments("user query");
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: `Use these docs: ${docs}` },
{ role: "user", content: "user query" },
],
});
span.setEvalScore("relevance", 0.92);
return response.choices[0].message.content;
} finally {
span.end();
}
Prompt management
The PromptClient fetches versioned prompts from the Zespan prompt library at runtime. Results are cached locally.
import { PromptClient, zespan } from "@zespan/sdk";
import OpenAI from "openai";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI());
const prompts = new PromptClient();
const prompt = await prompts.get("support-reply", { label: "production" });
const text = prompts.compile(prompt, {
customer_name: "Alex",
order_id: "ORD-7821",
});
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: text },
{ role: "user", content: "I need help with my order." },
],
});
See Prompt management for the full API reference — get, list, create, updateLabels, compile, clearCache.
PII redaction
Zespan automatically redacts values from tags and metadata fields before they leave your application. The key is preserved; the value is replaced with "[REDACTED]".
Default redacted keys (always applied): password, secret, token, api_key, apikey, auth, authorization, access_token, refresh_token, private_key, credential, ssn, credit_card, card_number, cvv.
Add custom keys at initialization — they are merged with the defaults:
zespan.init({
apiKey: process.env.ZESPAN_API_KEY!,
redactKeys: ["email", "phone", "address", "ip_address", "dob"],
});
To replace the defaults entirely with your own list:
zespan.init({
apiKey: process.env.ZESPAN_API_KEY!,
redactKeys: ["ssn", "account_number"],
replaceDefaultRedactKeys: true,
});
Replacing the defaults removes protection for common sensitive field names like password and token. Only do this if your custom list covers all sensitive fields your application may produce.
Redaction applies to tags and metadata fields, and to stored prompt and completion text. Prompt storage is on by default — set storePrompts: false to disable it entirely.
Guardrails
Guardrails run content safety checks before sending a prompt to the LLM (pre-check) and before returning the completion (post-check). Pass guardrails: true to any wrapper to enable both phases.
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI(), { guardrails: true });
For fine-grained control:
const openai = zespan.wrapOpenAI(new OpenAI(), {
guardrails: {
pre: true, // Check prompt before sending to LLM
post: true, // Check completion before returning to app
failClosed: false, // If guardrail service errors, allow call through
},
});
Handle blocks with GuardrailBlockedError:
import { zespan, GuardrailBlockedError } from "@zespan/sdk";
import OpenAI from "openai";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI(), { guardrails: true });
async function generateResponse(userMessage: string): Promise<string> {
try {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: userMessage }],
});
return response.choices[0].message.content ?? "";
} catch (err) {
if (err instanceof GuardrailBlockedError) {
console.warn(`Guardrail blocked ${err.phase} content:`, err.results);
return "I'm sorry, I can't help with that request.";
}
throw err;
}
}
GuardrailBlockedError properties:
phase — "pre" (input blocked) or "post" (output blocked)
results — array of per-policy results with passed, action, reason, modifiedText
Guardrails work on all provider wrappers: wrapAnthropic, wrapGoogle, wrapOpenRouter, and LumiqtraceCallbackHandler.
Configure guardrail policies in the Zespan dashboard under Settings → Guardrails. Policies are evaluated server-side — update them without redeploying your application.
Config propagation
Zespan can push configuration changes — model overrides, sample rate, guardrail toggles — to your running application without a redeployment. Changes made via ZespanPilot or the dashboard take effect within the next flush cycle (default 2 seconds).
What can be propagated:
| Rule type | Effect |
|---|
model_override | Redirect calls for a given model to a different model |
sample_rate | Increase or decrease the fraction of events traced |
guardrail_enable | Enable or disable guardrails on a wrapper |
pii_redact_keys | Add keys to the redaction list |
log_level | Toggle debug logging |
disable_tracing | Stop all tracing immediately |
Force an immediate config refresh:
import { zespan } from "@zespan/sdk";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
// Force refresh — useful during startup
await zespan.getClient().refreshConfig();
To disable remote config propagation entirely:
zespan.init({
apiKey: process.env.ZESPAN_API_KEY!,
disableConfigSync: true,
});
With disableConfigSync: true, the SDK ignores all remote config changes. All behavior is determined solely by the options passed to init().
Flushing in serverless environments
In short-lived processes such as AWS Lambda, Vercel Functions, or Cloudflare Workers, call zespan.flush() explicitly before the handler returns to guarantee delivery.
import { zespan } from "@zespan/sdk";
import OpenAI from "openai";
zespan.init({ apiKey: process.env.ZESPAN_API_KEY! });
const openai = zespan.wrapOpenAI(new OpenAI());
export async function handler(event: any) {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: event.prompt }],
});
const result = response.choices[0].message.content;
await zespan.flush();
return { statusCode: 200, body: result };
}
Omitting zespan.flush() in serverless environments is the most common cause of missing traces. Always call it before your handler returns.