Skip to main content
GitHub

JavaScript SDK

Complete JavaScript/TypeScript SDK reference.

Reference for the Risicare JavaScript/TypeScript SDK. The current published version is 0.9.0 — see the Changelog.

Installation

npm install risicare

The package has named exports only. It has no default export. Use import { init } from 'risicare' or import * as risicare from 'risicare'.

Core Functions

init

Initialize the SDK.

import { init } from 'risicare';
 
init(config?: RisicareConfig): void

enable / disable / isEnabled

Runtime control.

import { enable, disable, isEnabled } from 'risicare';
 
enable(): void
disable(): void
isEnabled(): boolean

isEnabled() is false before init(), with tracing off, with no API key, and after the SDK's own SIGTERM or SIGINT drain. enable() cannot turn tracing on when the SDK has no API key. In 0.8.0 and earlier, RISICARE_TRACING=true with no key left it true.

flush / shutdown

Export control.

import { flush, shutdown } from 'risicare';
 
flush(timeoutMs?: number): Promise<boolean>     // default 5000
shutdown(timeoutMs?: number): Promise<void>     // default 5000

flush() resolves true only when the gateway acknowledged every span and every score that the SDK accepted since the previous flush() returned (or since init()), and nothing is in the queue or in flight. This holds also in a SIGTERM or SIGINT listener and after shutdown().

A loss makes the next flush() resolve false one time, and the one after it true again. A loss is a span dropped for any reason (shutdown_residue included), a span rejected or left out of an acknowledgement, and a failed score (also a score that score() refuses at the call). Each one raises a counter that names the reason: droppedSpansByReason, rejectedSpans, failedScores. A flush() that resolves false at its deadline (timeoutMs, default 5000 ms) while none of them went up since the previous flush() means that the spans are still in flight, not lost. failedExports is not a loss counter: a later round can still deliver those spans.

flush() also resolves false when the SDK has no evidence of delivery: before init(), with tracing off, and with no API key. A flush() that you call while the SDK's own drain runs (your SIGTERM or SIGINT listener, registered after init()) waits for the drain inside its own deadline. flush() only waits; to end a process in a fixed time, call await shutdown(timeoutMs). A new init() starts a new count.

In 0.8.0 and earlier, flush() resolved true before init() and with tracing off, and it could resolve true in a signal listener or after shutdown() with nothing delivered.

If your own SIGTERM or SIGINT listener makes spans that must be delivered, register it before init(). Your listener runs once, in both orders, and the exit code that it sets stands: the SDK raises the signal again, so that the default action ends the process, only when you have no listener at that moment.

getMetrics

Returns processor metrics for monitoring export health.

import { getMetrics } from 'risicare';
 
getMetrics(): {
  sdkState: 'running' | 'shutdown' | 'uninitialised';
  exportedSpans: number;
  droppedSpans: number;
  droppedSpansByReason: {
    queue_full: number;
    transport_refused: number;
    shutdown_residue: number;
    unacknowledged: number;
  };
  rejectedSpans: number;
  failedScores: number;
  failedExports: number;
  maskErrors?: number;        // absent when sdkState is 'uninitialised'
  queueSize: number;
  queueCapacity: number;
  queueUtilization: number;
}

exportedSpans counts the spans that the gateway accepted, rejectedSpans the spans that it rejected, and failedScores the score requests that failed. sdkState: 'shutdown' marks the final counts, kept after shutdown().

getTraceContent

Returns whether content tracing is enabled.

import { getTraceContent } from 'risicare';
 
getTraceContent(): boolean

reportError

Report a caught exception so Risicare records it on an error span and classifies it against the error taxonomy. Root-cause diagnosis and fix generation are held for the beta, so reporting an error does not trigger them. Never throws.

import { reportError } from 'risicare';
 
try {
    const result = await llm.invoke(query);
} catch (error) {
    reportError(error);          // Records and classifies the error
    return fallbackResponse;
}
ParameterTypeDefaultDescription
errorError | string(required)The caught exception
options.namestringerror class nameCustom span name
options.attributesRecord{}Additional span attributes

score

Record a custom evaluation score on a trace. Non-blocking — sends in background via fetch(). Never throws.

import { score } from 'risicare';
 
score('4bf92f3577b34da6a3ce929d0e0e4736', 'factual_accuracy', 0.92, {
    comment: 'Verified against source documents',
});
ParameterTypeDefaultDescription
traceIdstring(required)The trace to score
namestring(required)Score name
valuenumber(required)Score between 0.0 and 1.0
options.spanIdstringundefinedSpecific span within the trace
options.commentstringundefinedHuman-readable explanation

A value outside [0.0, 1.0] is not sent. The SDK logs a WARNING for it. A failed request (an error, a timeout after 2000 ms, or a status of 300 or more) adds 1 to failedScores; the first failure logs a WARNING, and failures in the next 10 seconds are counted in the next one. The SDK does not follow a redirect. The traceId must be 32 lowercase hex characters: the SDK does not check the form, and the API refuses any other form.

Provider Patches

12 native provider patches, each imported from a sub-path export. All client-based patches return a transparent ES Proxy wrapper — the original client is unchanged. patchVercelAI() is the exception — it takes no client and wraps top-level functions, returning { tracedGenerateText, tracedStreamText, tracedGenerateObject }.

ProviderImportPatch Function
OpenAIrisicare/openaipatchOpenAI(client)
Anthropicrisicare/anthropicpatchAnthropic(client)
Vercel AIrisicare/vercel-aipatchVercelAI()
Google Geminirisicare/googlepatchGoogleAI(client)
Mistralrisicare/mistralpatchMistral(client)
Groqrisicare/groqpatchGroq(client)
Cohererisicare/coherepatchCohere(client)
Together AIrisicare/togetherpatchTogether(client)
Ollamarisicare/ollamapatchOllama(client)
HuggingFacerisicare/huggingfacepatchHuggingFace(client)
Cerebrasrisicare/cerebraspatchCerebras(client)
AWS Bedrockrisicare/bedrockpatchBedrock(client)
import { patchOpenAI } from 'risicare/openai';
import { patchAnthropic } from 'risicare/anthropic';
import { patchGroq } from 'risicare/groq';
import OpenAI from 'openai';
 
const openai = patchOpenAI(new OpenAI());
// All chat.completions.create and embeddings.create calls are now traced

Host Detection (OpenAI-Compatible)

When using patchOpenAI() with a client pointing to an OpenAI-compatible API, the SDK automatically detects the real provider from the base URL and sets gen_ai.system correctly:

ProviderBase URL
DeepSeekapi.deepseek.com
Together AIapi.together.xyz
Groqapi.groq.com
xAI (Grok)api.x.ai
Fireworksapi.fireworks.ai
Baseteninference.baseten.co
Novitaapi.novita.ai
BytePlusapi.byteplus.com, ark.cn-beijing.byteplus.com
import { patchOpenAI } from 'risicare/openai';
import OpenAI from 'openai';
 
// DeepSeek via OpenAI-compatible API — auto-detected as "deepseek"
const deepseek = patchOpenAI(new OpenAI({
    baseURL: 'https://api.deepseek.com/v1',
    apiKey: process.env.DEEPSEEK_API_KEY,
}));

patchVercelAI

Returns higher-order wrapper functions for Vercel AI SDK:

import { generateText, streamText, generateObject } from 'ai';
import { patchVercelAI } from 'risicare/vercel-ai';
 
const { tracedGenerateText, tracedStreamText, tracedGenerateObject } = patchVercelAI();
const generate = tracedGenerateText(generateText);
const stream = tracedStreamText(streamText);
const genObject = tracedGenerateObject(generateObject);

Framework Integrations

4 framework integrations, each imported from a sub-path export:

LangChain.js

import { RisicareCallbackHandler } from 'risicare/langchain';
 
const handler = new RisicareCallbackHandler();
const result = await chain.invoke(input, { callbacks: [handler] });

LangGraph.js

import { instrumentLangGraph } from 'risicare/langgraph';
 
const tracedGraph = instrumentLangGraph(compiledGraph);
const result = await tracedGraph.invoke(input);

Instructor

import { patchInstructor } from 'risicare/instructor';
 
const patchedClient = patchInstructor(instructorClient);
const result = await patchedClient.chat.completions.create({
  model: 'gpt-4o',
  response_model: { schema: MySchema, name: 'MySchema' },
  messages: [{ role: 'user', content: 'Extract the data' }],
});

LlamaIndex.TS

import { Settings } from 'llamaindex';
import { RisicareLlamaIndexHandler } from 'risicare/llamaindex';
 
const handler = new RisicareLlamaIndexHandler();
for (const name of RisicareLlamaIndexHandler.EVENT_NAMES) {
  Settings.callbackManager.on(name as any, (e: any) => handler.onEvent(e));
}
 
// Provider spans are suppressed only inside withSuppression():
const result = await handler.withSuppression(() => queryEngine.query({ query: '...' }));

Streaming

Trace async iterables with automatic span lifecycle management:

import { tracedStream } from 'risicare';
 
const stream = tracedStream(asyncIterable, { name: 'llm-stream' });
for await (const chunk of stream) {
    handleChunk(chunk);
}
// Span automatically records stream.chunk_count and stream.completed

Dedup

Framework integrations can suppress provider-level tracing to prevent double-tracing:

import { suppressProviderInstrumentation, isProviderInstrumentationSuppressed } from 'risicare';
 
await suppressProviderInstrumentation(async () => {
    // Provider patches are suppressed inside this scope
    await client.chat.completions.create(/* ... */);
});

Higher-Order Functions

agent

import { agent } from 'risicare';
 
agent<T extends (...args: any[]) => any>(
  options: {
    name?: string;
    role?: string;  // any string; canonical values are the AgentRole enum
    agentType?: string;
    version?: number;
    metadata?: Record<string, unknown>;
  },
  fn: T
): T

session

import { session } from 'risicare';
 
session<T extends (...args: any[]) => any>(
  optionsOrResolver: SessionOptions | ((...args: any[]) => SessionOptions),
  fn: T
): T
 
interface SessionOptions {
  sessionId: string;
  userId?: string;
}

Phase Functions

Phase decorators accept either a bare function or a name + function (added in 0.2.1):

These return a wrapped function — you must call it

traceThink / traceDecide / traceAct / traceObserve do not run your function. They return a wrapper, and a span is emitted only when you invoke that wrapper. Writing traceAct('execute-tool', fn); on its own emits zero spans — with no error and no warning, which reads as "tracing is broken" rather than "the wrapper was never called".

import { traceThink, traceDecide, traceAct, traceObserve } from 'risicare';
 
// Bare form with an anonymous function — the span is named `phase:think`.
// With a named function, the span is `<phase>:<function>`, for example `think:analyzeQuery`.
const analyzeBare = traceThink(async () => { /* ... */ });
 
// Named form — explicit span name
const analyze = traceThink('analyze-query', async () => { /* ... */ });
 
// All four phases support both forms
const choose = traceDecide('choose-action', async () => { /* ... */ });
const execute = traceAct('execute-tool', async () => { /* ... */ });
const check = traceObserve('check-result', async () => { /* ... */ });
 
// Nothing is traced until the wrappers are invoked:
await analyze();
await choose();
await execute();
await check();

Multi-Agent Functions

import { traceMessage, traceDelegate, traceCoordinate } from 'risicare';
 
traceMessage<T extends (...args: any[]) => any>(
  options: { to: string },
  fn: T
): T
 
traceDelegate<T extends (...args: any[]) => any>(
  options: { to: string },
  fn: T
): T
 
traceCoordinate<T extends (...args: any[]) => any>(
  options: { participants: string[] },
  fn: T
): T

Context Functions

getCurrentContext

import { getCurrentContext } from 'risicare';
 
// Returns a plain object snapshot. session/agent/span are either an
// object or null; phase is a SemanticPhase or null.
getCurrentContext(): {
  session: { sessionId: string; userId?: string } | null;
  agent: {
    agentId: string;
    agentName?: string;
    agentRole?: string;
    agentType?: string;
  } | null;
  span: { spanId: string; traceId: string } | null;
  phase: SemanticPhase | null;
}

Individual Getters

import {
  getCurrentTraceId,
  getCurrentSpanId,
  getCurrentAgentId,
  getCurrentSessionId,
} from 'risicare';
 
getCurrentTraceId(): string | undefined
getCurrentSpanId(): string | undefined
getCurrentAgentId(): string | undefined
getCurrentSessionId(): string | undefined

W3C Trace Context

import { injectTraceContext, extractTraceContext } from 'risicare';
 
injectTraceContext(headers: Record<string, string>): Record<string, string>
 
// Always returns a record (never undefined). When the incoming headers
// carry a Risicare tracestate it includes traceId, parentSpanId, flags,
// and any propagated sessionId / agentId.
extractTraceContext(headers: Record<string, string>): Record<string, string | undefined>

getTraceContext

Returns the current trace context as a serializable object. When called outside any active span it pre-allocates a root trace ID so the next span created in the same context inherits it.

import { getTraceContext } from 'risicare';
 
getTraceContext(): TraceContext   // { traceId, spanId, sessionId?, agentId? }

Context Scope Wrappers

Run a callback inside an explicit session, agent, or phase scope. Each returns the callback's result.

import { withSession, withAgent, withPhase } from 'risicare';
import { SemanticPhase } from 'risicare';
 
withSession(options: { sessionId: string; userId?: string }, fn: () => T): T
withAgent(options: { name?: string; role?: string; agentType?: string }, fn: () => T): T
withPhase(phase: SemanticPhase, fn: () => T): T

Webhook Verification

Verify the signature of an incoming Risicare webhook delivery before processing it. verifyWebhookSignature is a root export (import { verifyWebhookSignature } from 'risicare') — there is no risicare/webhooks subpath. It is dependency-light (Node crypto only) and ships in risicare@0.4.0.

import {
  verifyWebhookSignature,
  WebhookVerificationError,
  DEFAULT_TIMESTAMP_TOLERANCE_S,   // 300 (seconds)
} from 'risicare';
 
verifyWebhookSignature(
  payload: Uint8Array | ArrayBuffer | string,   // raw request body bytes
  headers: Headers | Map<string, string> | Record<string, string | string[] | undefined>,
  secret: string,
  opts?: { toleranceS?: number },               // default 300s skew window
): void   // returns undefined on success; throws on any failure

The verifier enforces three checks (all must pass): both X-Risicare-Signature and X-Risicare-Timestamp headers present and well-formed; timestamp within toleranceS of now (replay protection); and a constant-time HMAC-SHA256 match over the canonical <timestamp>.<payload_bytes> string. Any failure throws WebhookVerificationError — catch it specifically to return a 401.

Pass the raw body, not parsed JSON

The signature is computed over the EXACT bytes received. Parsing JSON and re-serializing changes key ordering and whitespace, so the signature will not match. Use your framework's raw-body option (e.g. express.raw()).

import express from 'express';
import { verifyWebhookSignature, WebhookVerificationError } from 'risicare';
 
const app = express();
app.post('/risicare-webhook', express.raw({ type: '*/*' }), (req, res) => {
  try {
    verifyWebhookSignature(req.body, req.headers, process.env.WEBHOOK_SECRET!);
  } catch (e) {
    if (e instanceof WebhookVerificationError) {
      return res.status(401).send(e.message);
    }
    throw e;
  }
  // ... process the verified event ...
  res.status(200).end();
});

Node runtime only

This verifier is synchronous and uses Node's crypto module. It does not run as-is on Cloudflare Workers / Vercel Edge / Deno Deploy. An async Web Crypto variant can be added on request.

Span Registry

import {
  registerSpan,
  getSpanById,
  unregisterSpan,
} from 'risicare';
 
registerSpan(span: Span, ttlMs?: number): void
getSpanById(spanId: string): Span | undefined
unregisterSpan(spanId: string): void

Types

RisicareConfig

interface RisicareConfig {
  apiKey?: string;
  projectId?: string;  // DEPRECATED — emits console.warn. Will be removed in v1.0.
  endpoint?: string;     // default 'https://ingest.risicare.ai' (spans)
  apiEndpoint?: string;  // default 'https://api.risicare.ai' (scores)
  environment?: string;
  serviceName?: string;
  serviceVersion?: string;
  enabled?: boolean;
  traceContent?: boolean;
  compress?: boolean;
  sampleRate?: number;
  batchSize?: number;
  batchTimeoutMs?: number;
  maxQueueSize?: number;
  debug?: boolean;
  fixRuntime?: boolean;  // default false since 0.7.0
  metadata?: Record<string, unknown>;
  mask?: (key: string, value: unknown) => unknown;
}

TraceContext

interface TraceContext {
  traceId: string;
  spanId: string;
  sessionId?: string;
  agentId?: string;
}

Span

Span and Tracer are types only. Import them with import type { Span, Tracer } from 'risicare'. A value import (import { Span } from 'risicare') is a compile error in TypeScript (TS2693), and a SyntaxError at run time in plain JavaScript under ESM (under CommonJS, require('risicare').Span is undefined).

class Span {
  traceId: string;
  spanId: string;
  name: string;
  kind: SpanKind;
  startTime: string;        // ISO 8601 timestamp
  endTime?: string;         // ISO 8601 timestamp, set on end()
  attributes: Record<string, unknown>;
  events: SpanEvent[];
  status: SpanStatus;
 
  setAttribute(key: string, value: unknown): this;
  addEvent(name: string, attributes?: Record<string, unknown>): this;
  setStatus(status: SpanStatus, message?: string): this;
  end(): void;
}

SpanKind

enum SpanKind {
  INTERNAL = 'internal',
  SERVER = 'server',
  CLIENT = 'client',
  PRODUCER = 'producer',
  CONSUMER = 'consumer',
  AGENT = 'agent',
  LLM_CALL = 'llm_call',
  TOOL_CALL = 'tool_call',
  RETRIEVAL = 'retrieval',
  DECISION = 'decision',
  DELEGATION = 'delegation',
  COORDINATION = 'coordination',
  MESSAGE = 'message',
  THINK = 'think',
  DECIDE = 'decide',
  OBSERVE = 'observe',
  REFLECT = 'reflect',
}

SpanStatus

enum SpanStatus {
  UNSET = 'unset',
  OK = 'ok',
  ERROR = 'error',
}

Next Steps