@letsping/sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.3.0] - 2026-02-28
9
+
10
+ ### Added
11
+ - First-run experience: single-command demo path and dashboard "Run this" flow for time to first approval.
12
+ - Framework-specific examples: LangGraph + Next.js, Vercel AI SDK + tools, Python + FastAPI (clone, set key, run).
13
+ - Agent path in SDK: helpers for agent workspace creation and signed ingest so agent quickstart does not require raw curl/HMAC.
14
+ - Ergonomic improvements: structured error codes with documentation links, JSDoc "See also" on key methods, optional retries and status helper for defer flows.
15
+ - README "Guides" section: HITL in 2 min, LangGraph, Vercel AI SDK, agent-only, webhooks (links to docs and examples).
16
+
17
+ ### Changed
18
+ - Compatibility: Node.js 18+ (unchanged). All packages aligned to 0.3.0 for coordinated release; public CordiaLabs/LetsPing repo synced with examples and READMEs.
19
+
20
+ ## [0.2.1] - 2025-02-28
21
+
22
+ ### Changed
23
+ - Package metadata: repository, homepage, license, keywords, engines (Node 18+).
24
+
25
+ ## [0.2.0] - 2025-02
26
+
27
+ ### Added
28
+ - LangGraph integration (`@letsping/sdk/integrations/langgraph`) for state persistence and HITL.
29
+ - Agent identity and escrow helpers: `signAgentCall`, `verifyEscrow`, `chainHandoff`.
30
+ - Cryo-Sleep state parking with signed URLs.
31
+ - Behavioral firewall (Markov-based anomaly detection) and smart-accept drift.
32
+
33
+ ### Changed
34
+ - Improved TypeScript types and exports.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 LetsPing / Cordia Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -4,14 +4,17 @@ The official Node.js/TypeScript SDK for [LetsPing](https://letsping.co).
4
4
 
5
5
  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.
6
6
 
7
+ **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.
8
+
7
9
  ### Features
8
10
  - **The Behavioral Shield:** Silently profiles your agent's execution paths via Markov Chains. Automatically intercepts 0-probability reasoning anomalies (hallucinations/prompt injections).
9
11
  - **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.
10
12
  - **Smart-Accept Drift Adaptation:** Approval decisions mathematically alter the baseline. Old unused reasoning paths decay automatically via Exponential Moving Average (EMA).
13
+ - **Agent Identity & Escrow Helpers:** Optional HMAC-based helpers (`signAgentCall`, `verifyEscrow`, `chainHandoff`) for cryptographically linking agent calls and handoffs to LetsPing requests.
11
14
 
12
15
  ## Requirements
13
- - Node.js 18+
14
- - TypeScript 5+ (recommended)
16
+
17
+ - **Compatibility:** Node.js 18+. TypeScript 5+ recommended.
15
18
  - (Optional) `@langchain/langgraph` and `@langchain/core` for state persistence
16
19
 
17
20
  ## Installation
@@ -22,6 +25,27 @@ npm install @letsping/sdk
22
25
 
23
26
  ## Usage
24
27
 
28
+ ### Minimal drop-in example
29
+
30
+ The fastest way to see your first approval in the dashboard:
31
+
32
+ ```ts
33
+ import { LetsPing } from "@letsping/sdk";
34
+
35
+ const apiKey = process.env.LETSPING_API_KEY;
36
+ if (!apiKey) throw new Error("Missing LETSPING_API_KEY env var.");
37
+
38
+ const lp = new LetsPing(apiKey);
39
+
40
+ const decision = await lp.ask({
41
+ service: "billing-agent",
42
+ action: "refund_user",
43
+ payload: { user_id: "u_123", amount: 100 },
44
+ });
45
+ ```
46
+
47
+ Every example in this README follows the same pattern: **either pass the key explicitly or rely on `LETSPING_API_KEY` via env**.
48
+
25
49
  ### Blocking Request (`ask`)
26
50
 
27
51
  Execution suspends until the request is approved, rejected, or times out.
@@ -34,7 +58,7 @@ const lp = new LetsPing(process.env.LETSPING_API_KEY!);
34
58
  async function processRefund(userId: string, amount: number) {
35
59
  try {
36
60
  const decision = await lp.ask({
37
- service: "billing-service",
61
+ service: "billing-agent",
38
62
  action: "refund_user",
39
63
  priority: "high",
40
64
  payload: { userId, amount },
@@ -199,7 +223,7 @@ export async function POST(req: NextRequest) {
199
223
  }
200
224
  ```
201
225
 
202
- 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.
226
+ 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.
203
227
 
204
228
  ## API Reference
205
229
 
@@ -219,7 +243,7 @@ Blocks until resolved (approve / reject / timeout).
219
243
  | `payload` | `Record<string, any>` | Context passed to human operator (and returned in Decision) |
220
244
  | `priority` | `"low" \| "medium" \| "high" \| "critical"` | Routing priority in dashboard |
221
245
  | `schema` | `object` | JSON Schema (draft 07) — generates editable form in dashboard |
222
- | `timeoutMs` | `number` | Max wait time (default: 86_400_000 ms = 24 hours) |
246
+ | `timeoutMs` | `number` | Max wait time in **milliseconds** (default: 86_400_000 ms = 24 hours) |
223
247
 
224
248
  ### `lp.defer(options): Promise<{ id: string }>`
225
249
 
@@ -229,19 +253,40 @@ Fire-and-forget: queues request and returns request ID immediately. Same options
229
253
 
230
254
  ```typescript
231
255
  interface Decision {
232
- status: "APPROVED" | "REJECTED";
256
+ status: "APPROVED" | "REJECTED" | "APPROVED_WITH_MODIFICATIONS";
233
257
  payload: Record<string, any>; // Original payload sent by agent
234
258
  patched_payload?: Record<string, any>; // Human-edited values (if modified)
235
- metadata: {
259
+ diff_summary?: any; // Field-level diff between payload and patched_payload
260
+ metadata?: {
236
261
  actor_id: string; // ID/email of the approving/rejecting human
237
262
  resolved_at: string; // ISO 8601 timestamp
263
+ method?: string; // Optional resolution method (e.g. "dashboard")
238
264
  };
239
265
  }
240
266
  ```
241
267
 
242
- For full documentation, request schema examples, error codes, and dashboard integration see:
268
+ **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.
269
+
270
+ **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).
271
+
272
+ **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.
273
+
274
+ For full documentation, request schema examples, and dashboard integration see:
243
275
  https://letsping.co/docs#sdk
244
276
 
277
+ ### Agent-to-Agent Escrow (optional)
278
+
279
+ For multi-agent systems that want cryptographic guarantees around handoffs, the SDK exposes:
280
+
281
+ - `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](https://letsping.co/agent/quickstart).
282
+ - `ingestWithAgentSignature(agentId, agentSecret, payload, options)` to POST a signed ingest (no hand-rolled HMAC or curl). Options: `{ projectId, ingestUrl, apiKey }`.
283
+ - `signAgentCall(agentId, secret, call)` to attach `agent_id` and `agent_signature` to `/ingest` calls.
284
+ - `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.
285
+ - `verifyEscrow(event, secret)` to validate LetsPing escrow webhooks.
286
+ - `chainHandoff(previous, nextData, secret)` to safely construct downstream handoffs tied to the original request id.
287
+
288
+ See the one-page spec at `/docs/agent-escrow-spec` in the LetsPing web app for the exact wire format and interoperability rules.
289
+
245
290
  Deploy agents with confidence.
246
291
 
247
292
  ## 2-Minute Demo (Node/TypeScript)
@@ -306,4 +351,10 @@ const lp = new LetsPing(process.env.LETSPING_API_KEY!, {
306
351
  });
307
352
  ```
308
353
 
309
- All `ask` / `defer` calls made through that client will flow through your local tunnel into the LetsPing dashboard.
354
+ All `ask` / `defer` calls made through that client will flow through your local tunnel into the LetsPing dashboard.
355
+
356
+ ---
357
+
358
+ **Compatibility:** Node 18+, TypeScript 5+. Optional: `@langchain/langgraph`, `@langchain/core` for LangGraph integration.
359
+
360
+ **License:** MIT. Source: [CordiaLabs/LetsPing](https://github.com/CordiaLabs/LetsPing) (packages/sdk).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@letsping/sdk",
3
- "version": "0.2.0",
4
- "description": "Behavioral Firewall and Cryo-Sleep State Parking for Autonomous Agents",
3
+ "version": "0.3.0",
4
+ "description": "Agent trust layer: behavioral firewall, HITL, and Cryo-Sleep state for AI agents. Works with LangGraph, Vercel AI SDK, and custom runners.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
@@ -22,6 +22,22 @@
22
22
  "dev": "tsup --watch",
23
23
  "clean": "rm -rf dist .turbo"
24
24
  },
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ },
28
+ "homepage": "https://letsping.co",
29
+ "license": "MIT",
30
+ "keywords": [
31
+ "letsping",
32
+ "agent",
33
+ "hitl",
34
+ "human-in-the-loop",
35
+ "behavioral-firewall",
36
+ "langgraph",
37
+ "vercel-ai",
38
+ "cryo-sleep",
39
+ "state-parking"
40
+ ],
25
41
  "peerDependencies": {
26
42
  "@langchain/core": ">=0.1.52",
27
43
  "@langchain/langgraph": ">=0.0.1",
@@ -48,5 +64,10 @@
48
64
  },
49
65
  "publishConfig": {
50
66
  "access": "public"
67
+ },
68
+ "repository": {
69
+ "type": "git",
70
+ "url": "https://github.com/CordiaLabs/LetsPing.git",
71
+ "directory": "packages/sdk"
51
72
  }
52
- }
73
+ }
package/src/index.d.ts CHANGED
@@ -8,28 +8,129 @@ export interface RequestOptions {
8
8
  timeoutMs?: number;
9
9
  }
10
10
  export interface Decision {
11
- status: "APPROVED" | "REJECTED";
11
+ status: "APPROVED" | "REJECTED" | "APPROVED_WITH_MODIFICATIONS";
12
12
  payload: any;
13
13
  patched_payload?: any;
14
+ diff_summary?: any;
14
15
  metadata?: {
15
16
  resolved_at: string;
16
17
  actor_id: string;
17
18
  method?: string;
18
19
  };
19
20
  }
21
+ export interface EscrowEnvelope {
22
+ id: string;
23
+ event: string;
24
+ data: any;
25
+ escrow?: {
26
+ mode: "none" | "handoff" | "finalized";
27
+ handoff_signature: string | null;
28
+ upstream_agent_id: string | null;
29
+ downstream_agent_id: string | null;
30
+ x402_mandate?: any;
31
+ ap2_mandate?: any;
32
+ };
33
+ }
34
+ export declare function verifyEscrow(event: EscrowEnvelope, secret: string): boolean;
35
+ export interface AgentCallPayload {
36
+ project_id: string;
37
+ service: string;
38
+ action: string;
39
+ payload: any;
40
+ }
41
+ export declare function signAgentCall(agentId: string, secret: string, call: AgentCallPayload): {
42
+ agent_id: string;
43
+ agent_signature: string;
44
+ };
45
+ export declare function signIngestBody(agentId: string, secret: string, body: {
46
+ project_id: string;
47
+ service: string;
48
+ action: string;
49
+ payload: any;
50
+ }): {
51
+ project_id: string;
52
+ service: string;
53
+ action: string;
54
+ payload: any;
55
+ agent_id: string;
56
+ agent_signature: string;
57
+ };
58
+ export declare function verifyAgentSignature(agentId: string, secret: string, call: AgentCallPayload, signature: string): boolean;
59
+
60
+ export interface AgentWorkspaceCredentials {
61
+ project_id: string;
62
+ api_key: string;
63
+ ingest_url: string;
64
+ agents_register_url: string;
65
+ agent_id: string;
66
+ agent_secret: string;
67
+ org_id?: string;
68
+ docs_url?: string;
69
+ }
70
+ export declare function createAgentWorkspace(options?: { baseUrl?: string }): Promise<AgentWorkspaceCredentials>;
71
+
72
+ export interface IngestWithAgentSignatureOptions {
73
+ projectId: string;
74
+ ingestUrl: string;
75
+ apiKey: string;
76
+ }
77
+ export interface IngestPayload {
78
+ service: string;
79
+ action: string;
80
+ payload: Record<string, any>;
81
+ }
82
+ export declare function ingestWithAgentSignature(
83
+ agentId: string,
84
+ agentSecret: string,
85
+ payload: IngestPayload,
86
+ options: IngestWithAgentSignatureOptions
87
+ ): Promise<Record<string, any>>;
88
+ export declare function chainHandoff(previous: EscrowEnvelope, nextData: {
89
+ service: string;
90
+ action: string;
91
+ payload: any;
92
+ upstream_agent_id: string;
93
+ downstream_agent_id: string;
94
+ }, secret: string): {
95
+ payload: any;
96
+ escrow: {
97
+ mode: "handoff";
98
+ upstream_agent_id: string;
99
+ downstream_agent_id: string;
100
+ handoff_signature: string;
101
+ };
102
+ };
103
+ export declare const LETSPING_DOCS_BASE: string;
104
+ export type LetsPingErrorCode = "LETSPING_401_AUTH" | "LETSPING_402_QUOTA" | "LETSPING_403_FORBIDDEN" | "LETSPING_404_NOT_FOUND" | "LETSPING_429_RATE_LIMIT" | "LETSPING_TIMEOUT" | "LETSPING_NETWORK" | "LETSPING_WEBHOOK_INVALID" | string;
20
105
  export declare class LetsPingError extends Error {
21
- status?: number | undefined;
22
- constructor(message: string, status?: number | undefined);
106
+ status?: number;
107
+ code?: LetsPingErrorCode;
108
+ documentationUrl?: string;
109
+ constructor(message: string, status?: number, code?: LetsPingErrorCode, documentationUrl?: string);
110
+ }
111
+ export interface RetryOptions {
112
+ maxAttempts?: number;
113
+ initialDelayMs?: number;
114
+ maxDelayMs?: number;
115
+ }
116
+ export interface RequestStatus {
117
+ id: string;
118
+ status: "PENDING" | "APPROVED" | "REJECTED";
119
+ payload?: any;
120
+ patched_payload?: any;
121
+ resolved_at?: string | null;
122
+ actor_id?: string | null;
23
123
  }
24
124
  export declare class LetsPing {
25
- private readonly apiKey;
26
- private readonly baseUrl;
27
125
  constructor(apiKey?: string, options?: {
28
126
  baseUrl?: string;
127
+ encryptionKey?: string;
128
+ retry?: RetryOptions;
29
129
  });
30
130
  ask(options: RequestOptions): Promise<Decision>;
31
- defer(options: RequestOptions): Promise<{
32
- id: string;
33
- }>;
34
- private request;
131
+ defer(options: RequestOptions): Promise<{ id: string }>;
132
+ waitForDecision(id: string, options?: { originalPayload?: Record<string, any>; timeoutMs?: number }): Promise<Decision>;
133
+ getRequestStatus(id: string): Promise<RequestStatus>;
134
+ tool(service: string, action: string, priority?: Priority): (context: string | Record<string, any>) => Promise<string>;
135
+ webhookHandler(payloadStr: string, signatureHeader: string, webhookSecret: string): Promise<{ id: string; event: string; data: Decision; state_snapshot?: Record<string, any> }>;
35
136
  }