Skip to main content

TypeScript SDK

Drop-in observability for OpenAI, Anthropic, Vercel AI, LangChain, and Mastra on Node.js, Bun, and Deno.

  • Package@eden-ai/sdk on npm
  • Default ingest APIhttps://api.edenobservability.com
  • Model — fire-and-forget; tracing must never block the caller

New here? Start with the Quickstart (Fill in creates your key).

Install

npm install @eden-ai/sdk
# or: pnpm add / bun add / yarn add @eden-ai/sdk

Framework SDKs (openai, @anthropic-ai/sdk, ai, @langchain/core, @mastra/core) are optional peer dependencies — install only what you use.

Quick start

import OpenAI from "openai";
import { configure, wrapOpenAI, getDefaultClient } from "@eden-ai/sdk";

configure({ orgId: "org_abc", apiKey: "sk_eden_..." });

const openai = wrapOpenAI(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));

const reply = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Hello!" }],
});

await getDefaultClient().flush();

The TypeScript SDK wraps client instances (Proxy-based), not global module exports. This survives ESM/CJS interop and bundler tree-shaking.

Configuration

Environment variables are read first; configure(...) overrides them.

Env varDefaultPurpose
EDEN_ORG_IDRequired for production. EDEN_DEFAULT_ORG accepted as alias.
EDEN_ORG_API_KEYAPI key (X-Org-Api-Key). EDEN_API_KEY accepted as alias.
EDEN_BASE_URLhttps://api.edenobservability.comIngestion / API base URL
EDEN_GATEWAY_URL{baseUrl}/gateway/v1OpenAI-compatible gateway override
EDEN_PROJECT_IDdefaultSent as X-Project-Id
EDEN_REALMproductionRealm tag on events
EDEN_DEBUG01 logs every batch flush to console.debug
EDEN_EXACT_CACHEonGateway optimization opt-out
EDEN_COALESCEonGateway optimization opt-out
EDEN_PROVIDER_CACHE_LAYOUTonGateway optimization opt-out
EDEN_RESPONSE_REPAIRonGateway optimization opt-out

configure() also accepts maxBatchSize (default 50), flushIntervalMs (default 250), maxQueueSize (default 4096), timeoutS (default 5), redactOnClient (default true), disabled, strict, and filters.

import { configure } from "@eden-ai/sdk";

configure({
orgId: "org_abc",
apiKey: "sk_eden_...",
baseUrl: "https://api.edenobservability.com",
tags: ["prod", "checkout-agent"],
debug: false,
strict: false, // true → throw when orgId missing
redactOnClient: true,
});

When orgId is unset, events are dropped silently (dev-friendly). Set strict: true to fail fast instead.

Instrumentation

Per-framework wrappers

import { wrapOpenAI, wrapAnthropic, wrapVercelAI, wrapLangChain, wrapMastra } from "@eden-ai/sdk";

OpenAI

import OpenAI from "openai";
import { wrapOpenAI } from "@eden-ai/sdk";

const openai = wrapOpenAI(new OpenAI(), { tags: ["checkout"] });

await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Hello!" }],
});

// Streaming — emits llm_completion_stream + per-chunk /ingest/chunks rows
const stream = await openai.chat.completions.create({
model: "gpt-4o-mini",
stream: true,
messages: [{ role: "user", content: "Stream a haiku." }],
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}

Anthropic

import Anthropic from "@anthropic-ai/sdk";
import { wrapAnthropic } from "@eden-ai/sdk";

const anthropic = wrapAnthropic(new Anthropic(), { tags: ["checkout"] });

await anthropic.messages.create({
model: "claude-3-5-sonnet-20241022",
max_tokens: 256,
messages: [{ role: "user", content: "Hello!" }],
});

Vercel AI

Pass the imported ai namespace — not a model factory:

import * as ai from "ai";
import { openai } from "@ai-sdk/openai";
import { wrapVercelAI } from "@eden-ai/sdk";

const wrapped = wrapVercelAI(ai);

await wrapped.generateText({
model: openai("gpt-4o-mini"),
prompt: "Write a haiku.",
});

const stream = await wrapped.streamText({
model: openai("gpt-4o-mini"),
prompt: "Stream a haiku.",
});
for await (const chunk of stream.textStream) process.stdout.write(chunk);

LangChain

Wraps any chat-model instance — instruments invoke, call, stream, and batch:

import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";
import { wrapLangChain } from "@eden-ai/sdk";

const chat = wrapLangChain(new ChatOpenAI({ modelName: "gpt-4o-mini" }), { tags: ["checkout"] });

await chat.invoke([new HumanMessage("Hello!")]);

Mastra

import { wrapMastra } from "@eden-ai/sdk";

const mastra = wrapMastra(mastraInstance, { tags: ["prod"] });
// Proxies getAgent / getWorkflow — emits agent.run events with source_format: mastra

instrument() convenience helper

Returns all wrap* functions plus detection status. Does not auto-wrap clients — you still call the wrappers yourself:

import { configure, instrument } from "@eden-ai/sdk";
import OpenAI from "openai";

configure({ orgId: "org_abc", apiKey: "sk_eden_..." });

const { wrapOpenAI, detected } = instrument({ tags: ["prod"] });
// detected: { openai: true, anthropic: false, vercelAi: false, langchain: false, mastra: false }

if (detected.openai) {
const openai = wrapOpenAI(new OpenAI());
}

Detection helpers

import { autoInstrument, detectInstalledFrameworks } from "@eden-ai/sdk";

const status = autoInstrument({ tags: ["prod"] });
// { openai: true, anthropic: true, vercelAi: true, langchain: false, mastra: false }

autoInstrument() is an alias of detectInstalledFrameworks() — it reports which packages are importable but does not wrap them.

Manual tracing

trace() context manager

import { trace, getDefaultClient } from "@eden-ai/sdk";

const result = await trace("myAgent", async (ctx) => {
ctx.setTag("prod");
ctx.setInput({ question: "hi" });
const answer = await doWork();
ctx.setOutput({ answer });
return answer;
});

await getDefaultClient().flush();

Import TraceController for imperative span building with setInput / setOutput / setTag / setStatus / recordError / end.

@traceAgent() decorator

import { traceAgent } from "@eden-ai/sdk";

class MyAgent {
@traceAgent({ name: "my-agent", tags: ["prod"], environmentId: "env_abc" })
async run(query: string) {
return await fetch("/api/echo", { method: "POST", body: query });
}
}

Flush and shutdown

import { getDefaultClient } from "@eden-ai/sdk";

const client = getDefaultClient();
await client.flush(); // drain event queue
await client.flushChunks(); // drain streaming chunk buffer
await client.dispose(); // flush both + stop timers — call on graceful shutdown

Register dispose() from a beforeExit handler or your server's shutdown hook.

Batching and backpressure

  • Events flush at maxBatchSize (default 50) or flushIntervalMs (default 250 ms), whichever comes first.
  • Stream chunks share the same timer and POST separately to /ingest/chunks.
  • When the queue exceeds maxQueueSize (default 4096), oldest events are dropped silently.
  • Network failures are swallowed at debug level unless strict: true.

Gateway (OpenAI-compatible)

Production gateway: https://api.edenobservability.com/gateway/v1. The SDK resolves resolvedGatewayUrl() as {baseUrl}/gateway/v1 unless gatewayUrl / EDEN_GATEWAY_URL is set.

import {
EdenSDK,
FilterConfig,
PIIConfig,
openaiClient,
} from "@eden-ai/sdk";

const sdk = new EdenSDK({
orgId: "org_abc",
apiKey: "sk_eden_...",
gatewayUrl: "https://api.edenobservability.com/gateway/v1",
filters: new FilterConfig({
pii: new PIIConfig({ mode: "fail_closed", disabledCategories: ["api_key"] }),
}),
});

const client = await openaiClient(sdk, {
upstreamBaseUrl: "https://my-internal-llm.example.com/v1",
upstreamApiKey: "<your-internal-key>",
});

await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "hello" }],
});

Raw headers for any HTTP client:

const headers = sdk.gatewayHeaders({
piiBypass: true,
filters: new FilterConfig({ pii: new PIIConfig({ mode: "off" }) }),
});
await fetch(`${sdk.resolvedGatewayUrl()}/chat/completions`, {
method: "POST",
headers,
body: JSON.stringify({ model: "gpt-4o-mini", messages: [...] }),
});

redactOnClient (default true) masks obvious PII in outgoing payloads before they leave the process.

Doctor

export EDEN_ORG_ID=org_...
export EDEN_ORG_API_KEY=sk_eden_...

npx eden-doctor
# or, from a project with @eden-ai/sdk installed:
node node_modules/@eden-ai/sdk/dist/doctor.js

Programmatic:

import { runDoctor } from "@eden-ai/sdk";

const report = await runDoctor();

Doctor checks orgId, apiKey, baseUrl, gatewayUrl, optimization flags, /health reachability, and emits a test event when configured.

Strict mode and disabled

configure({ strict: true, orgId: "org_abc", apiKey: "sk_eden_..." });
ModeHowBehaviour
Disabledconfigure({ disabled: true })Complete no-op
Missing orgNo EDEN_ORG_IDEvents dropped silently (unless strict)
Strictconfigure({ strict: true })Throws when orgId missing
DebugEDEN_DEBUG=1Logs every flush to console.debug

Token extraction

import { extractUsage, normalizeModelName } from "@eden-ai/sdk";

extractUsage({ prompt_tokens: 100, completion_tokens: 50 });
// => { inputTokens: 100, outputTokens: 50, cacheReadTokens: null, ... }

normalizeModelName("gpt-4o-2024-08-06"); // => "gpt-4o"
normalizeModelName("claude-3-5-sonnet-20241022"); // => "claude-3-5-sonnet"

Tier 2 fallbacks

import { installHttpCapture, uninstallHttpCapture } from "@eden-ai/sdk/http-capture";
import { installLogging, uninstallLogging } from "@eden-ai/sdk";
import { configureOtelExporter } from "@eden-ai/sdk";

installHttpCapture(); // patch fetch / undici for known LLM URLs
installLogging(); // forward console events as event_type: log
configureOtelExporter({ orgId: "...", apiKey: "..." });

Common errors

SymptomLikely causeFix
No events in portalMissing orgId / apiKeySet env vars or configure(); run doctor
wrapOpenAI no telemetryWrapped the wrong objectWrap the OpenAI instance, not a module import
wrapVercelAI no-opPassed a model factoryPass import * as ai from "ai"
Events stop mid-runQueue overflowIncrease maxQueueSize or await flush()
401 / 403 on ingestWrong key or orgKey must match orgId in the ingest URL
Strict mode crashorgId unsetSet EDEN_ORG_ID or disable strict

Next