Documentation menu
DocsProduct SDKs

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

Shell
npm install @traice/sdk openai

Provider 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.

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
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

OptionMeaning
providerExplicit provider identifier or stream hint
featureProduct feature or request path
tenantIdPaying customer or account
userIdEnd user
agentIdAgent identity
workflowIdWorkflow identity
runIdOne workflow or agent execution
stepIdStep within an execution
toolNameTool used by an agent
retryCountRetry attempt number
outcomeProduct or workflow result
sessionIdApplication session
envEnvironment label
metadataArbitrary structured context
tagsLegacy string key/value metadata
promptName, promptVersionVersioned prompt identifiers
prompt, outputOptional sample content when explicitly approved

Pass tenantId on customer-facing calls to support customer-level AI contribution margin.

Choose adapters

AdapterBehaviorTypical use
cloudBatch product events to trAIceProduction attribution
consolePrint events to stdoutLocal inspection
localAppend newline-delimited JSON to diskLocal analysis and CLI reports
webhookPOST events to your endpointCustom telemetry pipelines
otelEmit OpenTelemetry metricsExisting observability stacks

Use adapter names for default construction or pass adapter instances for explicit configuration.

TypeScript
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.

TypeScript
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:

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.

TypeScript
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.

TypeScript
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:

TypeScript
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.

TypeScript
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.

Reference and source