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

@demystify/ai

v0.1.0

Published

Tenant-scoped composition layer for the Demystify AI kit. One createAI({ tenantKey }) call returns surfaces already bound to that tenant — no method takes a tenant key, so none can take the wrong one. Attaches a per-tenant BYOK Credential to every backend

Readme

@demystify/ai

This package implements nothing.

It has no scorer, no parser, no redactor, no tokenizer, no scheduler, no retry policy, no cache and no transport. Every one of those already lives in a leaf package that does it better than a composition layer would, and none of them belongs here.

What it does own is one thing: a façade already bound to one tenant, so the five things a hand-wired stack has to remember cannot be forgotten.

import { createAI } from "@demystify/ai";

const ai = createAI({ tenantKey: "acme" });   // zero-config, keyless, offline

const outcome = await ai.operate(
  { name: "chat", estimateMicroUsd: 1_200, constraints: { requires: ["hindi"] } },
  async (ctx) => {
    const answer = await yourGateway.complete(prompt, ctx.backend); // ctx.backend carries the tenant's key
    return { value: answer.text, costMicroUsd: answer.costMicroUsd };
  },
);

if (!outcome.ok) console.warn(outcome.why);   // a refusal always says why

That one call did all of this, in this order, with no way to skip a step:

  1. opened a span tagged with the tenant,
  2. picked a driver that meets the constraints — or refused, naming every constraint and every rejected driver,
  3. checked the usage ceiling before the work, and did not run the body if it would cross,
  4. handed the body a BackendContext with the tenant's BYOK credential already attached,
  5. recorded what it actually cost, in integer µUSD, after the work.

Why this exists at all

Wiring this stack by hand means threading one tenantKey into several subsystems, attaching the tenant's Credential to the backend context, opening a span, checking the budget before spending, and recording usage after. Forget any one of them and nothing fails loudly. Forget the tenant key and the failure is a cross-tenant data leak.

So the load-bearing property of this package is negative:

No method on the returned object takes a tenant key.

Not memory.remember, not runs.claim, not usage(), not operate(). A caller who cannot pass a tenant key cannot pass the wrong one. It is enforced twice — in the types, where every bound method's argument is Omit<…, "tenantKey"> derived from the leaf's own input type, and at runtime, where the bound key is spread in last so one that arrived through a variable is overwritten rather than obeyed. test/tenant-binding.test.ts walks the entire returned surface and fails if a tenant key ever becomes passable again.


Exactly what it wires

| Package | Relationship | What the façade adds | |---|---|---| | @demystify/memory | runtime dependency | ai.memory.*, tenant-bound. remember / recall / forget / erasureHistory lose their tenantKey parameter. Zero-config default: new Memory({ store: new InMemoryStore() }). | | @demystify/agent-kernel | runtime dependency | ai.runs.*, tenant-bound. claim loses its tenantKey. Zero-config default: new AgentKernel({ store: new MemoryRunStore() }). | | @demystify/skills | runtime dependency | ai.skills.*, fixed to this tenant's registry and spanned under its tag. Zero-config default: an empty registry. | | @demystify/evals | accepted structurally | Pass a report.aggregate per driver id as evidence and driver choice ranks on measured per-tag results instead of a hardcoded preference. | | @demystify/platform-observability | accepted structurally | spanExporterToMetrics(metrics) is a TelemetrySink — same field names, same method, no adapter. | | @demystify/gateway-moat | shape mirrored | Credential and BackendContext are field-identical, so a credential from bearerCredential() and a context from this façade go straight into a backend. |

What it deliberately does NOT wire

@demystify/ai-guardrails, @demystify/grounding, @demystify/context and @demystify/workflow are pure functions that take no tenant key and hold no state. There is nothing to bind, nothing to scope and nothing to attach — a wrapper would add a span and an import, and take away the ability to tree-shake.

Call them directly:

import { guardUntrusted } from "@demystify/ai-guardrails";
import { checkGrounding, INDIA_ACCOUNTING } from "@demystify/grounding";
import { createBudget } from "@demystify/context";
import { readySteps } from "@demystify/workflow";

That is not a gap. A composition layer that re-exports every leaf becomes the thing you cannot avoid installing, which is the bug this design exists to prevent — see docs/federation/AI-KIT-ARCHITECTURE.md §0.

You can skip this package entirely

Everything here is wiring you can do yourself in about forty lines. Use the leaves directly if you want fewer moving parts, or if your app is single-tenant — in a single-tenant app the anti-leak property buys you nothing, and this package is mostly ceremony. It earns its place when one process serves many tenants.


The four capabilities

1. BYOK — a per-tenant credential, attached and never logged

import { bearerCredential } from "@demystify/gateway-moat/core";

const ai = createAI({ tenantKey: "acme", credential: bearerCredential(tenantKey) });
ai.backend();  // { tenantKey: "acme", credential }  — frozen, ready for a backend

The credential is held in a closure, never as a property, so { ...ai }, Object.keys(ai), structuredClone and a debugger dump have nothing to find. It never reaches a span attribute (attributes are scalars only, so an object cannot be one), never reaches the run ledger, and never reaches a log line.

createAI refuses a credential that does not redact itself:

createAI({ tenantKey: "acme", credential: { scheme: "bearer", reveal: () => key,
                                            toJSON: () => key, toString: () => key } });
// AIError [leaky_credential]: the supplied credential does not redact itself:
//   toJSON() produced something other than "[REDACTED]". Build it with
//   bearerCredential() / rawCredential() from the gateway's core, …

2. Telemetry — one span per operation, tenant-tagged

No new tracing vocabulary. FinishedSpan and TelemetrySink are field-identical to @demystify/platform-observability's FinishedSpan and SpanExporterLike:

import { createMetrics, spanExporterToMetrics } from "@demystify/platform-observability";

const metrics = createMetrics();
const ai = createAI({ tenantKey: "acme", telemetry: spanExporterToMetrics(metrics) });
// → dmstfy_span_duration_seconds{span="ai.op.chat",module="ai"}

Telemetry fails open: a sink that throws is swallowed, because a metric that cannot record must never break the request it was observing. Safety fails closed: a non-scalar span attribute is rejected outright.

The default sink discards. No telemetry, no phone-home, until you ask.

3. Usage limits — checked before the spend, recorded after

const ai = createAI({ tenantKey: "acme", limits: createMemoryLimits({ ceilingMicroUsd: 5_000_000 }) });

const outcome = await ai.operate({ name: "chat", estimateMicroUsd: 9_000_000 }, run);
// { ok: false, refusal: "over_ceiling",
//   why: 'an estimated 9000000 µUSD would take period "total" to … over the ceiling of 5000000 µUSD' }

run is never called. The strongest evidence that a check happened before a spend is that the work did not happen at all.

Two kinds of money, never mixed. AI cost is an integer count of micro-USD (estimateMicroUsd, costMicroUsd). Book money is integer minor units with a currency ({ amountMinor, currency } — what a run ceiling takes). Neither is ever a float, and passing one where the other belongs throws:

ai.operate({ name: "chat", estimateMicroUsd: 1.5 }, run);
// AIError [invalid_amount]: estimateMicroUsd must be a non-negative integer count
//   of micro-USD, got 1.5. AI cost is integer µUSD; book money is integer minor
//   units with a currency. Never a float, never mixed.

Swap createMemoryLimits() for any UsageLimits — a Postgres row, a metering service — without touching a call site.

4. Pick and choose — by constraint, with the reason attached

const ai = createAI({
  tenantKey: "acme",
  drivers: [
    { id: "on-device", costMicroUsd: 0,   provides: ["english", "hindi"], offline: true },
    { id: "hosted-lg", costMicroUsd: 900, provides: ["english", "hindi"] },
  ],
  evidence: {                              // ← a @demystify/evals report.aggregate
    "on-device": onDeviceReport.aggregate,
    "hosted-lg": hostedReport.aggregate,
  },
});

ai.choose({ requires: ["hindi"] });
// { chosen: true, driver: { id: "hosted-lg", … }, measuredScore: 0.93,
//   why: 'chose "hosted-lg": measured mean score 0.9300 over ["hindi"] across 40 case(s),
//         the best of 2 driver(s) that met the constraints (requires ["hindi"]).' }

Two rules make this auditable rather than magic:

  • A constraint filters; it never merely prefers. When nothing qualifies the answer is a refusal carrying every constraint and every loser — never the closest match, because "closest" to requires: ["hindi"] is a model that answers in English.
  • The ranking is not invented here. With measured evidence, the winner is the one with the best measured score on the tags actually required; without it, the tie-break is cost and why says exactly that rather than implying a judgement was made. Scoring stays in @demystify/evals.

A Driver is a descriptor — an id, a cost, capability tags. It carries no function, no endpoint and no key, so choosing one cannot itself call anything. Your host maps the chosen id onto whatever actually sends.


Zero-config

createAI({ tenantKey }) works with no API key, no connection string, no Redis, no Docker and no env var, on a machine with the network unplugged. The defaults are in-memory throughout — the same discipline as @demystify/platform-cache, and for the same reason: a keyless default has to be a faithful rehearsal of production, not a stub that pretends.

Every part is swappable

createAI({
  tenantKey,
  credential,             // any self-redacting Credential
  memory,                 // any MemoryPort — e.g. Memory over PgMemoryStore
  runs,                   // any RunsPort   — e.g. AgentKernel over PgRunStore
  skills,                 // any SkillsPort
  limits,                 // any UsageLimits — Postgres, a metering service
  telemetry,              // any TelemetrySink
  clock,                  // any Clock
  drivers, evidence, period,
});

No transport

This package opens no socket. It orchestrates packages that talk to things; it talks to nothing itself. test/no-transport.test.ts fails the build on fetch, WebSocket, node:http, node:net, child_process, dynamic import(), eval, new Function, process.env, console, any URL, any provider name and any auth-header literal — and asserts the source imports only the three packages it declares as dependencies.

One assertion is inverted from the kernel's version of that suite, for the same reason @demystify/evals inverted it: for the kernel, a callback is a transport, so it forbids exports that accept callables. Here callables are the product — ports in, a body function through. The inverse is asserted instead: every outbound capability must be supplied by the host, and every default this package can build on its own must be inert.


Limits and known gaps

  • A body that throws records no spend. If a failed call still cost money, catch it inside the body and return the cost rather than letting it escape.
  • createMemoryLimits is a reference adapter, not a metering product. It forgets everything when the process exits and counts one process only. Use a real UsageLimits in production.
  • Driver choice is per call, not a failover chain. It picks one driver and explains why. Retry and failover across drivers belong in the gateway's Router, which already does alias chains and provider health.
  • No agent loop. ai.runs is the kernel's plan-and-ledger, which structurally cannot send. The loop is the host's.
  • ai.skills carries no tenant key of its own — a SKILL.md is not tenant data. Binding fixes the registry and adds the tenant tag to the span; if that is all you need, use createSkillRegistry directly.
  • Single process. Nothing here coordinates across machines.

Install

pnpm add @demystify/ai

ESM only. Node ≥ 22. TypeScript strict with exactOptionalPropertyTypes. Tenancy is an opaque string — never parsed, never split, never interpreted.

Licence

MIT.