TypeScript and Node.js SDK
Meter provider calls and streams, select delivery adapters, and enforce active request rules in Node.js applications.
View source@traice/sdk records provider usage, calculates known-model cost, adds product attribution, and writes events through one or more adapters. It supports ES modules and CommonJS on Node.js 20.9 or newer.
Install
npm install @traice/sdk openaiProvider SDKs, LangChain, and OpenTelemetry are optional peer dependencies. Install only the integrations your application uses.
Configure cloud delivery
Configure the global meter once during application startup.
import { configure } from "@traice/sdk";
configure({
adapters: ["cloud"],
cloudApiKey: process.env.TRAICE_API_KEY,
cloudMaxQueueSize: 1_000,
cloudDurableQueuePath: "./.traice/outbox.ndjson",
});configure() merges values into the current process-wide configuration. Call resetConfig() first when you need a clean configuration, primarily in tests.
cloudDurableQueuePath is optional. Set it for long-running services that need
events to survive a process restart. The queue is bounded by
cloudMaxQueueSize; when full, the oldest event is removed so telemetry cannot
grow memory or disk without limit. Restrict access to the queue file because it
contains event metadata.
Meter an OpenAI call
meter() returns the provider response unchanged. It records provider, model, input and output tokens, provider-reported cache usage, calculated cost, latency, status, and supplied attribution.
import OpenAI from "openai";
import { meter } from "@traice/sdk";
const openai = new OpenAI();
const completion = await meter(
() =>
openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Summarize this ticket" }],
}),
{
feature: "support-summary",
tenantId: "customer_42",
userId: "user_123",
workflowId: "support",
},
);By default, adapter writes are fire-and-forget. Set awaitWrites: true on a call when delivery must finish before meter() resolves.
Each cloud event carries the SDK event UUID as a stable externalId. Delivery
retries are therefore idempotent and do not create a second usage row.
Meter an Anthropic call
Anthropic Messages responses are detected automatically, including cache read and cache creation tokens.
import Anthropic from "@anthropic-ai/sdk";
import { meter } from "@traice/sdk";
const anthropic = new Anthropic();
const message = await meter(
() =>
anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 500,
messages: [{ role: "user", content: "Draft a concise reply" }],
}),
{
feature: "draft-reply",
tenantId: "customer_42",
},
);Meter AI SDK and Vertex calls
Pass an explicit provider identifier when the response shape does not identify its provider. The SDK reads camelCase AI SDK usage from usage or totalUsage, including cache-read tokens.
import { meter } from "@traice/sdk";
const result = await meter(() => callVertexModel(), {
provider: "google-vertex",
feature: "answer-question",
tenantId: "customer_42",
});Set provider to the stable identifier your pricing configuration and trAIce workspace use. provider also acts as the provider hint for streaming calls.
Meter a stream
meterStream() returns the async iterable immediately and records usage after the stream completes. Consume the stream fully so its terminal usage information can be observed.
import { meterStream } from "@traice/sdk";
const stream = await meterStream(
() =>
openai.chat.completions.create({
model: "gpt-4o-mini",
messages,
stream: true,
stream_options: { include_usage: true },
}),
{
feature: "chat",
tenantId: "customer_42",
userId: "user_123",
},
);
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}Attribution options
| Option | Meaning |
|---|---|
provider | Explicit provider identifier or stream hint |
feature | Product feature or request path |
tenantId | Paying customer or account |
userId | End user |
agentId | Agent identity |
workflowId | Workflow identity |
runId | One workflow or agent execution |
stepId | Step within an execution |
toolName | Tool used by an agent |
retryCount | Retry attempt number |
outcome | Product or workflow result |
sessionId | Application session |
env | Environment label |
metadata | Arbitrary structured context |
tags | Legacy string key/value metadata |
promptName, promptVersion | Versioned prompt identifiers |
prompt, output | Optional sample content when explicitly approved |
Pass tenantId on customer-facing calls to support customer-level AI contribution margin.
Choose adapters
| Adapter | Behavior | Typical use |
|---|---|---|
cloud | Batch product events to trAIce | Production attribution |
console | Print events to stdout | Local inspection |
local | Append newline-delimited JSON to disk | Local analysis and CLI reports |
webhook | POST events to your endpoint | Custom telemetry pipelines |
otel | Emit OpenTelemetry metrics | Existing observability stacks |
Use adapter names for default construction or pass adapter instances for explicit configuration.
import { CloudAdapter, LocalAdapter, configure } from "@traice/sdk";
const cloud = new CloudAdapter({
apiKey: process.env.TRAICE_API_KEY!,
batchSize: 100,
flushIntervalMs: 2_000,
durableQueuePath: "./.traice/outbox.ndjson",
requestTimeoutMs: 10_000,
maxDeliveryAttempts: 4,
onDelivery: (summary) => metrics.record(summary),
onDeliveryError: (error) => logger.warn({ error }, "trAIce delivery delayed"),
});
configure({
adapters: [cloud, new LocalAdapter("./.traice-costs/events.ndjson")],
});Flush before exit
Call flush() before a short-lived process, job, or serverless invocation exits. It waits for pending writes and adapter buffers.
import { flush } from "@traice/sdk";
await flush();In a reused serverless environment, note that CloudAdapter.flush() is
shutdown-oriented and stops its periodic event and policy timers. Lambda can
also freeze unfinished asynchronous work after the handler returns. Read
SDK Runtime Architecture and Performance for the
delivery-first batchSize: 1 workaround, cache behavior, and measured latency.
Use getMeterStats() to inspect tracked events, dropped events, adapter errors, and unknown models for the current process.
For an explicitly constructed CloudAdapter, use getDeliveryStats() to
inspect queue depth and age, accepted and deduplicated rows, quota drops,
retries, failed batches, and the most recent success or error. Delivery uses
one in-flight flush, strict batches, a request timeout, bounded exponential
backoff, and Retry-After or rate-limit reset guidance from the server.
Prompt and output samples are not sent by default, even when they are present
on a local CostEvent. Set captureContent: true on CloudAdapter, or
cloudCaptureContent: true in global configuration, only after the workspace
has approved content collection.
When a local event includes prompt, cloud delivery derives a versioned,
API-key-scoped HMAC for duplicate-spend analysis. The fingerprint is stable
after conservative whitespace normalization. Raw prompt content remains
omitted unless content capture is enabled.
Framework integrations
The package exports helpers for Express, Next.js, and LangChain:
createExpressMiddleware()attaches prefilledreq.meter()andreq.meterStream()helpers.withCostTracking()wraps a Next.js App Router Route Handler.withMeteredAction()wraps a Next.js Server Action.createNextApiHandler()wraps a Pages Router API handler.LangChainCostHandlerrecords LLM callback usage exposed by LangChain.
These helpers use the same global SDK configuration and attribution model as meter().
Advisory workspace budgets
Use cloud budget policy when your application should remain the final decision maker. Warm it once during startup; the request path then reads memory only.
const cloud = new CloudAdapter({ apiKey: process.env.TRAICE_API_KEY! });
await cloud.warmPolicy();
const budget = cloud.getBudgetAdvice({
feature: "support-summary",
userId: currentUser.id,
});
if (budget.isBlocked) return fallbackWithoutAnLlm();
const model = budget.shouldDowngrade ? "gpt-4o-mini" : "gpt-4o";
const response = await openai.chat.completions.create({ model, messages });shouldDowngrade() uses the 80% warning threshold and isBlocked() uses the
100% exceeded threshold across matching workspace, feature, and user budgets.
Both are advisory: your code chooses the fallback or model. A cold, expired,
or failed cache returns false and refreshes asynchronously, so this path is
fail-open and adds no policy network read to the call.
Use getBudgetAdvice() when you need the matching scopes, utilization, or
reason, and getEnforcementStats() to observe policy refresh failures and
fail-open checks.
Active request enforcement
CloudAdapter.enforceRequest() executes supported active request rules for an explicitly wrapped path: exact or semantic cache, deny, retry cap, evidence-gated swap, downgrade, or route, and one-shot fallback.
Keep one adapter for the process and warm its rules before serving traffic. A cold or expired rules cache passes through and refreshes in the background.
import { CloudAdapter, TraiceEnforcementError } from "@traice/sdk";
const cloud = new CloudAdapter({ apiKey: process.env.TRAICE_API_KEY! });
await cloud.warmEnforcement();
const request = {
model: "gpt-4o",
messages: [{ role: "user", content: "Summarize this ticket" }],
temperature: 0,
};
try {
const response = await cloud.enforceRequest(
request,
(effectiveRequest) => openai.chat.completions.create(effectiveRequest),
{ feature: "support-summary", retryCount: 0, provider: "openai" },
);
} catch (error) {
if (error instanceof TraiceEnforcementError) {
console.error(error.toJSON());
} else {
throw error;
}
}Active deny and retry-cap rules throw TraiceEnforcementError before the provider call. Shadow rules pass through. A shadow semantic-cache rule may observe the completed call with the configured embedder and report a scored cache opportunity, but it never bypasses the provider. Unsupported actions, malformed rules, unavailable evidence, rule API errors, and explicit bypasses also pass through. Streaming requests can be denied or retry-capped but are never cached.
Swap and downgrade require current experiment evidence for the exact feature, source model, and target model. Fallback makes one configured fallback call after the original provider call fails. If it also fails, the original provider error is preserved.
Route rules require the same current evidence plus a non-empty model allowlist that contains the one configured target. The SDK does not select models autonomously and does not proxy provider traffic.
Controlled rollouts use deterministic local assignment from the cached policy.
For conversational or account-level consistency, pass a non-PII rolloutKey:
await cloud.enforceRequest(request, providerCall, {
feature: "support-summary",
provider: "openai",
rolloutKey: account.id,
});The raw key is hashed locally and is never uploaded. Assignment telemetry contains the bucket, arm, policy revision, and a request-scoped correlation ID. Successful, failed, and source-fallback outcomes include available provider and retry provenance. Without a key, each wrapped request receives a request-scoped assignment. A failing treatment call can make one source fallback call when enabled by the rollout policy.
Opt in to semantic caching
Semantic caching uses a bounded, process-local LRU and a customer-supplied embedding function. Your application owns the embedding infrastructure and its credentials.
const cloud = new CloudAdapter({
apiKey: process.env.TRAICE_API_KEY!,
semanticCache: {
embed: async (text) => myEmbeddingClient.embed(text),
timeoutMs: 1_000,
maxEntries: 250,
},
});The SDK sends no request text or response content to trAIce. The embedder gets
the normalized request by default. Pass semanticCacheText in the enforcement
context when you want to embed a smaller, approved representation. Entries are
isolated by workspace, rule, and requested model, then governed by the rule TTL
and similarity threshold. Streams and explicit bypasses are never cached.
Missing configuration, invalid input, embedding errors, and embedding timeouts
fail open to one normal provider call. Use getSemanticCacheStats() for local
cache health and savings metrics. For a shadow semantic-cache rule, matching
and the USD floor are evaluated in process. Only the similarity and verified
token cost basis are reported to trAIce.
Privacy and failure behavior
Provider errors are re-thrown. Adapter failures do not replace a successful provider response unless your application explicitly waits for adapter writes. Use onError or verbose configuration for adapter diagnostics.
Prompts and outputs are not required for attribution. You may pass prompt for
local fingerprinting without enabling content capture. Raw prompt or output
delivery still requires explicit captureContent approval.