npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@meilynx/sdk

v0.7.0

Published

Meilynx SDK for business-outcome and span ingestion, plus proxy correlation context.

Readme

Meilynx JS SDK

CI npm License

Meilynx is an AI governance and FinOps platform that gives enterprises visibility and control over LLM usage — from cost and compliance to business outcomes. Route your LLM traffic through the Local Proxy for cost, tokens, model, latency, and governance; use this SDK to record business-outcome events (captureOutcome), emit non-LLM spans, and propagate correlation context from your Node.js applications.

[!IMPORTANT] The SDK does not ingest LLM telemetry. LLM cost, tokens, model, latency, governance, and budgets are captured by the Local Proxy — the sole supported LLM-telemetry path — with business dimensions attached via the x-meilynx-context header. The SDK's roles are business-outcome events, non-LLM spans, and the correlation context that ties them back to proxy-captured LLM activity. (The track() LLM-ingestion helpers were removed in v0.7.0 — see the CHANGELOG.)

Install

npm install @meilynx/sdk
# or
yarn add @meilynx/sdk
# or
pnpm add @meilynx/sdk

Quickstart

Correlation context on proxy-routed calls

Point your OpenAI / Anthropic client at the Meilynx proxy, then call instrument() once at startup. Calls made inside an observe() / withAgent() scope automatically carry the active correlation context as the x-meilynx-context header (and, when a trace is active, a W3C traceparent), so the proxy attributes the captured LLM cost to the right customer, feature, agent, and step — no request-body changes.

import { MeilynxClient, instrument, observe } from "@meilynx/sdk";
import OpenAI from "openai";

const mx = new MeilynxClient({
  apiKey: process.env.MX_API_KEY,  // baseUrl defaults to https://api.meilynx.com
});

instrument({ client: mx }); // OpenAI + Anthropic calls carry Meilynx context on the proxy path

// Route the provider client through your Meilynx proxy.
const openai = new OpenAI({ baseURL: process.env.MEILYNX_PROXY_URL });

await observe({ featureKey: "ask_docs", customerId: "cust-acme" }, async () => {
  await openai.chat.completions.create({ model: "gpt-4o", messages: [/* ... */] });
});

await mx.shutdown();

The proxy records model, tokens, cost, latency, and governance for that call; the SDK's job is to tell it who and what the call was for.

Configuration

Environment variables (convention)

The SDK does not read environment variables directly. These are recommended names for your app configuration:

  • MX_BASE_URL (optional) — Meilynx API URL
  • MX_API_KEY — Project-scoped API key (mx_live_...)

Constructor options

| Option | Type | Default | Notes | | --- | --- | --- | --- | | apiKey | string | — | Required. API key (mx_live_...) for /v1/ingest/*. | | baseUrl | string | "https://api.meilynx.com" | Base URL for the Meilynx API. | | sourceSystem | string | optional | Defaults to sdk. | | outcomesEndpointPath | string | /v1/ingest/outcomes/events/batch | Outcome-event ingestion path. | | spansEndpointPath | string | /v1/ingest/spans | Non-LLM span ingestion path. | | flushAt | number | 25 | Batch size before flush. | | flushIntervalMs | number | 5000 | Auto-flush interval (ms). Set to 0 to disable. | | maxRetries | number | 3 | Retry attempts on 429/5xx. | | retryDelayMs | number | 250 | Base delay for backoff (ms). | | disableValidation | boolean | false | Disable JSON schema validation. |

Context propagation with observe()

The observe() function propagates business context (correlation IDs, feature keys, customer IDs) through the async call stack using AsyncLocalStorage. All AI calls made inside inherit this context automatically and carry it on the x-meilynx-context header when routed through the proxy:

import { observe } from "@meilynx/sdk";

async function handleRequest(customerId: string) {
  return observe({ featureKey: "ask_docs", customerId }, async () => {
    // all AI calls here are tagged with featureKey="ask_docs"
    const response = await openai.chat.completions.create({
      model: "gpt-4o",
      messages: [{ role: "user", content: "..." }],
    });
    return response;
  });
}

Nested observe() calls inherit the parent context and can override specific fields.

Agentic loops — withAgent, withStep, withTool

For agentic loops with multiple tool-call hops, use the dedicated helpers so every LLM call in one turn shares a single correlationId, is attributed to the named agent, and nests correctly in the trace tree. withAgent() establishes a trace root; withStep() / withTool() mint child spans and emit non-LLM span events to /v1/ingest/spans. Without an outer wrapper, a bare observe({ stepIndex: i }) inside a for loop generates a fresh random correlationId per iteration — the work scatters across distinct correlation groups.

import { withAgent, withStep, withTool } from "@meilynx/sdk";

await withAgent({ agentName: "workspace-assistant", featureKey: "assistant" }, async () => {
  for (let i = 0; i < maxSteps; i++) {
    const completion = await withStep(i, async () => {
      return openai.chat.completions
        .stream({ model: process.env.AZURE_OPENAI_DEPLOYMENT, messages, tools })
        .finalChatCompletion();
    });

    if (completion.choices[0].finish_reason === "stop") break;

    for (const call of completion.choices[0].message.tool_calls ?? []) {
      const result = await withTool(call.function.name, async () => runTool(call));
      messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) });
    }
  }
});

This populates the four dimensions the agentic dashboard groups on — correlationId, agentName, stepIndex, toolName — and, via the traceparent header, nests each proxied LLM call under the active step to form a session → turn → call tree in the Explorer.

See the full guide at docs.meilynx.com / Instrumenting Agentic Loops.

Azure OpenAI

Azure OpenAI is supported the same way: point your AzureOpenAI client (or an OpenAI client configured with an Azure baseURL) at the Meilynx proxy. The wrapper attaches the x-meilynx-context / traceparent headers on those calls too; the proxy tags the provider and resolves the deployment to the underlying model for cost.

Capturing outcomes

Outcomes are the business results your AI features produce:

import { mintIdempotencyKey } from "@meilynx/sdk";

mx.captureOutcome({
  outcomeType: "feature.result.accepted",
  idempotencyKey: mintIdempotencyKey("accepted", correlationId),
  correlationId,
  customerId: "cust-acme",
  featureKey: "ask_docs",
  occurredAtUtc: new Date(),
});

Set traceId / spanId / parentSpanId on an outcome to attach it to the trace tree built from proxy LLM-call spans, so a business result appears as a leaf under the turn that produced it.

Idempotency keys

Every outcome requires an idempotencyKey to prevent duplicate processing. Use mintIdempotencyKey() to generate a deterministic SHA-256 key from one or more fields:

import { mintIdempotencyKey } from "@meilynx/sdk";

// Same inputs always produce the same key
mintIdempotencyKey("accepted", "run-123");           // → "a1b2c3..."
mintIdempotencyKey("accepted", "run-123");           // → "a1b2c3..." (same)
mintIdempotencyKey("accepted", "run-456");           // → "d4e5f6..." (different)

Budget status

Check current budget utilization from your application. Results are cached for 60 seconds per query-parameter combination.

const status = await mx.getBudgetStatus({ customerId: "acme" });

for (const budget of status.budgets) {
  if (budget.action === "block") {
    console.warn(`Budget ${budget.name} exceeded: ${budget.utilizationPct}%`);
  }
}

Failsafe behavior

The SDK is designed to never break your application. Context injection on the provider wrappers is wrapped in defensive error handling:

  • If context building or header injection fails, the original LLM call proceeds unmodified.
  • If a provider rejects an instrumented request with a Meilynx-caused 400, the SDK retries once with the original args.
  • Real LLM provider errors (rate limits, auth failures, invalid requests) always propagate normally.

In other words: a bug in the Meilynx SDK will log a warning but never cause your AI calls to fail.

Browser / client-side outcomes

The main SDK is server-only, but you can capture outcomes from browser code using the lightweight @meilynx/sdk/browser export. It sends events to a server-side proxy endpoint you control — no API key is exposed to the browser.

Browser client:

import { MeilynxBrowser } from "@meilynx/sdk/browser";

const mx = new MeilynxBrowser({ proxyUrl: "/api/meilynx/outcome" });

await mx.captureOutcome({
  outcomeType: "feature.result.accepted",
  idempotencyKey: "accepted:run-123",
  correlationId: "run-123",
  occurredAtUtc: new Date().toISOString(),
});

Server-side proxy (Next.js example):

// app/api/meilynx/outcome/route.ts
import { MeilynxClient } from "@meilynx/sdk";

const mx = new MeilynxClient({ apiKey: process.env.MX_API_KEY! });

export async function POST(req: Request) {
  const body = await req.json();

  if (Array.isArray(body.events)) {
    mx.captureOutcomeBatch(body.events);
  } else {
    mx.captureOutcome(body);
  }

  await mx.flush();
  return Response.json({ ok: true });
}

See the browser outcomes guide for more details.

Batching and flushing

The SDK buffers outcome and span events and flushes automatically. Use flush() for deterministic delivery (e.g., request end) and shutdown() to drain queues before process exit. Retries occur for HTTP 429 and 5xx responses with exponential backoff; 401/403 errors throw immediately with an auth hint.

In short-lived environments (Lambda, Cloudflare Workers, Vercel Edge), flush before the handler returns:

// Vercel Edge / Cloudflare Workers
export default {
  async fetch(req, env, ctx) {
    const result = await handleRequest(req);
    ctx.waitUntil(mx.flush());     // flush without blocking the response
    return result;
  },
};

// AWS Lambda / Next.js API routes
export async function handler(event) {
  const result = await handleRequest(event);
  await mx.flush();                // flush before returning
  return result;
}

Set flushIntervalMs: 0 to disable the background flush timer if the runtime does not support long-lived timers.

Example script

npm run build
MX_BASE_URL=http://localhost:5000 MX_API_KEY=mx_live_... node examples/send-outcome.mjs

Compatibility

  • Node.js 18+ (recommended)
  • Works in serverless runtimes that support node:crypto and fetch.
  • Server-side SDK is not intended for browsers. Use @meilynx/sdk/browser for client-side outcome capture.

Security and data handling

  • Avoid sending sensitive PII unless required for analytics.
  • Prefer hashed or pseudonymous IDs (e.g., customerId, endUserId).
  • Redact secrets from attributes before sending.

Docs

Roadmap

  • OpenTelemetry bridge for trace export
  • Edge runtime optimizations
  • Built-in redaction helpers

License

MIT