JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 23
  • Score
    100M100P100Q58210F
  • License MIT

LetsPing Framework Adapters for Vercel AI SDK, LangChain, and Cloudflare Agents

Package Exports

  • @letsping/adapters/approval
  • @letsping/adapters/cloudflare
  • @letsping/adapters/cloudflare/mcp
  • @letsping/adapters/cloudflare/observability
  • @letsping/adapters/langchain
  • @letsping/adapters/vercel

Readme

@letsping/adapters

Adapters that wrap existing AI agent frameworks so tool calls go through LetsPing for approval and audit.

Pick your framework — one canonical example per stack. Each runs with only LETSPING_API_KEY; clone, set key, run.

Stack Example (clone-and-run)
LangGraph + Next.js examples/langgraph-nextjs
Vercel AI SDK examples/vercel-ai-tools
Python + FastAPI examples/python-fastapi
Cloudflare Agents See Cloudflare adapter below

This package provides typed wrappers around @letsping/sdk that integrate with:

  • Vercel AI SDK (as CoreTool)
  • LangChain / LangGraph (as DynamicStructuredTool)
  • Cloudflare Agents (Durable Objects) with keepAliveWhile and Tail Worker observability

When a model invokes one of these tools, execution pauses until the request is approved or rejected in the LetsPing dashboard.

Installation

npm install @letsping/adapters @letsping/sdk zod

When you should not use this

  • You only need a raw HTTP client. These adapters are for frameworks that already support tool plugins.
  • You want to stream full conversation logs or prompts. The adapters focus on tool calls; they do not turn LetsPing into a general logging layer.
  • You are not using LangGraph, Vercel AI SDK, LangChain, or Cloudflare Agents. In that case call @letsping/sdk directly instead.

One function for all frameworks

Export: @letsping/adapters/approval

If you want one opinionated entry point that returns the right tool type for your framework, use createApprovalTool.

import { createApprovalTool } from "@letsping/adapters/approval";
import { z } from "zod";

const tool = createApprovalTool({
  kind: "vercel",
  name: "delete_account",
  description: "Delete a user account. Requires human approval.",
  schema: z.object({ user_id: z.string() }),
  apiKey: process.env.LETSPING_API_KEY!
});

Vercel AI SDK adapter

Export: @letsping/adapters/vercel

import { createApprovalTool } from "@letsping/adapters/approval";
import { z } from "zod";

const deleteAccountTool = createApprovalTool({
  kind: "vercel",
  name: "delete_account",
  description: "Dangerous: permanently delete a user account.",
  apiKey: process.env.LETSPING_API_KEY!,
  schema: z.object({ user_id: z.string().describe("User id to delete") }),
});

const tools = {
  delete_account: deleteAccountTool,
};

The tool automatically suspends model generation until a decision is made in the LetsPing dashboard.

LangChain and LangGraph adapter

Export: @letsping/adapters/langchain

import { createLetsPingTool } from "@letsping/adapters/langchain";
import { z } from "zod";

const approvalTool = createLetsPingTool({
  name: "sensitive_action",
  description: "Request human permission before proceeding with this operation.",
  apiKey: process.env.LETSPING_API_KEY!,
  schema: z.object({
    reason: z.string().min(10).describe("Explain why this step is required"),
    estimated_impact: z.enum(["low", "medium", "high"]).default("medium"),
  }),
});

// Example usage in LangGraph or classic LangChain
const agent = new AgentExecutor({
  tools: [approvalTool /*, other tools */],
  llm,
  // ...
});

Tool progress (LangGraph streaming)
When using LangGraph with stream mode "tools", the adapter yields a progress event as soon as the request is queued: status: "intercepted_by_firewall", reason, triage_url, and request_id. Your client can read this from stream.toolProgress (e.g. useStream in React) to show "Waiting for admin approval" and a link to the LetsPing triage dashboard without blocking the stream.

Cloudflare Agents adapter

Export: @letsping/adapters/cloudflare

For Cloudflare Agents SDK (v0.7.0+, Durable Objects):

Copy paste into your Agent class

This is the minimum integration. It ensures the agent stays alive while waiting for a human and emits receipts into Tail Workers.

  1. keepAliveWhile(): keeps the Durable Object alive during HITL and escrow waits. Without it, Cloudflare can evict idle Durable Objects after roughly 70 seconds.

  2. Tail Worker receipts: events are published to diagnostics_channel on agents:letsping_firewall. In production, Tail Workers receive these in diagnosticsChannelEvents.

  3. MCP firewalling: use mcpToolCallToRequestOptions() to turn MCP tools/call params into LetsPing RequestOptions, so you can approve MCP tool calls before forwarding to the MCP server.

import { AIChatAgent } from "@cloudflare/ai-chat";
import { createApprovalTool } from "@letsping/adapters/approval";
import {
  mcpToolCallToRequestOptions,
} from "@letsping/adapters/cloudflare";
import { z } from "zod";

type Env = {
  LETSPING_API_KEY: string;
};

export class ChatAgent extends AIChatAgent<Env> {
  waitForMcpConnections = { timeout: 10_000 };

  async onChatMessage() {
    const apiKey = this.env.LETSPING_API_KEY;
    if (!apiKey) {
      return new Response("Missing LETSPING_API_KEY", { status: 500 });
    }

    const dangerousTool = createApprovalTool({
      kind: "cloudflare",
      defer: true,
      name: "run_sql",
      description: "Execute a SQL query. Requires human approval.",
      apiKey,
      schema: z.object({ query: z.string() }),
      cloudflare: { keepAliveWhile: this.keepAliveWhile.bind(this) },
      onIntercepted: ({ triage_url }) => {
        this.addMessage({
          role: "assistant",
          parts: [
            {
              type: "text",
              text: `Waiting for approval: ${triage_url}`,
            },
          ],
        });
      },
    });

    const result = await dangerousTool.execute({ query: "DROP TABLE users" });

    this.addMessage({
      role: "assistant",
      parts: [{ type: "text", text: JSON.stringify(result) }],
    });

    return this.toUIMessageStreamResponse();
  }
}

// MCP: intercept tools/call and send through LetsPing first
const requestOptions = mcpToolCallToRequestOptions(
  { name: "execute_sql", arguments: { query: "DROP TABLE users" } },
  { service: "mcp-db", priority: "high" }
);
const decision = await this.keepAliveWhile(() => lp.ask(requestOptions));

MCP full example (waitForMcpConnections plus firewall proxy)

Cloudflare Agents SDK v0.7+ can wait for MCP tools to reconnect after hibernation. If your agent calls MCP tools, you can firewall every tools/call before forwarding it.

import { LetsPing } from "@letsping/sdk";
import { mcpToolCallToRequestOptions } from "@letsping/adapters/cloudflare/mcp";

export class MyAgent extends AIChatAgent<Env> {
  waitForMcpConnections = { timeout: 10_000 };

  async onChatMessage() {
    const lp = new LetsPing(this.env.LETSPING_API_KEY);
    const tools = await this.mcp.getAITools();

    // Example MCP call
    const mcpCall = { name: "execute_sql", arguments: { query: "DROP TABLE users" } };

    // Firewall MCP tool arguments before running the tool
    const opts = mcpToolCallToRequestOptions(mcpCall, { service: "mcp-db", priority: "critical" });
    const decision = await this.keepAliveWhile(() => lp.ask(opts));
    if (decision.status === "REJECTED") return new Response("Blocked by LetsPing", { status: 403 });

    const approved = decision.patched_payload ?? decision.payload;
    return tools.execute_sql(approved);
  }
}

Tail Worker output (exact shape)

In your Tail Worker, each receipt appears in diagnosticsChannelEvents. This is what it looks like:

export default {
  async tail(events) {
    for (const event of events) {
      for (const msg of event.diagnosticsChannelEvents || []) {
        if (msg.channel === "agents:letsping_firewall") {
          console.log(msg.timestamp, msg.channel, msg.message);
        }
      }
    }
  }
};

Example msg.message payloads:

{ "type": "letsping:intercepted", "payload": { "request_id": "req_123", "service": "db-agent", "action": "sql", "triage_url": "https://letsping.co/requests/req_123" }, "timestamp": 0 }
{ "type": "letsping:approved_with_modifications", "payload": { "request_id": "req_123", "diff_summary": { "changes": { "query": { "from": "DROP TABLE users", "to": "SELECT 1" } } } }, "timestamp": 0 }
{ "type": "letsping:escrow_settled", "payload": { "transaction_hash": "0xabc", "amount": 5.0 }, "timestamp": 0 }

Optional subpaths:

  • @letsping/adapters/cloudflare/observability: emitIntercepted, emitApproved, emitRejected, emitEscrowSettled, emitError, for custom Tail Worker receipts.
  • @letsping/adapters/cloudflare/mcp: mcpToolCallToRequestOptions, isMcpToolCallRequest, MCP types.

Peer Dependencies

Make sure the following compatible versions are installed in your project:

Package Minimum Version Purpose
zod >= 3.0.0 Schema definition & validation
ai >= 2.0.0 Vercel AI SDK (for /vercel adapter)
@langchain/core >= 0.1.0 LangChain core types & tools

Notes

  • The apiKey is passed per-tool. For most applications you will use the same key across tools.
  • The schema (Zod) is used both for type safety and to generate an editable form in the LetsPing dashboard.
  • If the human modifies values in the form, the resolved payload will contain the updated values (patched_payload in the LetsPing SDK response).
  • Cryo-Sleep / state snapshots: To park large agent state alongside a tool invocation, use the underlying @letsping/sdk client with state_snapshot when calling ask / defer from within your handler logic. The SDK will encrypt and upload the snapshot via signed URL.
  • Rehydration: These adapters handle the execution pause natively within standard framework constructs, but they do not automatically rehydrate the framework once the process exits. You must handle webhook delivery and instantiate your framework resumption logic manually (see the SDK README webhook examples for Next.js / FastAPI).

For full LetsPing API documentation, see: https://letsping.co/docs

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