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

@letsping/sdk

v0.3.2

Published

Agent trust layer: behavioral firewall, HITL, and Cryo-Sleep state for AI agents. Works with LangGraph, Vercel AI SDK, and custom runners.

Downloads

737

Readme

@letsping/sdk

The official Node.js/TypeScript SDK for LetsPing.

LetsPing is a behavioral firewall and Human-in-the-Loop (HITL) infrastructure layer for Agentic AI. It provides mathematically secure state-parking (Cryo-Sleep) and execution governance for autonomous agents built on frameworks like LangGraph, Vercel AI SDK, and custom architectures.

What you get with this SDK: One client that connects your agent to the full LetsPing stack: a hosted dashboard for triage and approvals, a Markov-based behavioral firewall that learns your graph and intercepts anomalies, Cryo-Sleep state parking so long-running flows survive serverless limits, and audit trails for compliance. Use LangGraph (or any runtime) for the graph; use LetsPing for the human layer and guardrails.

Features

  • The Behavioral Shield: Silently profiles your agent's execution paths via Markov Chains. Automatically intercepts 0-probability reasoning anomalies (hallucinations/prompt injections).
  • Cryo-Sleep State Parking: Pauses execution and securely uploads massive agent states directly to storage using Signed URLs, entirely bypassing serverless timeouts and webhook payload limits.
  • Smart-Accept Drift Adaptation: Approval decisions mathematically alter the baseline. Old unused reasoning paths decay automatically via Exponential Moving Average (EMA).
  • Agent Identity & Escrow Helpers: Optional HMAC-based helpers (signAgentCall, verifyEscrow, chainHandoff) for cryptographically linking agent calls and handoffs to LetsPing requests.

Requirements

  • Compatibility: Node.js 18+. TypeScript 5+ recommended.
  • (Optional) @langchain/langgraph and @langchain/core for state persistence

Installation

npm install @letsping/sdk

Usage

Minimal drop-in example

The fastest way to see your first approval in the dashboard:

import { LetsPing } from "@letsping/sdk";

const apiKey = process.env.LETSPING_API_KEY;
if (!apiKey) throw new Error("Missing LETSPING_API_KEY env var.");

const lp = new LetsPing(apiKey);

const decision = await lp.ask({
  service: "billing-agent",
  action: "refund_user",
  payload: { user_id: "u_123", amount: 100 },
});

Every example in this README follows the same pattern: either pass the key explicitly or rely on LETSPING_API_KEY via env.

Blocking Request (ask)

Execution suspends until the request is approved, rejected, or times out.

import { LetsPing } from "@letsping/sdk";

const lp = new LetsPing(process.env.LETSPING_API_KEY!);

async function processRefund(userId: string, amount: number) {
  try {
    const decision = await lp.ask({
      service: "billing-agent",
      action: "refund_user",
      priority: "high",
      payload: { userId, amount },

      // Optional: JSON Schema to render an editable form in the dashboard
      // (If using Zod: convert via zodToJsonSchema(mySchema))
      schema: {
        type: "object",
        properties: {
          amount: { type: "number", maximum: 5000 }
        },
        required: ["amount"]
      },

      // Optional override (default: 24 hours)
      timeoutMs: 30 * 60 * 1000, // 30 minutes
    });

    if (decision.status === "APPROVED") {
      // Prefer patched_payload if human edited values
      const data = decision.patched_payload ?? decision.payload;
      await stripe.refunds.create({
        charge: data.userId,
        amount: Math.round(data.amount * 100),
      });
      console.log("Refund executed");
    } else {
      console.log(`Refund ${decision.status.toLowerCase()} by operator`);
    }
  } catch (error) {
    console.error("Approval failed or timed out:", error);
  }
}

Non-Blocking Request (defer)

Queues the request immediately and returns; ideal for serverless or event-driven flows.

const { id } = await lp.defer({
  service: "notification-agent",
  action: "send_email",
  payload: {
    to: "[email protected]",
    subject: "Your invoice is ready",
    amount: 249.99
  },
  priority: "medium",
  // Optional: Pass the full LangGraph/Vercel state dict.
  // It will be encrypted client-side and uploaded directly to S3.
  state_snapshot: agentState
});

console.log(`Approval request queued → ${id}`);

Webhook Rehydration (Next.js Example)

When you pass state_snapshot to ask / defer, the SDK:

  • Encrypts the snapshot with either LETSPING_ENCRYPTION_KEY or a one‑time DEK.
  • Uploads it directly to your storage bucket using a signed URL.
  • Includes a state_download_url (and DEK) in subsequent webhooks.

You can use the built‑in webhookHandler to validate and hydrate webhooks in a Next.js App Router route:

// Example Next.js App Router route
import { NextRequest, NextResponse } from "next/server";
import { LetsPing } from "@letsping/sdk";

const lp = new LetsPing();
const WEBHOOK_SECRET = process.env.LETSPING_WEBHOOK_SECRET!;

export async function POST(req: NextRequest) {
  const rawBody = await req.text();
  const signature = req.headers.get("x-letsping-signature") || "";

  try {
    const { id, event, data, state_snapshot } = await lp.webhookHandler(
      rawBody,
      signature,
      WEBHOOK_SECRET
    );

    // At this point:
    // - `data` contains the decision payload (status, payload, patched_payload, metadata, etc.)
    // - `state_snapshot` contains your decrypted agent state, if Cryo-Sleep was used.

    await handleDecision({ id, event, data, state_snapshot });

    return NextResponse.json({ ok: true });
  } catch (err: any) {
    console.error("LetsPing webhook error:", err);
    return NextResponse.json({ error: "invalid webhook" }, { status: 400 });
  }
}

async function handleDecision(args: {
  id: string;
  event: string;
  data: any;
  state_snapshot?: Record<string, any>;
}) {
  // Example: resume a workflow run or LangGraph thread using `state_snapshot`
}

This pattern works similarly for Express/Fastify — call lp.webhookHandler(rawBody, signature, secret), then resume your framework using the provided state_snapshot.

LangGraph Integration (Persisted State)

LetsPing provides a LetsPingCheckpointer for LangGraph JS/TS under @letsping/sdk/integrations/langgraph.
In v0.2 this checkpointer persists checkpoints remotely via the LetsPing control plane — encrypted alongside your existing Cryo‑Sleep state in Supabase Storage. Threads can survive process restarts without you wiring your own database.

import { StateGraph } from "@langchain/langgraph";
import { LetsPing } from "@letsping/sdk";
import { LetsPingCheckpointer } from "@letsping/sdk/integrations/langgraph";

const lp = new LetsPing(process.env.LETSPING_API_KEY!);
const checkpointer = new LetsPingCheckpointer(lp);

const builder = new StateGraph<any /* your state type */>({});
const graph = builder.compile({ checkpointer });

Auto‑resuming a thread after approval (webhook + checkpointer)

Because checkpoints are stored remotely, you can resume a LangGraph thread from any worker once a human clicks Approve. A minimal Next.js webhook + auto‑resume flow looks like:

// Example Next.js App Router route for LangGraph auto-resume
import { NextRequest, NextResponse } from "next/server";
import { LetsPing } from "@letsping/sdk";
import { StateGraph } from "@langchain/langgraph";
import { LetsPingCheckpointer } from "@letsping/sdk/integrations/langgraph";
import { graphBuilder } from "@/lib/langgraph"; // your app's graph definition

const lp = new LetsPing(process.env.LETSPING_API_KEY!);
const checkpointer = new LetsPingCheckpointer(lp);
const graph = graphBuilder.compile({ checkpointer });

export async function POST(req: NextRequest) {
  const raw = await req.text();
  const sig = req.headers.get("x-letsping-signature") || "";

  const event = await lp.webhookHandler(raw, sig, process.env.LETSPING_WEBHOOK_SECRET!);
  const { data, state_snapshot } = event;

  // You decide how to encode the thread id into your state snapshot.
  const threadId = state_snapshot?.thread_id as string | undefined;
  if (!threadId) return NextResponse.json({ ok: false, error: "missing_thread_id" }, { status: 400 });

  // Resume the graph from the latest remote checkpoint.
  await graph.invoke(state_snapshot.input, {
    configurable: { thread_id: threadId },
  });

  return NextResponse.json({ ok: true });
}

In your agent runner, you simply include thread_id and state_snapshot when you first call LetsPing from inside a LangGraph node. The checkpointer and webhook then keep the thread resumable across restarts. If the human edited the payload in the dashboard, data.patched_payload (or data.payload) is available in the webhook payload — use your framework’s normal state-update or channel overwrite semantics to inject the approved payload into the resumed graph so the run sees the correct values.

API Reference

new LetsPing(apiKey, options?)

  • apiKey (string) — required — Service Role or Project API key from LetsPing dashboard
  • options.baseUrl (string) — optional — Override endpoint (self-hosted / staging)

lp.ask(options): Promise<Decision>

Blocks until resolved (approve / reject / timeout).

| Property | Type | Description | |--------------|---------------------------------|-----------------------------------------------------------------------------| | service | string | Service / module identifier (e.g. "billing", "compliance") | | action | string | Action name (e.g. "refund", "transfer_funds") | | payload | Record<string, any> | Context passed to human operator (and returned in Decision) | | priority | "low" \| "medium" \| "high" \| "critical" | Routing priority in dashboard | | schema | object | JSON Schema (draft 07) — generates editable form in dashboard | | timeoutMs | number | Max wait time in milliseconds (default: 86_400_000 ms = 24 hours) |

lp.defer(options): Promise<{ id: string }>

Fire-and-forget: queues request and returns request ID immediately. Same options shape as ask.

Decision Type

interface Decision {
  status: "APPROVED" | "REJECTED" | "APPROVED_WITH_MODIFICATIONS";
  payload: Record<string, any>;          // Original payload sent by agent
  patched_payload?: Record<string, any>; // Human-edited values (if modified)
  diff_summary?: any;                    // Field-level diff between payload and patched_payload
  metadata?: {
    actor_id: string;                    // ID/email of the approving/rejecting human
    resolved_at: string;                 // ISO 8601 timestamp
    method?: string;                     // Optional resolution method (e.g. "dashboard")
  };
}

Structured errors: All API and network errors are thrown as LetsPingError with optional status, code (e.g. LETSPING_402_QUOTA, LETSPING_429_RATE_LIMIT, LETSPING_TIMEOUT), and documentationUrl so you can branch or log and link users to the right doc. See https://letsping.co/docs#errors.

Optional retries: Pass retry: { maxAttempts: 3, initialDelayMs: 1000, maxDelayMs: 10000 } in the constructor to enable exponential backoff for ingest and status calls (429 and 5xx are retried).

Status helper: Use lp.getRequestStatus(id) after defer() to poll for request status without calling the raw HTTP API. See https://letsping.co/docs#requests.

For full documentation, request schema examples, and dashboard integration see:
https://letsping.co/docs#sdk

Agent-to-Agent Escrow (optional)

For multi-agent systems that want cryptographic guarantees around handoffs, the SDK exposes:

  • createAgentWorkspace(options?) to do request-token → redeem → register in one call. Returns { project_id, api_key, ingest_url, agent_id, agent_secret } so the agent gets its own workspace without a human. Rate limits apply; see agent quickstart.
  • ingestWithAgentSignature(agentId, agentSecret, payload, options) to POST a signed ingest (no hand-rolled HMAC or curl). Options: { projectId, ingestUrl, apiKey }.
  • signAgentCall(agentId, secret, call) to attach agent_id and agent_signature to /ingest calls.
  • signIngestBody(agentId, secret, body) to take an existing ingest body ({ project_id, service, action, payload }) and return it with agent_id and agent_signature attached.
  • verifyEscrow(event, secret) to validate LetsPing escrow webhooks.
  • chainHandoff(previous, nextData, secret) to safely construct downstream handoffs tied to the original request id.

See the one-page spec at /docs/agent-escrow-spec in the LetsPing web app for the exact wire format and interoperability rules.

Deploy agents with confidence.

2-Minute Demo (Node/TypeScript)

You can feel the full LetsPing loop (intercept → approve → resume) in under 2 minutes.

// demo.ts
import { LetsPing } from "@letsping/sdk";

async function main() {
  const apiKey = process.env.LETSPING_API_KEY;
  if (!apiKey) {
    console.error("Missing LETSPING_API_KEY env var.");
    process.exit(1);
  }

  const lp = new LetsPing(apiKey);

  console.log("Sending demo approval request to LetsPing…");
  const decision = await lp.ask({
    service: "demo-agent",
    action: "transfer_funds",
    priority: "high",
    payload: {
      amount: 500,
      currency: "USD",
      recipient: "acct_demo_123",
    },
  });

  if (decision.status === "REJECTED") {
    console.log("Demo request REJECTED by human. No action taken.");
  } else if (decision.status === "APPROVED_WITH_MODIFICATIONS") {
    console.log("APPROVED WITH MODIFICATIONS:");
    console.dir(decision.diff_summary, { depth: null });
  } else {
    console.log("APPROVED with original payload.");
  }
}

main().catch((err) => {
  console.error("Demo failed:", err);
  process.exit(1);
});

Run:

export LETSPING_API_KEY="lp_live_..."
node demo.ts

Then open the LetsPing dashboard for your project, approve/reject the demo-agent / transfer_funds request, and watch the script resume.

If you’re using the local tunnel (npx @letsping/cli dev), you can also point the SDK at it during local development:

const lp = new LetsPing(process.env.LETSPING_API_KEY!, {
  baseUrl: "http://localhost:<port>/api",
});

All ask / defer calls made through that client will flow through your local tunnel into the LetsPing dashboard.


Compatibility: Node 18+, TypeScript 5+. Optional: @langchain/langgraph, @langchain/core for LangGraph integration.

License: MIT. Source: CordiaLabs/LetsPing (packages/sdk).