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

@swfte/nexus-sdk

v0.1.0

Published

Embedded agent observability for Node services — zero dependencies, bundler-proof.

Readme

@swfte/nexus-sdk

Agent observability and enforcement for Node services you run yourself.

npm Node Licence Dependencies

nexus wrap captures what an agent does inside a developer's terminal. This package captures the same thing inside your own Node application — a third attach point on one event ledger, stamped producer="sdk" so a run in production and a run on a laptop are the same shape in the same tables.

npm install @swfte/nexus-sdk

Node 18+. Zero dependencies — see Why no dependencies. ESM and CommonJS both supported, from one instance.

Part of Nexus by Swfte — savings, governance and a real audit trail for AI agents. This is the Node sibling of swfte-nexus-sdk for Python: same concepts, same event shapes, same collector. See also the terminal wrapper (npm i -g @swfte/nexus).


Contents

| Section | | |---|---| | Start here, if you are bundling | read this before anything else if you deploy a bundle | | Three levels, pick one | the whole API, in increasing detail | | Guarantees | what this SDK promises never to do to your process | | Configuration | every variable and the precedence rule | | Privacy tiers | what actually goes on the wire, with a worked example | | OpenTelemetry | the OTLP bridge, and why it is a bridge | | Enforcement | the part that can say no | | What this does not do | honestly, and with reasons | | Why no dependencies | and what that costs us instead | | Development | running the suite, the layout |

Reference documentation

| Document | For | |---|---| | docs/API.md | every export, its signature, and what it returns | | PARITY.md | line-by-line account of what matches the Python SDK, what deliberately does not, and why | | SCOPE.md | the original feasibility spike — historical, kept for the record | | AGENTS.md | orientation for coding agents working in this repository | | CONTRIBUTING.md | how to run the suite and what review will ask | | SECURITY.md | reporting a vulnerability | | CHANGELOG.md | what changed, and why |


Start here, if you are bundling

Most Node advice about telemetry assumes it can patch your imports at startup. In a bundled application that does not work, and it fails silently, so it is worth knowing before you choose anything:

| | ESM app | CJS app | bundled (esbuild / webpack / ncc / Next.js) | |---|---|---|---| | Module hooks (--import, --require) | works | works | silently captures nothing | | The Vercel AI SDK seam | works | works | works | | The explicit API | works | works | works |

A bundler inlines your dependencies into your own file, so there is no import '@anthropic-ai/sdk' left for a hook to intercept — the module boundary the technique needs no longer exists in the artifact that runs. It is not degraded, it is absent, and worse: a bundled app carries its own inlined copy of the SDK, so events would belong to a different session from the app's own runs.

Next.js bundles server code by default. That makes the deployment shape most likely to be running an agent in Node exactly the shape module hooks cannot serve.

So this SDK is built on the two columns that survive. It ships no module hooks, and it does not offer zero-code capture in Node. When nothing is capturing, it says so — instrumentation() returns "none" and every pipeline_health record carries it. An empty dashboard that looks plausible is worse than an error.

The measurements behind all of this are in SCOPE.md, and reproducible.


Three levels, pick one

Level 1 — declare who you are. One call, at startup:

import * as nexus from '@swfte/nexus-sdk';

nexus.init({ service: 'checkout', env: 'prod', version: '2026.8.1' });

Datadog's unified tagging. service / env / version land on every event as join keys. There is no user identity anywhere in this SDK: a service does not need one, and inventing one is how a telemetry pipeline becomes a compliance problem.

Level 2 — capture your model calls. If you use the Vercel AI SDK, one more call:

import { registerTelemetry } from 'ai';
import { telemetry } from '@swfte/nexus-sdk/ai';

registerTelemetry(telemetry());

Token counts with the cache-read/cache-write split, model and provider ids, tool names and durations, and exact cost from the cache split — with no changes at your call sites, and it survives bundling. Note the direction: you import ai, not us. This package has no dependency on it and no version range to conflict with yours.

Level 3 — say what the agent did.

await nexus.withAgent('refund-request', { goalClass: 'transaction' }, async (run) => {
  const act = run.action('db.write', 'refunds');
  act.effect({ rows: 1, amountCents: 4200 }).end();

  run.usage({ model: 'claude-opus-4', inputTokens: 1800, outputTokens: 240 });
  run.outcome('success', { verified: true, verifiedBy: 'ledger_balance' });
});

A run is a unit of agent work with an outcome. An action is something with an effect on the world — the thing enforcement gates. The distinction is not stylistic: what an agent did (behavior_trace) and what a model said it did (rationalisation) are different epistemic classes, stamped as such, and only one is admissible as evidence.

Prefer withAgent to agent() in async code. The current run propagates across await correctly there and only there (AsyncLocalStorage); the object returned by agent() cannot do that, and action() several frames down will not find it.

All three levels compose.


Guarantees

A telemetry SDK must never be the reason a request fails. Concretely, each of these is a test:

  • Every public entry point is wrapped in a guard that contains exceptions and returns a safe default. A guarded failure hands back an inert proxy rather than undefined, so host code written against a Run keeps running instead of failing two lines later on cannot read property 'outcome' of undefined.
  • The calling thread never performs I/O. flush() is asynchronous because there is no honest synchronous form to offer.
  • The queue is bounded and drops oldest, because the newest events describe the incident. Every drop is counted and reported in counters(). Silence about dropped data is a bug.
  • Shutdown flushes to a deadline. A hung collector must not hang your container — asserted by a test with a server that accepts the request and never answers.
  • SIGKILL loses the in-flight buffer. That is not fixable and we do not pretend otherwise.

The kill switch is real

NEXUS_ENABLED=0

No timers, no sink opened, no exit handler, no socket. NEXUS_ENABLED=0 beats init({ enabled: true }) — an operator disabling telemetry from outside the process has to beat what the application says, or it is not a kill switch.


Configuration

Explicit arguments win over environment variables, which win over defaults. The one exception is NEXUS_ENABLED=0, which wins over everything.

| Variable | Default | | |---|---|---| | NEXUS_ENABLED | 1 | 0 disables entirely | | NEXUS_SERVICE / NEXUS_ENV / NEXUS_VERSION | unknown | unified tagging; the service name also falls back to OTEL_SERVICE_NAME / DD_SERVICE / K_SERVICE / AWS_LAMBDA_FUNCTION_NAME | | NEXUS_COLLECTOR_URL | http://127.0.0.1:8791 | full URL; none for no collector | | NEXUS_COLLECTOR_HOST / _PORT / _SCHEME | — | sidecar mode: not hardcoded loopback, IPv6 hosts bracketed | | NEXUS_API_KEY | — | bearer token; see below | | NEXUS_TIER | metadata_only | metadata_only / hashed / full | | NEXUS_CONFIG_FILE | — | JSON file, read only when named; no implicit search path | | NEXUS_HEALTH_INTERVAL_MS | 60000 | service_health window; 0 disables | | NEXUS_SPILL_DIR | — | durable overflow for batches the collector permanently refused | | NEXUS_QUEUE_CAPACITY | 10000 | events, then drop-oldest | | NEXUS_BATCH_SIZE | 500 | | | NEXUS_FLUSH_INTERVAL_MS / NEXUS_FLUSH_DEADLINE_MS | 2000 / 2000 | | | NEXUS_HTTP_TIMEOUT_MS | 2000 | | | NEXUS_SDK_SINK | — | append events to a file as NDJSON, for debugging | | NEXUS_APPLICATION / NEXUS_REPO / NEXUS_COMMIT / NEXUS_BRANCH / NEXUS_DEPLOYMENT_ID | auto | provenance; auto-detected from Vercel and GitHub Actions | | NEXUS_DEBUG | — | 1 writes contained errors to stderr |

Events are POSTed to {collector}/v1/events as {"events": [...]}.

The bearer token is withheld from plain HTTP to a non-loopback host. https: anywhere is fine, and http: to loopback is fine; anything else and the token is left off the request rather than sent in cleartext across a network. The request still goes, so you get a diagnosable 401 instead of silence.


Privacy tiers

Every free-text field carries its shape at every tier — <name>_chars and <name>_fingerprint are content-free, so "the same error as yesterday" stays answerable and a repeated target stays groupable without the text leaving the process. What changes per tier is the content beside it.

| Tier | On the wire | |---|---| | metadata_only (default) | Shape only. The content key is absent — not blank, not masked. | | hashed | plus <name>_preview: redacted text, truncated. | | full | plus <name>: redacted text, at the field's full limit. |

A tier is a decision about content, never a waiver on credentials. full is not "unscrubbed", it is "scrubbed, at greater length": bearer tokens, API keys in nineteen vendor shapes, PEM blocks, Luhn-valid card numbers, mod-97-valid IBANs, national IDs, phone numbers and email addresses go regardless of tier. Structured values are scrubbed too, which is the case a free-text redactor misses — effect({ headers: { Authorization: 'Bearer …' } }) is masked by key name, and a number under a key that is not a recognised measurement is masked below full.

The redactor is a port of the Python SDK's, and the two are checked against each other over a shared corpus (node scripts/redact-parity.mjs, 178 cases). One tier setting means the same thing in a Node service and a Python one.

It is not magic, and the limits are worth knowing. It is a bounded set of anchored patterns plus key-name matching, not an entropy scan, because it runs on your request path. Jane Doe is not recognisable by any regular expression, so a field naming a person still contains their name at full. The default tier is the primary control; redaction is the second line.

The AI SDK bridge emits tool inputs and outputs on this ladder, and no model text. Tool payloads are structured, which is exactly what the scrubber is for. Model text reaches the ledger through @swfte/nexus-sdk/otel instead, as model_response and model_thinking, where it is re-gated by your tier on ingest because upstream redaction is not trusted. The prompt itself is emitted by neither path: there is no prompt event in the contract for a second producer to write against.


OpenTelemetry

If you already run GenAI instrumentation — OpenInference, OpenLLMetry/OpenLIT, or anything emitting the OTel GenAI semantic conventions — the bridge turns its spans into nexus events:

import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { spanProcessor } from '@swfte/nexus-sdk/otel';

new NodeTracerProvider({ spanProcessors: [spanProcessor()] }).register();

Same direction as the AI SDK bridge: you import OpenTelemetry, not us. spanProcessor() returns a plain object with the four methods the interface requires, so this package still has no dependency and no version range to conflict with yours.

Three things it does deliberately:

  • An unclassified span is dropped, not guessed at. A GenAI span whose kind is not modelled is counted under bridge_unclassified and emitted nowhere — never defaulted into behavior_trace, which is the class the ledger treats as evidence. A bridge that guesses produces confidently wrong events, which is worse than none. stats().unclassified is the number to alert on.
  • Model text is re-gated by your tier on ingest. Every instrumentation in this space has its own content-capture switch and its own idea of what a secret looks like; some have none. Whether text leaves your process is your decision, not theirs.
  • A tool call's arguments contribute a shape, not their values. {amount: 4200, note: '…'} becomes field names and types with no value at any depth, because tool_action.target is an identifier field and a payload must not be standing in front of a gate that assumes one.

Enforcement

Every agent-observability tool observes. This one can also refuse:

import * as policy from '@swfte/nexus-sdk/policy';

policy.install(signedEnvelope);        // Ed25519, verified before a rule is read

run.action('db.write', 'prod-orders'); // throws Denied if a rule says no

Importing that module gates run.action(...). It is the only gated call site, and it is the only one that can be: enforcement needs a decision taken before the effect, which needs a call site you own. Tool calls made through the AI SDK seam cannot be gated in-process — see below.

A policy is a signed envelope from your control plane. A rule refuses for real only if it carries "enforce": true; everything else evaluates, records, and lets the call through, so a shadow deployment tells you what would have been blocked without blocking it.

The failure semantics are the part worth reading twice, because they decide what happens on your worst day:

| Situation | What happens | |---|---| | No policy installed, or one that will not verify | Allow, and raise an integrity alert | | Policy verified but stale | enforce-marked rules keep enforcing; unmarked ones become advice | | Evaluation exceeds its latency budget | Allow, and record the overrun | | A bug inside policy evaluation | Allow, and raise an integrity alert | | A rule needs a human, and there is no approver | The rule's own on_timeout: deny if enforce-marked, allow if not |

Denying because we could not reach something is never a default. failClosed is the single opt-in exception, and it says so in the reason string on every decision it produces — a service that starts refusing should be able to tell you why without a support ticket.

Denied is the only exception this SDK will ever put in your stack trace, and you opted into it twice: the rule carried enforce: true and the call site did not decline. With no policy installed — the default — nothing here can throw.


What this does not do

Named, because a gap that is named is fine and a gap that is silent is not. PARITY.md has the full list against the Python SDK.

  • No module-hook auto-instrumentation. No --import @swfte/nexus-sdk/register. It cannot serve bundled applications, and shipping a half-working version of it produces exactly the empty dashboard this SDK is supposed to make impossible. The spike's implementation is still in the repository under src/hooks/, excluded from the published package.
  • No provider adapters. No direct instrumentation of @anthropic-ai/sdk or openai. If you call those libraries directly rather than through the AI SDK, use run.usage(...).
  • No enforcement through the AI SDK. The seam is observe-only by its type signature — the callback returns void, there is no channel to refuse on, and a throw from onToolExecutionStart neither stops the tool nor reaches your code. Measured, not assumed. In Node, enforcement means the explicit API, where you own the call site.
  • Enforcement reaches exactly one call site. @swfte/nexus-sdk/policy gates run.action(...), and nothing else. An application using the AI SDK seam cannot be gated in-process at all, now or later: that seam's callbacks return void, so there is no channel to refuse on, and a throw from onToolExecutionStart neither stops the tool nor reaches your code. Enforcement in Node means the explicit API where you own the call site, a gateway in front of your provider, or nothing. Worth knowing before you design around it.
  • No human-approval channel. A require_approval rule resolves to its own on_timeout — deny if the rule is enforce-marked, allow if not — rather than waiting for a person. Node cannot park its event loop, and a synchronous wait would freeze the process.
  • The OTel bridge does not deduplicate retries. @swfte/nexus-sdk/otel reads spans from whatever GenAI instrumentation you run and emits token_usage, tool_action, model_response and model_thinking. What it does not port is Python's multi-span logical-call join, so against an instrumentation that emits one span per HTTP attempt, token counts sum across retries instead of being deduplicated.
  • No nexus-run launcher. There is no Node equivalent of PYTHONPATH-based bootstrapping that survives bundling.

Why no dependencies

The core declares nothing. Not an HTTP client, not a UUID library.

This is a product decision, not asceticism. An observability SDK is installed into a dependency graph somebody else has already resolved, and every requirement we add is a version range that can conflict with theirs — a conflict at install time is where the adoption conversation ends, before any of this code runs. scripts/npm-guard.cjs and a test both fail the build if dependencies, peerDependencies or optionalDependencies stops being empty.

The /ai entry point is the shape this forces and it is a better shape anyway: we never import ai, you hand us registerTelemetry.


Development

npm test          # 339 tests, node --test, no third-party framework
npm run typecheck # tsc --strict against the public declarations
npm run guard     # what `npm publish` would ship, checked against an allowlist

Two checks need a network and are not part of npm test:

npm run probe:ai                # the bridge against the real `ai` package, end to end
npm run parity                  # redactor, rate card and policy envelope, diffed against Python
bash scripts/consumer-check.sh  # install the tarball; run strict tsc against it as a consumer

test/contract.test.mjs validates every emitted event against contract/events.v1.json from nexus-devtools. It looks for it at ../nexus-devtools/contract/events.v1.json, overridable with NEXUS_CONTRACT, and skips loudly rather than silently when it is absent.

Layout

src/core.cjs        the SDK: config, context, queue, transport, run/action, contract
src/index.js  .cjs  ESM and CJS entries over the one core (dual-package hazard)
src/ai.cjs    .js   the Vercel AI SDK bridge
src/provenance.cjs  where a commit and a repo came from, and how confident we are
src/hooks/          the spike's module hooks — NOT shipped, kept for the record
fixtures/           stand-in provider, probe applications, the real-library probes
bundle/             esbuild build + the start-up measurement harness

Nexus, beyond this package

This SDK is one attach point of three. All three write the same events to the same ledger, so a run in production and a run on a laptop are the same shape in the same tables.

| | Install | What it attaches to | |---|---|---| | Terminal wrapper | npm i -g @swfte/nexus · pip install swfte-nexus | coding agents in a developer's terminal — Claude Code, Codex | | Node SDK — this package | npm i @swfte/nexus-sdk | your own Node services | | Python SDK | pip install swfte-nexus-sdk | your own Python services |

Licence

Apache-2.0. See LICENSE and NOTICE.

Built by Swfte AI.