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

veragent

v0.6.1

Published

Vendor-neutral control plane client for AI agents — capture and govern agent activity. Node/TypeScript SDK.

Readme

Veragent Node SDK

The client library that makes Veragent a vendor-neutral control plane for agents built in JavaScript/TypeScript — the Node peer of the Python SDK, speaking the exact same wire contract so your backend treats both identically.

The core is dependency-free (Node built-ins only: fetch, AsyncLocalStorage). Requires Node 18+.

Install

npm install veragent

Quick start

import { Veragent } from "veragent";

const va = new Veragent({ agent: "support-agent" }); // reads VERAGENT_API_KEY

// Report any action. Non-blocking — buffers and returns immediately.
va.track("refund issued", {
  eventType: "tool_call",        // the governable surface
  tool: "issue_refund",
  inputs: { orderId: "A-1001", amount: 49 },
});

Group a run

Nested track() calls inside run() automatically inherit one correlation id (via AsyncLocalStorage):

await va.run("nightly-job", async () => {
  va.track("step one", { eventType: "tool_call", tool: "fetch" });
  va.track("step two", { eventType: "tool_call", tool: "write" });
});

Wrap a function

const issueRefund = va.trackAction("issue_refund", async (orderId: string, amount: number) => {
  // ... your logic ...
  return { ok: true };
});

Authorize — ask permission before acting

va.authorize(...) is the pre-action decision point. It calls the live /api/authorize endpoint with the same agent name + API key you configured the client with — you never re-pass credentials. It stays inert (reporting-only, allowed: true) until you turn enforcement on, so it's safe to wire in first:

const va = new Veragent({ agent: "crypto-paper-trader", enforcementEnabled: true });

Blocking (the simple case)

const decision = await va.authorize("polymarket.place_order", {
  context: { market: "BTC-100k", size: 250 },
});

if (decision.allowed) {
  placeOrder();                 // ✅ authorized
} else {
  // MUST handle the not-allowed branch — these mean different things:
  if (decision.status === "denied") {
    console.warn(`blocked by policy: ${decision.reason}`);
  } else if (decision.status === "timed_out") {
    console.warn("no human answered in time; server fail mode applied");
  } else if (decision.status === "error") {
    console.error(`couldn't reach Veragent: ${decision.reason}`);
  }
  skipOrRollback();
}

If the action is auto-decided, the answer comes back on that first call — one round trip, no polling. If it escalates to a human, authorize() blocks and polls until the decision is terminal or timeoutSeconds (default 300, server clamps 10–3600) elapses.

Async (high-throughput loops)

Don't block a hot loop on a human. Fire the request, do other work, poll later:

const decision = await va.authorize("refund.issue", {
  context: { order: "A-1001" },
  wait: "async",
}); // returns immediately — terminal OR status === "pending"

// ...later, poll yourself:
const latest = await va.getDecision(decision.decisionId);
if (latest.status === "pending") {
  // still waiting on a human; check again later
} else if (latest.allowed) {
  issueRefund();
}

The decision object

interface Decision {
  allowed: boolean;          // the one thing you must check
  status: string;            // "allowed" | "denied" | "timed_out" | "error" | "pending"
  decisionId: string;
  reason: string;
  decidedBy?: string | null;
  resolvedAt?: string | null;
}

Pass { raiseOnDeny: true } to throw PolicyDenied on a terminal not-allowed decision instead of returning it.

Treat authorize() as fallible. It is a network call. A client-side inability to reach Veragent returns status: "error", allowed: false (fail-safe — never silently "allowed"), which is not the same as a policy denied. Distinguish denied (a real no) from timed_out (nobody answered → the server's fail mode decided) from error (couldn't get an answer at all). Prefer wait: "async" for high-throughput loops, and keep privileged/irreversible actions late and rollback-able so a denial or error is cheap to honor. Unlike track() (which drops failures silently), authorize failures are legible — surfaced in the returned Decision.

Acting for a human: on_behalf_of

context.on_behalf_of is a reserved-by-convention context key naming the human principal the agent is acting for — "this agent acted on behalf of user X". context runs through the same redactor as inputs/outputs before it leaves the process (see Redaction-first), and is then stored verbatim on the decision record. Governance rules can condition on context.on_behalf_of today (any operator, e.g. escalate when an agent acts for no named principal).

const decision = await va.authorize("Refund €420 to customer 8821", {
  permission: "PAYMENT",
  context: { on_behalf_of: "[email protected]", amount: 420 },
});

Two rules of the road:

  • Opaque and yours. An email, an IdP subject id, any stable string — pick one form per organisation and keep it stable.
  • Attested by the caller, never verified by Veragent. The platform cannot resolve your identity provider; this is attribution evidence on the decision record, not authentication.

Emitting the same key in track() payloads is encouraged for consistency — but pass it in metadata, not inputs:

va.track("refund issued", { metadata: { on_behalf_of: "[email protected]" } });

metadata keys land at the top level of the event (metadata.on_behalf_of), which is where event-side rules and budgets read them. inputs is stored nested, at metadata.inputs.*, and is not reachable by key name — so a rule or budget keyed on a name you sent inside inputs will never match.

Instrument Vercel AI SDK tools

Wrap the tools you pass to generateText / streamText — every tool with an execute function is captured, with no changes to the tools themselves:

import { generateText } from "ai";
import { instrumentTools } from "veragent/vercel-ai";

const result = await generateText({
  model,
  tools: instrumentTools(myTools, va),                  // capture every tool call
  // tools: instrumentTools(myTools, va, { enforce: true }), // also gate them
  prompt: "...",
});

Tools without a callable execute (provider-executed or client-side tools) are returned uninstrumented: their calls are neither captured nor gated, including under enforce: true. Instrument the tools you need governed on the server side.

Tool execution is the governable surface, so calls are tracked as eventType: "tool_call". With enforce: true, each call is authorized first and a denied one is blocked before it runs. The adapter has no dependency on the ai package (it duck-types the tools object) and preserves your tools type.

Instrument MCP tool calls

Wrap an MCP Client so every tool call through it is captured (and optionally gated):

import { instrumentMcpClient } from "veragent/mcp";

instrumentMcpClient(client, va);                  // capture every tool call
// instrumentMcpClient(client, va, { enforce: true }); // also gate each call

MCP is the framework-agnostic tool layer, so instrumenting client.callTool captures the governable surface no matter what drives the client. With enforce: true, a denied call is blocked before it runs (returns an isError result). Dependency-free — it duck-types the client.

Instrument LangChain.js

instrument(va) returns a LangChain callback handler — pass it in the callbacks array:

import { instrument } from "veragent/langchain";

await chain.invoke(input, { callbacks: [instrument(va)] });

Every LLM call, tool call, and error in the run is captured; tool calls as eventType: "tool_call". Dependency-free (it's a plain callback-methods object, no @langchain/core import). Capture-only — callbacks fire alongside execution, not before it, so use authorize() or the MCP interceptor for enforcement.

Instrument the OpenAI Agents SDK

instrument(va) registers a Veragent tracing processor with @openai/agents, so every span the SDK emits is captured:

import { instrument } from "veragent/openai-agents";

instrument(va);                  // capture every span the Agents SDK emits
// ...then run agents as usual.

Spans map onto events by type: function → tool_call, generation/response → llm_call, agent → lifecycle, handoff/guardrail → decision, anything else → action. Registration is additive (addTraceProcessor), so existing processors keep running. Dependency-free — it duck-types the SDK's TracingProcessor/Span shapes (the package is only needed at runtime). Capture-only by design: the tracing API notifies on span end, after the action happened, so there is no pre-action hook to gate against — use authorize() or the MCP interceptor for enforcement. Every span mapping is wrapped so a capture error never throws into your agent run.

Failure semantics

What happens when things go wrong is a first-class part of the contract — read this before you enforce. There are three distinct layers, and they fail differently on purpose.

1. Enforcement is OFF by default — this is the deliberate fail-open default. A client constructed without enforcementEnabled: true never makes an authorization network call at all: authorize() returns { allowed: true, status: "allowed" } immediately (reporting-only), and the adapters (enforce defaults to false) run every tool call. Out of the box, nothing is ever blocked. This lets you wire authorize() through your codebase safely before you are ready to enforce. Until you opt in, there is no enforcement guarantee — by design.

2. With enforcement ON, an unreachable Veragent fails safe — your action does not get a "yes". When enforcementEnabled: true and the authorize() call cannot reach Veragent (network failure or client-side timeout), the SDK returns { allowed: false, status: "error" } — it never fabricates an approval. If you honor decision.allowed (and the enforce: true adapters do — they block the call), an outage means the guarded action does not proceed. At the SDK boundary an outage is therefore fail-closed: the agent never proceeds thinking it was authorized. Distinguish the three not-allowed statuses: denied (a real policy/human no), timed_out (an escalation nobody answered — see layer 3), and error (Veragent could not be reached at all).

3. Escalation timeouts are decided server-side by the matched policy's fail mode. When a policy escalates an action to a human and no one answers before the deadline, the outcome is governed by that policy's per-rule fail mode, configured in the dashboard — not by the SDK:

  • fail_closed — on timeout the request resolves denied. Use it for high-stakes, low-frequency actions (a wire transfer should not go through because an approver was at lunch).
  • fail_open — on timeout the request resolves allowed. Use it for high-frequency actions where blocking the agent does more harm than the occasional un-reviewed pass.

The SDK reports this back as status: "timed_out" with allowed reflecting the policy's fail mode.

Plan note. Enforcing escalate-to-human rules require the Starter plan or above. On Free, allow/deny enforcement and shadow mode work in full, but an escalate rule cannot be promoted to enforce — so timed_out and the fail-mode branch above only arise from Starter upward.

The enforce-mode default stays fail-open at the enforcement toggle (layer 1) and fail-safe at the network boundary (layer 2). Keep privileged or irreversible actions late and rollback-able so that honoring a denied or an error is cheap. Unlike track() (which drops failures silently so it can never slow your agent), authorize() failures are always legible — surfaced in the returned Decision, never swallowed.

Design guarantees

  • Safe. track() only buffers; all network I/O is async and off your code path. Instrumentation never blocks, slows, or throws into your code path. If Veragent is unreachable, events are dropped with a log line — your agent keeps running. (The ingest endpoint accepts one event per POST and rate-limits at 60 requests/min per key; maxBatch sets the flush trigger, not a multi-event request.)
  • Non-blocking. Events flush on an interval / size threshold and once on beforeExit. Call await va.close() for a deterministic final flush (e.g. before a short-lived process exits).
  • Redaction-first. Inputs/outputs run through a redactor before leaving the process (sensitive keys masked, long strings truncated). On by default; pass redact: false or a custom function to override.
  • Enforcement-ready. va.authorize(action, { context }) is the pre-action decision point, calling the live /api/authorize endpoint. It is safe to wire in today — while enforcementEnabled is off it returns allowed: true (reporting-only); turn it on to get real allow/deny verdicts. Unlike track(), authorize failures are legible, not silent: if Veragent can't be reached you get status: "error", allowed: false so the agent never proceeds thinking it was authorized. See Authorize.
  • Wire-compatible. Emits the same { agent, action, severity, metadata } envelope as the Python SDK, tagged sdk: "veragent-node/<package version>".

Configuration

new Veragent({
  apiKey,              // or env VERAGENT_API_KEY
  agent: "agent",
  endpoint,            // default https://www.veragent.io/api/ingest-event
  redact: true,        // true | false | (value) => value
  flushInterval: 2,    // seconds
  maxBatch: 50,
  maxQueue: 10000,
  timeout: 10,         // seconds
  enforcementEnabled: false,  // opt in to real allow/deny — see Failure semantics
});

Adapter roadmap

| Surface | Status | |---|---| | Core (track, run, trackAction, authorize) | ✅ shipped | | Vercel AI SDK (instrumentTools) | ✅ shipped | | LangChain.js (instrument callback) | ✅ shipped | | MCP (TypeScript) interceptor (instrumentMcpClient) | ✅ shipped | | OpenAI Agents SDK (tracing processor) | ✅ shipped |

Versioning & stability

This SDK is pre-1.0 (0.x), and here is exactly what that means. Within 0.x we keep the surfaces you build against stable: the Decision shape (allowed / status / decisionId / reason and the five status values), the constructor options documented above, the adapter entry points, and the wire envelope. Changes are additive — new options, new fields, new adapters. If we ever have to break one of those, the minor version jumps with a loud changelog entry; nothing breaks in a patch release.

The platform side of the contract is versioned independently and published. The failure semantics above and the decision statuses are stated at veragent.io/trust, under When the SDK cannot reach us. The server's API stability posture — additive-only within v1, the error envelope, the frozen-semantics inventory — is at veragent.io/docs/api, with the machine-readable contract served at /api/v1/openapi.yaml and rendered at /docs/api/reference. Those cover the admin plane; this SDK speaks the agent plane (/api/ingest-event and /api/authorize).

The npm and PyPI packages version independentlyveragent on npm and veragent on PyPI move at their own pace and their version numbers are not meant to match. A lower number on one registry is not staleness; both speak the same wire contract, and each README states its own guarantees.

Changelog

0.6.1 (2026-07-22)

Accuracy release from a pre-launch fact check of the published package. No API changes.

  • Fixed a defect that could kill your process. instrument() for OpenAI Agents registered its trace processor without catching failure; when @openai/agents was not resolvable the resulting unhandled rejection terminated the host under Node's default --unhandled-rejections=throw. It now reports and stays inert — which is what makes "never throws into your code path" true.
  • Corrected the on_behalf_of recipe. The old text said to send it in inputs; inputs is stored nested at metadata.inputs.* and is not reachable by key name, so rules and budgets keyed on it never matched. Use metadata — it lands top-level.
  • Corrected the platform-docs pointer. The failure semantics and decision statuses are documented at veragent.io/trust; the previous link pointed at the Management API stability page, which documents neither.
  • Removed "batched". Ingest accepts one event per POST and rate-limits at 60/min per key; maxBatch is a flush trigger, not a multi-event request.
  • Corrected "context passes through verbatim". context is redacted client-side like inputs/outputs, then stored verbatim.
  • Withdrew "zero added latency" — unmeasured, so unclaimed.
  • Stated the plan requirement: enforcing escalate-to-human needs Starter or above.
  • Documented a real gap: Vercel AI tools without execute are neither captured nor gated, including under enforce: true.

0.6.0 (2026-07-22)

  • Removed the failClosed constructor option. It was decorative from the day it shipped — assigned and never read; no fail-open path ever existed behind it. Behavior is unchanged: with enforcement on, an unreachable Veragent has always returned { allowed: false, status: "error" }. If you passed failClosed, delete the line — TypeScript will point at it. (This is the loud minor-version break the stability statement above describes.)
  • New documentation: the three-layer Failure semantics contract, on_behalf_of attribution, and this Versioning & stability statement.
  • The wire tag sdk: "veragent-node/<version>" now tracks the package version — it had been stuck at 0.2.0 since 0.2.0.