@letsping/sdk 0.1.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/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@letsping/sdk",
3
+ "version": "0.1.0",
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
+ }
29
+ }
package/src/index.ts ADDED
@@ -0,0 +1,122 @@
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 ADDED
@@ -0,0 +1,16 @@
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 ADDED
@@ -0,0 +1,10 @@
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
+ });