@letsping/sdk 0.1.1 → 0.1.3

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.
@@ -0,0 +1,37 @@
1
+ type Priority = "low" | "medium" | "high" | "critical";
2
+ interface RequestOptions {
3
+ service: string;
4
+ action: string;
5
+ payload: Record<string, any>;
6
+ priority?: Priority;
7
+ schema?: Record<string, any>;
8
+ timeoutMs?: number;
9
+ }
10
+ interface Decision {
11
+ status: "APPROVED" | "REJECTED";
12
+ payload: any;
13
+ patched_payload?: any;
14
+ metadata?: {
15
+ resolved_at: string;
16
+ actor_id: string;
17
+ method?: string;
18
+ };
19
+ }
20
+ declare class LetsPingError extends Error {
21
+ status?: number | undefined;
22
+ constructor(message: string, status?: number | undefined);
23
+ }
24
+ declare class LetsPing {
25
+ private readonly apiKey;
26
+ private readonly baseUrl;
27
+ constructor(apiKey?: string, options?: {
28
+ baseUrl?: string;
29
+ });
30
+ ask(options: RequestOptions): Promise<Decision>;
31
+ defer(options: RequestOptions): Promise<{
32
+ id: string;
33
+ }>;
34
+ private request;
35
+ }
36
+
37
+ export { type Decision, LetsPing, LetsPingError, type Priority, type RequestOptions };
@@ -0,0 +1,44 @@
1
+ export type Priority = "low" | "medium" | "high" | "critical";
2
+ export interface RequestOptions {
3
+ service: string;
4
+ action: string;
5
+ payload: Record<string, any>;
6
+ priority?: Priority;
7
+ schema?: Record<string, any>;
8
+ timeoutMs?: number;
9
+ role?: string;
10
+ }
11
+ export interface Decision {
12
+ status: "APPROVED" | "REJECTED" | "APPROVED_WITH_MODIFICATIONS";
13
+ payload: any;
14
+ patched_payload?: any;
15
+ diff_summary?: any;
16
+ metadata?: {
17
+ resolved_at: string;
18
+ actor_id: string;
19
+ method?: string;
20
+ };
21
+ }
22
+ export declare class LetsPingError extends Error {
23
+ status?: number | undefined;
24
+ constructor(message: string, status?: number | undefined);
25
+ }
26
+ declare function computeDiff(original: any, patched: any): any;
27
+ export declare class LetsPing {
28
+ private readonly apiKey;
29
+ private readonly baseUrl;
30
+ private readonly encryptionKey;
31
+ constructor(apiKey?: string, options?: {
32
+ baseUrl?: string;
33
+ encryptionKey?: string;
34
+ });
35
+ private _encrypt;
36
+ private _decrypt;
37
+ ask(options: RequestOptions): Promise<Decision>;
38
+ defer(options: RequestOptions): Promise<{
39
+ id: string;
40
+ }>;
41
+ private request;
42
+ tool(service: string, action: string, priority?: Priority): (context: string | Record<string, any>) => Promise<string>;
43
+ }
44
+ export { computeDiff };
package/dist/index.js ADDED
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __commonJS = (cb, mod) => function __require() {
7
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // package.json
24
+ var require_package = __commonJS({
25
+ "package.json"(exports2, module2) {
26
+ module2.exports = {
27
+ name: "@letsping/sdk",
28
+ version: "0.1.3",
29
+ description: "The Human-in-the-Loop SDK for Autonomous Agents",
30
+ main: "./dist/index.js",
31
+ module: "./dist/index.mjs",
32
+ files: [
33
+ "dist"
34
+ ],
35
+ types: "./dist/index.d.ts",
36
+ exports: {
37
+ ".": {
38
+ types: "./dist/index.d.ts",
39
+ require: "./dist/index.js",
40
+ import: "./dist/index.mjs"
41
+ }
42
+ },
43
+ scripts: {
44
+ build: "tsup && tsc --emitDeclarationOnly --outDir dist",
45
+ dev: "tsup --watch",
46
+ clean: "rm -rf dist .turbo"
47
+ },
48
+ dependencies: {},
49
+ devDependencies: {
50
+ tsup: "^8.0.0",
51
+ typescript: "^5.7.2",
52
+ "@types/node": "^22.0.0"
53
+ },
54
+ publishConfig: {
55
+ access: "public"
56
+ }
57
+ };
58
+ }
59
+ });
60
+
61
+ // src/index.ts
62
+ var index_exports = {};
63
+ __export(index_exports, {
64
+ LetsPing: () => LetsPing,
65
+ LetsPingError: () => LetsPingError,
66
+ computeDiff: () => computeDiff
67
+ });
68
+ module.exports = __toCommonJS(index_exports);
69
+ var import_node_crypto = require("crypto");
70
+ var SDK_VERSION = "0.1.2";
71
+ try {
72
+ SDK_VERSION = require_package().version;
73
+ } catch {
74
+ }
75
+ var LetsPingError = class extends Error {
76
+ constructor(message, status) {
77
+ super(message);
78
+ this.status = status;
79
+ this.name = "LetsPingError";
80
+ }
81
+ };
82
+ function isEncEnvelope(v) {
83
+ return typeof v === "object" && v !== null && v._lp_enc === true && typeof v.iv === "string" && typeof v.ct === "string";
84
+ }
85
+ function encryptPayload(keyBase64, payload) {
86
+ const keyBuf = Buffer.from(keyBase64, "base64");
87
+ const iv = (0, import_node_crypto.randomBytes)(12);
88
+ const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", keyBuf, iv);
89
+ const plain = Buffer.from(JSON.stringify(payload), "utf8");
90
+ const ct = Buffer.concat([cipher.update(plain), cipher.final(), cipher.getAuthTag()]);
91
+ return {
92
+ _lp_enc: true,
93
+ iv: iv.toString("base64"),
94
+ ct: ct.toString("base64")
95
+ };
96
+ }
97
+ function decryptPayload(keyBase64, envelope) {
98
+ const keyBuf = Buffer.from(keyBase64, "base64");
99
+ const iv = Buffer.from(envelope.iv, "base64");
100
+ const ctFull = Buffer.from(envelope.ct, "base64");
101
+ const authTag = ctFull.subarray(ctFull.length - 16);
102
+ const ct = ctFull.subarray(0, ctFull.length - 16);
103
+ const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", keyBuf, iv);
104
+ decipher.setAuthTag(authTag);
105
+ const plain = Buffer.concat([decipher.update(ct), decipher.final()]);
106
+ return JSON.parse(plain.toString("utf8"));
107
+ }
108
+ function computeDiff(original, patched) {
109
+ if (original === patched) return null;
110
+ if (typeof original !== "object" || typeof patched !== "object" || original === null || patched === null || Array.isArray(original) || Array.isArray(patched)) {
111
+ if (JSON.stringify(original) !== JSON.stringify(patched)) {
112
+ return { from: original, to: patched };
113
+ }
114
+ return null;
115
+ }
116
+ const changes = {};
117
+ let hasChanges = false;
118
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(original), ...Object.keys(patched)]);
119
+ for (const key of allKeys) {
120
+ const oV = original[key];
121
+ const pV = patched[key];
122
+ if (!(key in original)) {
123
+ changes[key] = { from: void 0, to: pV };
124
+ hasChanges = true;
125
+ } else if (!(key in patched)) {
126
+ changes[key] = { from: oV, to: void 0 };
127
+ hasChanges = true;
128
+ } else {
129
+ const nestedDiff = computeDiff(oV, pV);
130
+ if (nestedDiff) {
131
+ changes[key] = nestedDiff;
132
+ hasChanges = true;
133
+ }
134
+ }
135
+ }
136
+ return hasChanges ? changes : null;
137
+ }
138
+ var LetsPing = class {
139
+ constructor(apiKey, options) {
140
+ const key = apiKey || process.env.LETSPING_API_KEY;
141
+ if (!key) throw new Error("LetsPing: API Key is required. Pass it to the constructor or set LETSPING_API_KEY env var.");
142
+ this.apiKey = key;
143
+ this.baseUrl = options?.baseUrl || "https://letsping.co/api";
144
+ this.encryptionKey = options?.encryptionKey ?? process.env.LETSPING_ENCRYPTION_KEY ?? null;
145
+ }
146
+ _encrypt(payload) {
147
+ if (!this.encryptionKey) return payload;
148
+ return encryptPayload(this.encryptionKey, payload);
149
+ }
150
+ _decrypt(val) {
151
+ if (!this.encryptionKey || !isEncEnvelope(val)) return val;
152
+ try {
153
+ return decryptPayload(this.encryptionKey, val);
154
+ } catch {
155
+ return val;
156
+ }
157
+ }
158
+ async ask(options) {
159
+ if (options.schema && options.schema._def) {
160
+ throw new LetsPingError("LetsPing Error: Raw Zod schema detected. You must convert it to JSON Schema (e.g. using 'zod-to-json-schema') before passing it to the SDK.");
161
+ }
162
+ const { id } = await this.request("POST", "/ingest", {
163
+ service: options.service,
164
+ action: options.action,
165
+ payload: this._encrypt(options.payload),
166
+ priority: options.priority || "medium",
167
+ schema: options.schema,
168
+ metadata: { role: options.role, sdk: "node" }
169
+ });
170
+ const timeout = options.timeoutMs || 24 * 60 * 60 * 1e3;
171
+ const start = Date.now();
172
+ let delay = 1e3;
173
+ const maxDelay = 1e4;
174
+ while (Date.now() - start < timeout) {
175
+ try {
176
+ const check = await this.request("GET", `/status/${id}`);
177
+ if (check.status === "APPROVED" || check.status === "REJECTED") {
178
+ const decryptedPayload = this._decrypt(check.payload) ?? options.payload;
179
+ const decryptedPatched = check.patched_payload ? this._decrypt(check.patched_payload) : void 0;
180
+ let diff_summary;
181
+ let finalStatus = check.status;
182
+ if (check.status === "APPROVED" && decryptedPatched !== void 0) {
183
+ finalStatus = "APPROVED_WITH_MODIFICATIONS";
184
+ const diff = computeDiff(decryptedPayload, decryptedPatched);
185
+ diff_summary = diff ? { changes: diff } : { changes: "Unknown structure changes" };
186
+ }
187
+ return {
188
+ status: finalStatus,
189
+ payload: decryptedPayload,
190
+ patched_payload: decryptedPatched,
191
+ diff_summary,
192
+ metadata: {
193
+ resolved_at: check.resolved_at,
194
+ actor_id: check.actor_id
195
+ }
196
+ };
197
+ }
198
+ } catch (e) {
199
+ const s = e.status;
200
+ if (s && s >= 400 && s < 500 && s !== 404 && s !== 429) throw e;
201
+ }
202
+ const jitter = Math.random() * 200;
203
+ await new Promise((r) => setTimeout(r, delay + jitter));
204
+ delay = Math.min(delay * 1.5, maxDelay);
205
+ }
206
+ throw new LetsPingError(`Request ${id} timed out waiting for approval.`);
207
+ }
208
+ async defer(options) {
209
+ return this.request("POST", "/ingest", {
210
+ ...options,
211
+ payload: this._encrypt(options.payload)
212
+ });
213
+ }
214
+ async request(method, path, body) {
215
+ const headers = {
216
+ "Authorization": `Bearer ${this.apiKey}`,
217
+ "Content-Type": "application/json",
218
+ "User-Agent": `letsping-node/${SDK_VERSION}`
219
+ };
220
+ try {
221
+ const response = await fetch(`${this.baseUrl}${path}`, {
222
+ method,
223
+ headers,
224
+ body: body ? JSON.stringify(body) : void 0
225
+ });
226
+ if (!response.ok) {
227
+ const errorText = await response.text();
228
+ let message = errorText;
229
+ try {
230
+ const json = JSON.parse(errorText);
231
+ if (json.message) message = json.message;
232
+ } catch {
233
+ }
234
+ throw new LetsPingError(`API Error [${response.status}]: ${message}`, response.status);
235
+ }
236
+ return response.json();
237
+ } catch (e) {
238
+ if (e instanceof LetsPingError) throw e;
239
+ throw new LetsPingError(`Network Error: ${e.message}`);
240
+ }
241
+ }
242
+ tool(service, action, priority = "medium") {
243
+ return async (context) => {
244
+ let payload;
245
+ try {
246
+ if (typeof context === "string") {
247
+ try {
248
+ payload = JSON.parse(context);
249
+ } catch {
250
+ payload = { raw_context: context };
251
+ }
252
+ } else if (typeof context === "object" && context !== null) {
253
+ payload = context;
254
+ } else {
255
+ payload = { raw_context: String(context) };
256
+ }
257
+ const result = await this.ask({ service, action, payload, priority });
258
+ if (result.status === "REJECTED") {
259
+ return "STOP: Action Rejected by Human.";
260
+ }
261
+ if (result.status === "APPROVED_WITH_MODIFICATIONS") {
262
+ return JSON.stringify({
263
+ status: "APPROVED_WITH_MODIFICATIONS",
264
+ message: "The human reviewer authorized this action but modified your original payload. Please review the diff_summary to learn from this correction.",
265
+ diff_summary: result.diff_summary,
266
+ original_payload: result.payload,
267
+ executed_payload: result.patched_payload
268
+ });
269
+ }
270
+ return JSON.stringify({
271
+ status: "APPROVED",
272
+ executed_payload: result.payload
273
+ });
274
+ } catch (e) {
275
+ return `ERROR: System Failure: ${e.message}`;
276
+ }
277
+ };
278
+ }
279
+ };
280
+ // Annotate the CommonJS export names for ESM import in node:
281
+ 0 && (module.exports = {
282
+ LetsPing,
283
+ LetsPingError,
284
+ computeDiff
285
+ });
286
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@letsping/sdk\",\n \"version\": \"0.1.3\",\n \"description\": \"The Human-in-the-Loop SDK for Autonomous Agents\",\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.mjs\",\n \"files\": [\n \"dist\"\n ],\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"require\": \"./dist/index.js\",\n \"import\": \"./dist/index.mjs\"\n }\n },\n \"scripts\": {\n \"build\": \"tsup && tsc --emitDeclarationOnly --outDir dist\",\n \"dev\": \"tsup --watch\",\n \"clean\": \"rm -rf dist .turbo\"\n },\n \"dependencies\": {},\n \"devDependencies\": {\n \"tsup\": \"^8.0.0\",\n \"typescript\": \"^5.7.2\",\n \"@types/node\": \"^22.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}","import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\r\n\nlet SDK_VERSION = \"0.1.2\";\r\ntry {\r\n \n SDK_VERSION = require(\"../package.json\").version;\r\n} catch { }\r\n\r\nexport type Priority = \"low\" | \"medium\" | \"high\" | \"critical\";\r\n\nexport interface RequestOptions {\r\n \r\n service: string;\r\n \r\n action: string;\r\n \r\n payload: Record<string, any>;\r\n \r\n priority?: Priority;\r\n \r\n schema?: Record<string, any>;\r\n \r\n timeoutMs?: number;\r\n \r\n role?: string;\r\n}\r\n\nexport interface Decision {\r\n status: \"APPROVED\" | \"REJECTED\" | \"APPROVED_WITH_MODIFICATIONS\";\r\n \r\n payload: any;\r\n \r\n patched_payload?: any;\r\n \r\n diff_summary?: any;\r\n metadata?: {\r\n resolved_at: string;\r\n actor_id: string;\r\n method?: string;\r\n };\r\n}\r\n\r\nexport class LetsPingError extends Error {\r\n constructor(message: string, public status?: number) {\r\n super(message);\r\n this.name = \"LetsPingError\";\r\n }\r\n}\r\n\ninterface EncEnvelope {\r\n _lp_enc: true;\r\n iv: string; \n ct: string; \n}\r\n\r\nfunction isEncEnvelope(v: unknown): v is EncEnvelope {\r\n return (\r\n typeof v === \"object\" && v !== null &&\r\n (v as any)._lp_enc === true &&\r\n typeof (v as any).iv === \"string\" &&\r\n typeof (v as any).ct === \"string\"\r\n );\r\n}\r\n\r\nfunction encryptPayload(keyBase64: string, payload: Record<string, any>): EncEnvelope {\r\n const keyBuf = Buffer.from(keyBase64, \"base64\");\r\n const iv = randomBytes(12);\r\n const cipher = createCipheriv(\"aes-256-gcm\", keyBuf, iv);\r\n const plain = Buffer.from(JSON.stringify(payload), \"utf8\");\r\n const ct = Buffer.concat([cipher.update(plain), cipher.final(), cipher.getAuthTag()]);\r\n return {\r\n _lp_enc: true,\r\n iv: iv.toString(\"base64\"),\r\n ct: ct.toString(\"base64\"),\r\n };\r\n}\r\n\r\nfunction decryptPayload(keyBase64: string, envelope: EncEnvelope): Record<string, any> {\r\n const keyBuf = Buffer.from(keyBase64, \"base64\");\r\n const iv = Buffer.from(envelope.iv, \"base64\");\r\n const ctFull = Buffer.from(envelope.ct, \"base64\");\r\n \n const authTag = ctFull.subarray(ctFull.length - 16);\r\n const ct = ctFull.subarray(0, ctFull.length - 16);\r\n const decipher = createDecipheriv(\"aes-256-gcm\", keyBuf, iv);\r\n decipher.setAuthTag(authTag);\r\n const plain = Buffer.concat([decipher.update(ct), decipher.final()]);\r\n return JSON.parse(plain.toString(\"utf8\"));\r\n}\r\n\r\nfunction computeDiff(original: any, patched: any): any {\r\n if (original === patched) return null;\r\n\r\n if (\r\n typeof original !== \"object\" ||\r\n typeof patched !== \"object\" ||\r\n original === null ||\r\n patched === null ||\r\n Array.isArray(original) ||\r\n Array.isArray(patched)\r\n ) {\r\n if (JSON.stringify(original) !== JSON.stringify(patched)) {\r\n return { from: original, to: patched };\r\n }\r\n return null;\r\n }\r\n\r\n const changes: Record<string, any> = {};\r\n let hasChanges = false;\r\n const allKeys = new Set([...Object.keys(original), ...Object.keys(patched)]);\r\n\r\n for (const key of allKeys) {\r\n const oV = original[key];\r\n const pV = patched[key];\r\n\r\n if (!(key in original)) {\r\n changes[key] = { from: undefined, to: pV };\r\n hasChanges = true;\r\n } else if (!(key in patched)) {\r\n changes[key] = { from: oV, to: undefined };\r\n hasChanges = true;\r\n } else {\r\n const nestedDiff = computeDiff(oV, pV);\r\n if (nestedDiff) {\r\n changes[key] = nestedDiff;\r\n hasChanges = true;\r\n }\r\n }\r\n }\r\n\r\n return hasChanges ? changes : null;\r\n}\r\n\nexport class LetsPing {\r\n private readonly apiKey: string;\r\n private readonly baseUrl: string;\r\n private readonly encryptionKey: string | null;\r\n\n constructor(apiKey?: string, options?: { baseUrl?: string; encryptionKey?: string }) {\r\n const key = apiKey || process.env.LETSPING_API_KEY;\r\n if (!key) throw new Error(\"LetsPing: API Key is required. Pass it to the constructor or set LETSPING_API_KEY env var.\");\r\n\r\n this.apiKey = key;\r\n this.baseUrl = options?.baseUrl || \"https://letsping.co/api\";\r\n this.encryptionKey = options?.encryptionKey\r\n ?? process.env.LETSPING_ENCRYPTION_KEY\r\n ?? null;\r\n }\r\n\r\n private _encrypt(payload: Record<string, any>): Record<string, any> {\r\n if (!this.encryptionKey) return payload;\r\n return encryptPayload(this.encryptionKey, payload) as any;\r\n }\r\n\r\n private _decrypt(val: any): any {\r\n if (!this.encryptionKey || !isEncEnvelope(val)) return val;\r\n try {\r\n return decryptPayload(this.encryptionKey, val);\r\n } catch {\r\n \n return val;\r\n }\r\n }\r\n\n async ask(options: RequestOptions): Promise<Decision> {\r\n if (options.schema && (options.schema as any)._def) {\r\n throw new LetsPingError(\"LetsPing Error: Raw Zod schema detected. You must convert it to JSON Schema (e.g. using 'zod-to-json-schema') before passing it to the SDK.\");\r\n }\r\n\r\n const { id } = await this.request<{ id: string }>(\"POST\", \"/ingest\", {\r\n service: options.service,\r\n action: options.action,\r\n payload: this._encrypt(options.payload),\r\n priority: options.priority || \"medium\",\r\n schema: options.schema,\r\n metadata: { role: options.role, sdk: \"node\" }\r\n });\r\n\r\n const timeout = options.timeoutMs || 24 * 60 * 60 * 1000;\r\n const start = Date.now();\r\n let delay = 1000;\r\n const maxDelay = 10000;\r\n\r\n while (Date.now() - start < timeout) {\r\n try {\r\n const check = await this.request<any>(\"GET\", `/status/${id}`);\r\n\r\n if (check.status === \"APPROVED\" || check.status === \"REJECTED\") {\r\n const decryptedPayload = this._decrypt(check.payload) ?? options.payload;\r\n const decryptedPatched = check.patched_payload ? this._decrypt(check.patched_payload) : undefined;\r\n\r\n let diff_summary;\r\n let finalStatus = check.status;\r\n if (check.status === \"APPROVED\" && decryptedPatched !== undefined) {\r\n finalStatus = \"APPROVED_WITH_MODIFICATIONS\";\r\n const diff = computeDiff(decryptedPayload, decryptedPatched);\r\n diff_summary = diff ? { changes: diff } : { changes: \"Unknown structure changes\" };\r\n }\r\n\r\n return {\r\n status: finalStatus,\r\n payload: decryptedPayload,\r\n patched_payload: decryptedPatched,\r\n diff_summary,\r\n metadata: {\r\n resolved_at: check.resolved_at,\r\n actor_id: check.actor_id,\r\n }\r\n };\r\n }\r\n } catch (e: any) {\r\n const s = e.status;\r\n if (s && s >= 400 && s < 500 && s !== 404 && s !== 429) throw e;\r\n }\r\n\r\n const jitter = Math.random() * 200;\r\n await new Promise(r => setTimeout(r, delay + jitter));\r\n delay = Math.min(delay * 1.5, maxDelay);\r\n }\r\n\r\n throw new LetsPingError(`Request ${id} timed out waiting for approval.`);\r\n }\r\n\r\n async defer(options: RequestOptions): Promise<{ id: string }> {\r\n return this.request<{ id: string }>(\"POST\", \"/ingest\", {\r\n ...options,\r\n payload: this._encrypt(options.payload),\r\n });\r\n }\r\n\r\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\r\n const headers: Record<string, string> = {\r\n \"Authorization\": `Bearer ${this.apiKey}`,\r\n \"Content-Type\": \"application/json\",\r\n \"User-Agent\": `letsping-node/${SDK_VERSION}`,\r\n };\r\n\r\n try {\r\n const response = await fetch(`${this.baseUrl}${path}`, {\r\n method,\r\n headers,\r\n body: body ? JSON.stringify(body) : undefined,\r\n });\r\n\r\n if (!response.ok) {\r\n const errorText = await response.text();\r\n let message = errorText;\r\n try {\r\n const json = JSON.parse(errorText);\r\n if (json.message) message = json.message;\r\n } catch { }\r\n throw new LetsPingError(`API Error [${response.status}]: ${message}`, response.status);\r\n }\r\n\r\n return response.json() as Promise<T>;\r\n } catch (e: any) {\r\n if (e instanceof LetsPingError) throw e;\r\n throw new LetsPingError(`Network Error: ${e.message}`);\r\n }\r\n }\r\n\r\n tool(service: string, action: string, priority: Priority = \"medium\"): (context: string | Record<string, any>) => Promise<string> {\r\n return async (context: string | Record<string, any>): Promise<string> => {\r\n let payload: Record<string, any>;\r\n try {\r\n if (typeof context === \"string\") {\r\n try { payload = JSON.parse(context); }\r\n catch { payload = { raw_context: context }; }\r\n } else if (typeof context === \"object\" && context !== null) {\r\n payload = context;\r\n } else {\r\n payload = { raw_context: String(context) };\r\n }\r\n\r\n const result = await this.ask({ service, action, payload, priority });\r\n\r\n if (result.status === \"REJECTED\") {\r\n return \"STOP: Action Rejected by Human.\";\r\n }\r\n\r\n if (result.status === \"APPROVED_WITH_MODIFICATIONS\") {\r\n return JSON.stringify({\r\n status: \"APPROVED_WITH_MODIFICATIONS\",\r\n message: \"The human reviewer authorized this action but modified your original payload. Please review the diff_summary to learn from this correction.\",\r\n diff_summary: result.diff_summary,\r\n original_payload: result.payload,\r\n executed_payload: result.patched_payload\r\n });\r\n }\r\n\r\n return JSON.stringify({\r\n status: \"APPROVED\",\r\n executed_payload: result.payload\r\n });\r\n } catch (e: any) {\r\n return `ERROR: System Failure: ${e.message}`;\r\n }\r\n };\r\n }\r\n}\r\n\r\nexport { computeDiff };"],"mappings":"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","names":["exports","module"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,261 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __commonJS = (cb, mod) => function __require() {
3
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
4
+ };
5
+
6
+ // package.json
7
+ var require_package = __commonJS({
8
+ "package.json"(exports, module) {
9
+ module.exports = {
10
+ name: "@letsping/sdk",
11
+ version: "0.1.3",
12
+ description: "The Human-in-the-Loop SDK for Autonomous Agents",
13
+ main: "./dist/index.js",
14
+ module: "./dist/index.mjs",
15
+ files: [
16
+ "dist"
17
+ ],
18
+ types: "./dist/index.d.ts",
19
+ exports: {
20
+ ".": {
21
+ types: "./dist/index.d.ts",
22
+ require: "./dist/index.js",
23
+ import: "./dist/index.mjs"
24
+ }
25
+ },
26
+ scripts: {
27
+ build: "tsup && tsc --emitDeclarationOnly --outDir dist",
28
+ dev: "tsup --watch",
29
+ clean: "rm -rf dist .turbo"
30
+ },
31
+ dependencies: {},
32
+ devDependencies: {
33
+ tsup: "^8.0.0",
34
+ typescript: "^5.7.2",
35
+ "@types/node": "^22.0.0"
36
+ },
37
+ publishConfig: {
38
+ access: "public"
39
+ }
40
+ };
41
+ }
42
+ });
43
+
44
+ // src/index.ts
45
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
46
+ var SDK_VERSION = "0.1.2";
47
+ try {
48
+ SDK_VERSION = require_package().version;
49
+ } catch {
50
+ }
51
+ var LetsPingError = class extends Error {
52
+ constructor(message, status) {
53
+ super(message);
54
+ this.status = status;
55
+ this.name = "LetsPingError";
56
+ }
57
+ };
58
+ function isEncEnvelope(v) {
59
+ return typeof v === "object" && v !== null && v._lp_enc === true && typeof v.iv === "string" && typeof v.ct === "string";
60
+ }
61
+ function encryptPayload(keyBase64, payload) {
62
+ const keyBuf = Buffer.from(keyBase64, "base64");
63
+ const iv = randomBytes(12);
64
+ const cipher = createCipheriv("aes-256-gcm", keyBuf, iv);
65
+ const plain = Buffer.from(JSON.stringify(payload), "utf8");
66
+ const ct = Buffer.concat([cipher.update(plain), cipher.final(), cipher.getAuthTag()]);
67
+ return {
68
+ _lp_enc: true,
69
+ iv: iv.toString("base64"),
70
+ ct: ct.toString("base64")
71
+ };
72
+ }
73
+ function decryptPayload(keyBase64, envelope) {
74
+ const keyBuf = Buffer.from(keyBase64, "base64");
75
+ const iv = Buffer.from(envelope.iv, "base64");
76
+ const ctFull = Buffer.from(envelope.ct, "base64");
77
+ const authTag = ctFull.subarray(ctFull.length - 16);
78
+ const ct = ctFull.subarray(0, ctFull.length - 16);
79
+ const decipher = createDecipheriv("aes-256-gcm", keyBuf, iv);
80
+ decipher.setAuthTag(authTag);
81
+ const plain = Buffer.concat([decipher.update(ct), decipher.final()]);
82
+ return JSON.parse(plain.toString("utf8"));
83
+ }
84
+ function computeDiff(original, patched) {
85
+ if (original === patched) return null;
86
+ if (typeof original !== "object" || typeof patched !== "object" || original === null || patched === null || Array.isArray(original) || Array.isArray(patched)) {
87
+ if (JSON.stringify(original) !== JSON.stringify(patched)) {
88
+ return { from: original, to: patched };
89
+ }
90
+ return null;
91
+ }
92
+ const changes = {};
93
+ let hasChanges = false;
94
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(original), ...Object.keys(patched)]);
95
+ for (const key of allKeys) {
96
+ const oV = original[key];
97
+ const pV = patched[key];
98
+ if (!(key in original)) {
99
+ changes[key] = { from: void 0, to: pV };
100
+ hasChanges = true;
101
+ } else if (!(key in patched)) {
102
+ changes[key] = { from: oV, to: void 0 };
103
+ hasChanges = true;
104
+ } else {
105
+ const nestedDiff = computeDiff(oV, pV);
106
+ if (nestedDiff) {
107
+ changes[key] = nestedDiff;
108
+ hasChanges = true;
109
+ }
110
+ }
111
+ }
112
+ return hasChanges ? changes : null;
113
+ }
114
+ var LetsPing = class {
115
+ constructor(apiKey, options) {
116
+ const key = apiKey || process.env.LETSPING_API_KEY;
117
+ if (!key) throw new Error("LetsPing: API Key is required. Pass it to the constructor or set LETSPING_API_KEY env var.");
118
+ this.apiKey = key;
119
+ this.baseUrl = options?.baseUrl || "https://letsping.co/api";
120
+ this.encryptionKey = options?.encryptionKey ?? process.env.LETSPING_ENCRYPTION_KEY ?? null;
121
+ }
122
+ _encrypt(payload) {
123
+ if (!this.encryptionKey) return payload;
124
+ return encryptPayload(this.encryptionKey, payload);
125
+ }
126
+ _decrypt(val) {
127
+ if (!this.encryptionKey || !isEncEnvelope(val)) return val;
128
+ try {
129
+ return decryptPayload(this.encryptionKey, val);
130
+ } catch {
131
+ return val;
132
+ }
133
+ }
134
+ async ask(options) {
135
+ if (options.schema && options.schema._def) {
136
+ throw new LetsPingError("LetsPing Error: Raw Zod schema detected. You must convert it to JSON Schema (e.g. using 'zod-to-json-schema') before passing it to the SDK.");
137
+ }
138
+ const { id } = await this.request("POST", "/ingest", {
139
+ service: options.service,
140
+ action: options.action,
141
+ payload: this._encrypt(options.payload),
142
+ priority: options.priority || "medium",
143
+ schema: options.schema,
144
+ metadata: { role: options.role, sdk: "node" }
145
+ });
146
+ const timeout = options.timeoutMs || 24 * 60 * 60 * 1e3;
147
+ const start = Date.now();
148
+ let delay = 1e3;
149
+ const maxDelay = 1e4;
150
+ while (Date.now() - start < timeout) {
151
+ try {
152
+ const check = await this.request("GET", `/status/${id}`);
153
+ if (check.status === "APPROVED" || check.status === "REJECTED") {
154
+ const decryptedPayload = this._decrypt(check.payload) ?? options.payload;
155
+ const decryptedPatched = check.patched_payload ? this._decrypt(check.patched_payload) : void 0;
156
+ let diff_summary;
157
+ let finalStatus = check.status;
158
+ if (check.status === "APPROVED" && decryptedPatched !== void 0) {
159
+ finalStatus = "APPROVED_WITH_MODIFICATIONS";
160
+ const diff = computeDiff(decryptedPayload, decryptedPatched);
161
+ diff_summary = diff ? { changes: diff } : { changes: "Unknown structure changes" };
162
+ }
163
+ return {
164
+ status: finalStatus,
165
+ payload: decryptedPayload,
166
+ patched_payload: decryptedPatched,
167
+ diff_summary,
168
+ metadata: {
169
+ resolved_at: check.resolved_at,
170
+ actor_id: check.actor_id
171
+ }
172
+ };
173
+ }
174
+ } catch (e) {
175
+ const s = e.status;
176
+ if (s && s >= 400 && s < 500 && s !== 404 && s !== 429) throw e;
177
+ }
178
+ const jitter = Math.random() * 200;
179
+ await new Promise((r) => setTimeout(r, delay + jitter));
180
+ delay = Math.min(delay * 1.5, maxDelay);
181
+ }
182
+ throw new LetsPingError(`Request ${id} timed out waiting for approval.`);
183
+ }
184
+ async defer(options) {
185
+ return this.request("POST", "/ingest", {
186
+ ...options,
187
+ payload: this._encrypt(options.payload)
188
+ });
189
+ }
190
+ async request(method, path, body) {
191
+ const headers = {
192
+ "Authorization": `Bearer ${this.apiKey}`,
193
+ "Content-Type": "application/json",
194
+ "User-Agent": `letsping-node/${SDK_VERSION}`
195
+ };
196
+ try {
197
+ const response = await fetch(`${this.baseUrl}${path}`, {
198
+ method,
199
+ headers,
200
+ body: body ? JSON.stringify(body) : void 0
201
+ });
202
+ if (!response.ok) {
203
+ const errorText = await response.text();
204
+ let message = errorText;
205
+ try {
206
+ const json = JSON.parse(errorText);
207
+ if (json.message) message = json.message;
208
+ } catch {
209
+ }
210
+ throw new LetsPingError(`API Error [${response.status}]: ${message}`, response.status);
211
+ }
212
+ return response.json();
213
+ } catch (e) {
214
+ if (e instanceof LetsPingError) throw e;
215
+ throw new LetsPingError(`Network Error: ${e.message}`);
216
+ }
217
+ }
218
+ tool(service, action, priority = "medium") {
219
+ return async (context) => {
220
+ let payload;
221
+ try {
222
+ if (typeof context === "string") {
223
+ try {
224
+ payload = JSON.parse(context);
225
+ } catch {
226
+ payload = { raw_context: context };
227
+ }
228
+ } else if (typeof context === "object" && context !== null) {
229
+ payload = context;
230
+ } else {
231
+ payload = { raw_context: String(context) };
232
+ }
233
+ const result = await this.ask({ service, action, payload, priority });
234
+ if (result.status === "REJECTED") {
235
+ return "STOP: Action Rejected by Human.";
236
+ }
237
+ if (result.status === "APPROVED_WITH_MODIFICATIONS") {
238
+ return JSON.stringify({
239
+ status: "APPROVED_WITH_MODIFICATIONS",
240
+ message: "The human reviewer authorized this action but modified your original payload. Please review the diff_summary to learn from this correction.",
241
+ diff_summary: result.diff_summary,
242
+ original_payload: result.payload,
243
+ executed_payload: result.patched_payload
244
+ });
245
+ }
246
+ return JSON.stringify({
247
+ status: "APPROVED",
248
+ executed_payload: result.payload
249
+ });
250
+ } catch (e) {
251
+ return `ERROR: System Failure: ${e.message}`;
252
+ }
253
+ };
254
+ }
255
+ };
256
+ export {
257
+ LetsPing,
258
+ LetsPingError,
259
+ computeDiff
260
+ };
261
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@letsping/sdk\",\n \"version\": \"0.1.3\",\n \"description\": \"The Human-in-the-Loop SDK for Autonomous Agents\",\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.mjs\",\n \"files\": [\n \"dist\"\n ],\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"require\": \"./dist/index.js\",\n \"import\": \"./dist/index.mjs\"\n }\n },\n \"scripts\": {\n \"build\": \"tsup && tsc --emitDeclarationOnly --outDir dist\",\n \"dev\": \"tsup --watch\",\n \"clean\": \"rm -rf dist .turbo\"\n },\n \"dependencies\": {},\n \"devDependencies\": {\n \"tsup\": \"^8.0.0\",\n \"typescript\": \"^5.7.2\",\n \"@types/node\": \"^22.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}","import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\r\n\nlet SDK_VERSION = \"0.1.2\";\r\ntry {\r\n \n SDK_VERSION = require(\"../package.json\").version;\r\n} catch { }\r\n\r\nexport type Priority = \"low\" | \"medium\" | \"high\" | \"critical\";\r\n\nexport interface RequestOptions {\r\n \r\n service: string;\r\n \r\n action: string;\r\n \r\n payload: Record<string, any>;\r\n \r\n priority?: Priority;\r\n \r\n schema?: Record<string, any>;\r\n \r\n timeoutMs?: number;\r\n \r\n role?: string;\r\n}\r\n\nexport interface Decision {\r\n status: \"APPROVED\" | \"REJECTED\" | \"APPROVED_WITH_MODIFICATIONS\";\r\n \r\n payload: any;\r\n \r\n patched_payload?: any;\r\n \r\n diff_summary?: any;\r\n metadata?: {\r\n resolved_at: string;\r\n actor_id: string;\r\n method?: string;\r\n };\r\n}\r\n\r\nexport class LetsPingError extends Error {\r\n constructor(message: string, public status?: number) {\r\n super(message);\r\n this.name = \"LetsPingError\";\r\n }\r\n}\r\n\ninterface EncEnvelope {\r\n _lp_enc: true;\r\n iv: string; \n ct: string; \n}\r\n\r\nfunction isEncEnvelope(v: unknown): v is EncEnvelope {\r\n return (\r\n typeof v === \"object\" && v !== null &&\r\n (v as any)._lp_enc === true &&\r\n typeof (v as any).iv === \"string\" &&\r\n typeof (v as any).ct === \"string\"\r\n );\r\n}\r\n\r\nfunction encryptPayload(keyBase64: string, payload: Record<string, any>): EncEnvelope {\r\n const keyBuf = Buffer.from(keyBase64, \"base64\");\r\n const iv = randomBytes(12);\r\n const cipher = createCipheriv(\"aes-256-gcm\", keyBuf, iv);\r\n const plain = Buffer.from(JSON.stringify(payload), \"utf8\");\r\n const ct = Buffer.concat([cipher.update(plain), cipher.final(), cipher.getAuthTag()]);\r\n return {\r\n _lp_enc: true,\r\n iv: iv.toString(\"base64\"),\r\n ct: ct.toString(\"base64\"),\r\n };\r\n}\r\n\r\nfunction decryptPayload(keyBase64: string, envelope: EncEnvelope): Record<string, any> {\r\n const keyBuf = Buffer.from(keyBase64, \"base64\");\r\n const iv = Buffer.from(envelope.iv, \"base64\");\r\n const ctFull = Buffer.from(envelope.ct, \"base64\");\r\n \n const authTag = ctFull.subarray(ctFull.length - 16);\r\n const ct = ctFull.subarray(0, ctFull.length - 16);\r\n const decipher = createDecipheriv(\"aes-256-gcm\", keyBuf, iv);\r\n decipher.setAuthTag(authTag);\r\n const plain = Buffer.concat([decipher.update(ct), decipher.final()]);\r\n return JSON.parse(plain.toString(\"utf8\"));\r\n}\r\n\r\nfunction computeDiff(original: any, patched: any): any {\r\n if (original === patched) return null;\r\n\r\n if (\r\n typeof original !== \"object\" ||\r\n typeof patched !== \"object\" ||\r\n original === null ||\r\n patched === null ||\r\n Array.isArray(original) ||\r\n Array.isArray(patched)\r\n ) {\r\n if (JSON.stringify(original) !== JSON.stringify(patched)) {\r\n return { from: original, to: patched };\r\n }\r\n return null;\r\n }\r\n\r\n const changes: Record<string, any> = {};\r\n let hasChanges = false;\r\n const allKeys = new Set([...Object.keys(original), ...Object.keys(patched)]);\r\n\r\n for (const key of allKeys) {\r\n const oV = original[key];\r\n const pV = patched[key];\r\n\r\n if (!(key in original)) {\r\n changes[key] = { from: undefined, to: pV };\r\n hasChanges = true;\r\n } else if (!(key in patched)) {\r\n changes[key] = { from: oV, to: undefined };\r\n hasChanges = true;\r\n } else {\r\n const nestedDiff = computeDiff(oV, pV);\r\n if (nestedDiff) {\r\n changes[key] = nestedDiff;\r\n hasChanges = true;\r\n }\r\n }\r\n }\r\n\r\n return hasChanges ? changes : null;\r\n}\r\n\nexport class LetsPing {\r\n private readonly apiKey: string;\r\n private readonly baseUrl: string;\r\n private readonly encryptionKey: string | null;\r\n\n constructor(apiKey?: string, options?: { baseUrl?: string; encryptionKey?: string }) {\r\n const key = apiKey || process.env.LETSPING_API_KEY;\r\n if (!key) throw new Error(\"LetsPing: API Key is required. Pass it to the constructor or set LETSPING_API_KEY env var.\");\r\n\r\n this.apiKey = key;\r\n this.baseUrl = options?.baseUrl || \"https://letsping.co/api\";\r\n this.encryptionKey = options?.encryptionKey\r\n ?? process.env.LETSPING_ENCRYPTION_KEY\r\n ?? null;\r\n }\r\n\r\n private _encrypt(payload: Record<string, any>): Record<string, any> {\r\n if (!this.encryptionKey) return payload;\r\n return encryptPayload(this.encryptionKey, payload) as any;\r\n }\r\n\r\n private _decrypt(val: any): any {\r\n if (!this.encryptionKey || !isEncEnvelope(val)) return val;\r\n try {\r\n return decryptPayload(this.encryptionKey, val);\r\n } catch {\r\n \n return val;\r\n }\r\n }\r\n\n async ask(options: RequestOptions): Promise<Decision> {\r\n if (options.schema && (options.schema as any)._def) {\r\n throw new LetsPingError(\"LetsPing Error: Raw Zod schema detected. You must convert it to JSON Schema (e.g. using 'zod-to-json-schema') before passing it to the SDK.\");\r\n }\r\n\r\n const { id } = await this.request<{ id: string }>(\"POST\", \"/ingest\", {\r\n service: options.service,\r\n action: options.action,\r\n payload: this._encrypt(options.payload),\r\n priority: options.priority || \"medium\",\r\n schema: options.schema,\r\n metadata: { role: options.role, sdk: \"node\" }\r\n });\r\n\r\n const timeout = options.timeoutMs || 24 * 60 * 60 * 1000;\r\n const start = Date.now();\r\n let delay = 1000;\r\n const maxDelay = 10000;\r\n\r\n while (Date.now() - start < timeout) {\r\n try {\r\n const check = await this.request<any>(\"GET\", `/status/${id}`);\r\n\r\n if (check.status === \"APPROVED\" || check.status === \"REJECTED\") {\r\n const decryptedPayload = this._decrypt(check.payload) ?? options.payload;\r\n const decryptedPatched = check.patched_payload ? this._decrypt(check.patched_payload) : undefined;\r\n\r\n let diff_summary;\r\n let finalStatus = check.status;\r\n if (check.status === \"APPROVED\" && decryptedPatched !== undefined) {\r\n finalStatus = \"APPROVED_WITH_MODIFICATIONS\";\r\n const diff = computeDiff(decryptedPayload, decryptedPatched);\r\n diff_summary = diff ? { changes: diff } : { changes: \"Unknown structure changes\" };\r\n }\r\n\r\n return {\r\n status: finalStatus,\r\n payload: decryptedPayload,\r\n patched_payload: decryptedPatched,\r\n diff_summary,\r\n metadata: {\r\n resolved_at: check.resolved_at,\r\n actor_id: check.actor_id,\r\n }\r\n };\r\n }\r\n } catch (e: any) {\r\n const s = e.status;\r\n if (s && s >= 400 && s < 500 && s !== 404 && s !== 429) throw e;\r\n }\r\n\r\n const jitter = Math.random() * 200;\r\n await new Promise(r => setTimeout(r, delay + jitter));\r\n delay = Math.min(delay * 1.5, maxDelay);\r\n }\r\n\r\n throw new LetsPingError(`Request ${id} timed out waiting for approval.`);\r\n }\r\n\r\n async defer(options: RequestOptions): Promise<{ id: string }> {\r\n return this.request<{ id: string }>(\"POST\", \"/ingest\", {\r\n ...options,\r\n payload: this._encrypt(options.payload),\r\n });\r\n }\r\n\r\n private async request<T>(method: string, path: string, body?: any): Promise<T> {\r\n const headers: Record<string, string> = {\r\n \"Authorization\": `Bearer ${this.apiKey}`,\r\n \"Content-Type\": \"application/json\",\r\n \"User-Agent\": `letsping-node/${SDK_VERSION}`,\r\n };\r\n\r\n try {\r\n const response = await fetch(`${this.baseUrl}${path}`, {\r\n method,\r\n headers,\r\n body: body ? JSON.stringify(body) : undefined,\r\n });\r\n\r\n if (!response.ok) {\r\n const errorText = await response.text();\r\n let message = errorText;\r\n try {\r\n const json = JSON.parse(errorText);\r\n if (json.message) message = json.message;\r\n } catch { }\r\n throw new LetsPingError(`API Error [${response.status}]: ${message}`, response.status);\r\n }\r\n\r\n return response.json() as Promise<T>;\r\n } catch (e: any) {\r\n if (e instanceof LetsPingError) throw e;\r\n throw new LetsPingError(`Network Error: ${e.message}`);\r\n }\r\n }\r\n\r\n tool(service: string, action: string, priority: Priority = \"medium\"): (context: string | Record<string, any>) => Promise<string> {\r\n return async (context: string | Record<string, any>): Promise<string> => {\r\n let payload: Record<string, any>;\r\n try {\r\n if (typeof context === \"string\") {\r\n try { payload = JSON.parse(context); }\r\n catch { payload = { raw_context: context }; }\r\n } else if (typeof context === \"object\" && context !== null) {\r\n payload = context;\r\n } else {\r\n payload = { raw_context: String(context) };\r\n }\r\n\r\n const result = await this.ask({ service, action, payload, priority });\r\n\r\n if (result.status === \"REJECTED\") {\r\n return \"STOP: Action Rejected by Human.\";\r\n }\r\n\r\n if (result.status === \"APPROVED_WITH_MODIFICATIONS\") {\r\n return JSON.stringify({\r\n status: \"APPROVED_WITH_MODIFICATIONS\",\r\n message: \"The human reviewer authorized this action but modified your original payload. Please review the diff_summary to learn from this correction.\",\r\n diff_summary: result.diff_summary,\r\n original_payload: result.payload,\r\n executed_payload: result.patched_payload\r\n });\r\n }\r\n\r\n return JSON.stringify({\r\n status: \"APPROVED\",\r\n executed_payload: result.payload\r\n });\r\n } catch (e: any) {\r\n return `ERROR: System Failure: ${e.message}`;\r\n }\r\n };\r\n }\r\n}\r\n\r\nexport { computeDiff };"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,29 +1,32 @@
1
- {
2
- "name": "@letsping/sdk",
3
- "version": "0.1.1",
4
- "description": "The Human-in-the-Loop SDK for Autonomous Agents",
5
- "main": "./dist/index.js",
6
- "module": "./dist/index.mjs",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "require": "./dist/index.js",
12
- "import": "./dist/index.mjs"
13
- }
14
- },
15
- "scripts": {
16
- "build": "tsup",
17
- "dev": "tsup --watch",
18
- "clean": "rm -rf dist .turbo"
19
- },
20
- "dependencies": {},
21
- "devDependencies": {
22
- "tsup": "^8.0.0",
23
- "typescript": "^5.0.0",
24
- "@types/node": "^20.0.0"
25
- },
26
- "publishConfig": {
27
- "access": "public"
28
- }
1
+ {
2
+ "name": "@letsping/sdk",
3
+ "version": "0.1.3",
4
+ "description": "The Human-in-the-Loop SDK for Autonomous Agents",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "require": "./dist/index.js",
15
+ "import": "./dist/index.mjs"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "tsup && tsc --emitDeclarationOnly --outDir dist",
20
+ "dev": "tsup --watch",
21
+ "clean": "rm -rf dist .turbo"
22
+ },
23
+ "dependencies": {},
24
+ "devDependencies": {
25
+ "tsup": "^8.0.0",
26
+ "typescript": "^5.7.2",
27
+ "@types/node": "^22.0.0"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
29
32
  }
package/src/index.ts DELETED
@@ -1,122 +0,0 @@
1
- export type Priority = "low" | "medium" | "high" | "critical";
2
-
3
- export interface RequestOptions {
4
- service: string;
5
- action: string;
6
- payload: Record<string, any>;
7
- priority?: Priority;
8
- /**
9
- * JSON Schema object defining the structure of the payload for the Human UI.
10
- * Note: If using Zod, use `zod-to-json-schema` before passing it here.
11
- */
12
- schema?: Record<string, any>;
13
- timeoutMs?: number;
14
- }
15
-
16
- export interface Decision {
17
- status: "APPROVED" | "REJECTED";
18
- payload: any;
19
- patched_payload?: any;
20
- metadata?: {
21
- resolved_at: string;
22
- actor_id: string;
23
- method?: string;
24
- };
25
- }
26
-
27
- export class LetsPingError extends Error {
28
- constructor(message: string, public status?: number) {
29
- super(message);
30
- this.name = "LetsPingError";
31
- }
32
- }
33
-
34
- export class LetsPing {
35
- private readonly apiKey: string;
36
- private readonly baseUrl: string;
37
-
38
- constructor(apiKey?: string, options?: { baseUrl?: string }) {
39
- const key = apiKey || process.env.LETSPING_API_KEY;
40
- if (!key) throw new Error("LetsPing: API Key is required. Pass it to the constructor or set LETSPING_API_KEY env var.");
41
-
42
- this.apiKey = key;
43
- this.baseUrl = options?.baseUrl || "https://letsping.co/api";
44
- }
45
-
46
- async ask(options: RequestOptions): Promise<Decision> {
47
- // Warn if a raw Zod object is passed, as it serializes to {}
48
- if (options.schema && (options.schema as any)._def) {
49
- console.warn("\x1b[33m%s\x1b[0m", "⚠️ LetsPing Warning: It looks like you passed a raw Zod object to 'schema'. This will result in an empty form. Please convert it to JSON Schema first (e.g. using 'zod-to-json-schema').");
50
- }
51
-
52
- const { id } = await this.request<{ id: string }>("POST", "/ingest", {
53
- service: options.service,
54
- action: options.action,
55
- payload: options.payload,
56
- priority: options.priority || "medium",
57
- schema: options.schema
58
- });
59
-
60
- const timeout = options.timeoutMs || 24 * 60 * 60 * 1000;
61
- const start = Date.now();
62
- let delay = 1000;
63
- const maxDelay = 10000;
64
-
65
- while (Date.now() - start < timeout) {
66
- try {
67
- const check = await this.request<any>("GET", `/status/${id}`);
68
-
69
- if (check.status === "APPROVED" || check.status === "REJECTED") {
70
- return {
71
- status: check.status,
72
- payload: options.payload,
73
- patched_payload: check.patched_payload || options.payload,
74
- metadata: {
75
- resolved_at: check.resolved_at,
76
- actor_id: check.actor_id
77
- }
78
- };
79
- }
80
- } catch (e: any) {
81
- // Ignore 404 (pending) and 429 (rate limit), throw everything else
82
- if (e.status !== 404 && e.status !== 429) throw e;
83
- }
84
-
85
- const jitter = Math.random() * 200;
86
- await new Promise(r => setTimeout(r, delay + jitter));
87
- delay = Math.min(delay * 1.5, maxDelay);
88
- }
89
-
90
- throw new LetsPingError(`Request ${id} timed out waiting for approval.`);
91
- }
92
-
93
- async defer(options: RequestOptions): Promise<{ id: string }> {
94
- return this.request<{ id: string }>("POST", "/ingest", options);
95
- }
96
-
97
- private async request<T>(method: string, path: string, body?: any): Promise<T> {
98
- const headers: Record<string, string> = {
99
- "Authorization": `Bearer ${this.apiKey}`,
100
- "Content-Type": "application/json",
101
- "User-Agent": "letsping-node/0.1.0"
102
- };
103
-
104
- try {
105
- const response = await fetch(`${this.baseUrl}${path}`, {
106
- method,
107
- headers,
108
- body: body ? JSON.stringify(body) : undefined,
109
- });
110
-
111
- if (!response.ok) {
112
- const errorText = await response.text();
113
- throw new LetsPingError(`LetsPing API Error [${response.status}]: ${errorText}`, response.status);
114
- }
115
-
116
- return response.json() as Promise<T>;
117
- } catch (e: any) {
118
- if (e instanceof LetsPingError) throw e;
119
- throw new LetsPingError(`Network Error: ${e.message}`);
120
- }
121
- }
122
- }
package/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "moduleResolution": "Bundler",
6
- "strict": true,
7
- "declaration": true,
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "forceConsistentCasingInFileNames": true,
11
- "baseUrl": "."
12
- },
13
- "include": [
14
- "src"
15
- ]
16
- }
package/tsup.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from "tsup";
2
-
3
- export default defineConfig({
4
- entry: ["src/index.ts"],
5
- format: ["cjs", "esm"],
6
- dts: true,
7
- clean: true,
8
- sourcemap: true,
9
- splitting: false,
10
- });