Skip to main content
mirrorkit on npm is the TypeScript sibling of the Python package. Point it at your Anthropic / OpenAI / LangChain.js calls and your agent’s traces stream to Mirrors with negligible overhead — non-blocking, background-batched, and it never throws into your app.

Install

npm install mirrorkit
Requires Node ≥ 18 (uses the built-in fetch). Zero runtime dependencies — the LLM SDKs are optional peers, instrumented only if present.

Usage

import * as mirrorkit from 'mirrorkit';
import Anthropic from '@anthropic-ai/sdk';

mirrorkit.init({ apiKey: 'mk_live_...', project: 'my-agent' });

// Recommended: wrap each client once. Guaranteed capture, no global patching.
const anthropic = mirrorkit.instrument(new Anthropic());

const res = await anthropic.messages.create({
  model: 'claude-opus-4-8',
  max_tokens: 256,
  messages: [{ role: 'user', content: "What's the weather in Paris?" }],
});
init() also best-effort auto-instruments importable SDKs, so in many setups the instrument() call is optional. We recommend instrument() anyway: it is immune to the ESM/CJS dual-instance and version-drift issues that make global patching unreliable in JS.

Options

mirrorkit.init({
  apiKey: 'mk_live_...',
  project: 'my-agent',
  endpoint: 'https://api.runmirrors.com', // optional; else MIRRORKIT_ENDPOINT / MIRROR_ENDPOINT / prod
  flushIntervalMs: 2000,                  // ms between background flushes
  maxBatch: 50,                           // max traces per POST
  maxQueue: 10_000,                       // in-memory cap; oldest dropped past this
  instrument: true,                       // best-effort auto-patch at init()
  debug: false,                           // internal debug logs (or MIRRORKIT_DEBUG=1)
});

LangChain.js

LangChain.js has no global handler hook, so attach the handler explicitly — either per call or by wrapping the runnable:
// per call:
await chain.invoke(input, { callbacks: [mirrorkit.handler()] });

// or wrap once:
const traced = mirrorkit.instrument(chain);
await traced.invoke(input);
LangChain.js support is best-effort — validate against your pinned @langchain/core version. Anthropic and OpenAI are the fully-solid path.

Manual logging

For anything not auto-instrumented, enqueue a trace yourself (OpenAI-style messages):
mirrorkit.logTrace(
  [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: "What's the weather in Paris?" },
    {
      role: 'assistant',
      content: null,
      tool_calls: [{ id: 'call_1', function: { name: 'get_weather', arguments: '{"city":"Paris"}' } }],
    },
    { role: 'tool', tool_call_id: 'call_1', content: '18C, sunny' },
    { role: 'assistant', content: "It's 18C and sunny in Paris." },
  ],
  { model: 'gpt-4o' },
);

Custom instrumentation

Providers are pluggable — register your own to trace a framework that isn’t covered out of the box:
mirrorkit.register(
  mirrorkit.defineInstrumentation({
    name: 'my-framework',
    canWrap: (t): boolean => typeof (t as any).run === 'function',
    wrap: (target, ctx) => {
      const orig = (target as any).run.bind(target);
      (target as any).run = async (input: any) => {
        const out = await orig(input);
        ctx.emit({ id: crypto.randomUUID(), messages: /* convert out → messages */ [] });
        return out;
      };
      return target;
    },
  }),
);
register() works before or after init().

Graceful shutdown

The collector drains on Node’s beforeExit. For deterministic delivery in serverless or short-lived processes, flush explicitly:
await mirrorkit.flush();    // block until the queue drains
await mirrorkit.shutdown(); // stop the timer and flush

API

FunctionDescription
init(options)Start the collector (idempotent).
instrument(client)Wrap an SDK client / runnable; returns it traced.
register(instrumentation)Add or override an instrumentation.
defineInstrumentation(inst)Typed identity helper for custom instrumentations.
logTrace(messages, opts?)Manually enqueue one trace.
handler()LangChain.js callback handler.
flush(timeoutMs?)Block until the queue drains.
shutdown()Stop the collector (flushes first).

Known limitations

  • Streaming (stream: true) calls are passed through untraced — non-streaming calls are fully captured.
  • Auto-instrumentation can miss if your app loads a different copy of an SDK than init() resolves (ESM/CJS duplication). instrument() is unaffected.
  • LangChain.js is best-effort (see above).