@letsping/sdk 0.1.6 → 0.2.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/README.md +156 -13
- package/dist/index.d.mts +37 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +528 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +493 -0
- package/dist/index.mjs.map +1 -0
- package/dist/integrations/langgraph.d.mts +20 -0
- package/dist/integrations/langgraph.d.ts +17 -0
- package/dist/integrations/langgraph.js +150 -0
- package/dist/integrations/langgraph.js.map +1 -0
- package/dist/integrations/langgraph.mjs +125 -0
- package/dist/integrations/langgraph.mjs.map +1 -0
- package/dist-tsc/index.d.ts +68 -0
- package/examples/langgraph-demo.ts +80 -0
- package/package.json +1 -1
- package/src/index.ts +67 -7
- package/src/integrations/langgraph.ts +105 -7
|
@@ -2,13 +2,84 @@ import { BaseCheckpointSaver, Checkpoint, CheckpointMetadata, CheckpointTuple }
|
|
|
2
2
|
import { RunnableConfig } from "@langchain/core/runnables";
|
|
3
3
|
import { LetsPing } from "../index";
|
|
4
4
|
|
|
5
|
+
type StoredCheckpoint = {
|
|
6
|
+
checkpoint: Checkpoint;
|
|
7
|
+
metadata: CheckpointMetadata;
|
|
8
|
+
};
|
|
9
|
+
|
|
5
10
|
export class LetsPingCheckpointer extends BaseCheckpointSaver {
|
|
6
|
-
private checkpoints: Record<string,
|
|
11
|
+
private checkpoints: Record<string, StoredCheckpoint> = {};
|
|
7
12
|
|
|
8
13
|
constructor(public client: LetsPing) {
|
|
9
14
|
super();
|
|
10
15
|
}
|
|
11
16
|
|
|
17
|
+
private getTransport(): (<T = any>(method: string, path: string, body?: any) => Promise<T>) | null {
|
|
18
|
+
const clientAny = this.client as any;
|
|
19
|
+
if (typeof clientAny.request === "function") {
|
|
20
|
+
return clientAny.request.bind(this.client);
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private async saveRemote(
|
|
26
|
+
threadId: string,
|
|
27
|
+
checkpointId: string,
|
|
28
|
+
checkpoint: Checkpoint,
|
|
29
|
+
metadata: CheckpointMetadata
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
const transport = this.getTransport();
|
|
32
|
+
if (!transport) {
|
|
33
|
+
console.warn("[LetsPingCheckpointer] Missing underlying transport; falling back to in-memory only.");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
await transport("POST", "/langgraph/checkpoints", {
|
|
38
|
+
thread_id: threadId,
|
|
39
|
+
checkpoint_id: checkpointId,
|
|
40
|
+
checkpoint,
|
|
41
|
+
metadata,
|
|
42
|
+
});
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.warn("[LetsPingCheckpointer] Failed to persist checkpoint remotely; falling back to in-memory only.", e);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private async loadRemote(
|
|
49
|
+
threadId: string,
|
|
50
|
+
checkpointId?: string
|
|
51
|
+
): Promise<StoredCheckpoint | null> {
|
|
52
|
+
const transport = this.getTransport();
|
|
53
|
+
if (!transport) {
|
|
54
|
+
console.warn("[LetsPingCheckpointer] Missing underlying transport; using in-memory checkpoints only.");
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const search = checkpointId
|
|
58
|
+
? `?thread_id=${encodeURIComponent(threadId)}&checkpoint_id=${encodeURIComponent(checkpointId)}`
|
|
59
|
+
: `?thread_id=${encodeURIComponent(threadId)}&latest=1`;
|
|
60
|
+
try {
|
|
61
|
+
const res = await transport<any>("GET", `/langgraph/checkpoints${search}`);
|
|
62
|
+
if (res && res.checkpoint && res.metadata) {
|
|
63
|
+
return { checkpoint: res.checkpoint as Checkpoint, metadata: res.metadata as CheckpointMetadata };
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
// If not found or backend unavailable, fall back to local cache only.
|
|
67
|
+
console.warn("[LetsPingCheckpointer] Failed to load remote checkpoint", e);
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private async deleteRemote(threadId: string): Promise<void> {
|
|
73
|
+
const transport = this.getTransport();
|
|
74
|
+
if (!transport) return;
|
|
75
|
+
const search = `?thread_id=${encodeURIComponent(threadId)}`;
|
|
76
|
+
try {
|
|
77
|
+
await transport("DELETE", `/langgraph/checkpoints${search}`);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.warn("[LetsPingCheckpointer] Failed to delete remote checkpoints", e);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
12
83
|
async put(
|
|
13
84
|
config: RunnableConfig,
|
|
14
85
|
checkpoint: Checkpoint,
|
|
@@ -18,17 +89,22 @@ export class LetsPingCheckpointer extends BaseCheckpointSaver {
|
|
|
18
89
|
const threadId = config.configurable?.thread_id;
|
|
19
90
|
const checkpointId = checkpoint.id;
|
|
20
91
|
|
|
21
|
-
|
|
92
|
+
if (!threadId || !checkpointId) {
|
|
93
|
+
return config;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.checkpoints[`${threadId}:${checkpointId}`] = { checkpoint, metadata };
|
|
97
|
+
await this.saveRemote(threadId, checkpointId, checkpoint, metadata);
|
|
22
98
|
|
|
23
99
|
return {
|
|
24
100
|
configurable: {
|
|
25
101
|
thread_id: threadId,
|
|
26
102
|
checkpoint_id: checkpointId,
|
|
27
|
-
}
|
|
103
|
+
},
|
|
28
104
|
};
|
|
29
105
|
}
|
|
30
106
|
|
|
31
|
-
//
|
|
107
|
+
// METHODS REQUIRED BY LANGGRAPH V0.1+
|
|
32
108
|
async putWrites(config: RunnableConfig, writes: any, taskId: string): Promise<void> {
|
|
33
109
|
// No-op for V1: LetsPing focuses on primary state parking, not granular sub-task writes.
|
|
34
110
|
}
|
|
@@ -39,27 +115,49 @@ export class LetsPingCheckpointer extends BaseCheckpointSaver {
|
|
|
39
115
|
delete this.checkpoints[key];
|
|
40
116
|
}
|
|
41
117
|
}
|
|
118
|
+
await this.deleteRemote(threadId);
|
|
42
119
|
}
|
|
43
120
|
|
|
44
121
|
async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {
|
|
45
122
|
const threadId = config.configurable?.thread_id;
|
|
46
123
|
const checkpointId = config.configurable?.checkpoint_id;
|
|
124
|
+
if (!threadId) return undefined;
|
|
125
|
+
|
|
126
|
+
// Prefer remote truth, fall back to local cache.
|
|
127
|
+
const remote = await this.loadRemote(threadId, checkpointId);
|
|
128
|
+
if (remote) {
|
|
129
|
+
return { config, checkpoint: remote.checkpoint, metadata: remote.metadata };
|
|
130
|
+
}
|
|
47
131
|
|
|
48
132
|
if (checkpointId) {
|
|
49
133
|
const match = this.checkpoints[`${threadId}:${checkpointId}`];
|
|
50
|
-
if (match)
|
|
134
|
+
if (match) {
|
|
135
|
+
return { config, checkpoint: match.checkpoint, metadata: match.metadata };
|
|
136
|
+
}
|
|
51
137
|
}
|
|
52
138
|
|
|
53
139
|
let latest: CheckpointTuple | undefined;
|
|
54
140
|
for (const [key, val] of Object.entries(this.checkpoints)) {
|
|
55
141
|
if (key.startsWith(`${threadId}:`)) {
|
|
56
|
-
latest = { config, checkpoint: val
|
|
142
|
+
latest = { config, checkpoint: val.checkpoint, metadata: val.metadata };
|
|
57
143
|
}
|
|
58
144
|
}
|
|
59
145
|
return latest;
|
|
60
146
|
}
|
|
61
147
|
|
|
62
148
|
async *list(config: RunnableConfig, options?: any): AsyncGenerator<CheckpointTuple> {
|
|
63
|
-
|
|
149
|
+
const threadId = config.configurable?.thread_id;
|
|
150
|
+
if (!threadId) return;
|
|
151
|
+
|
|
152
|
+
const remoteLatest = await this.loadRemote(threadId);
|
|
153
|
+
if (remoteLatest) {
|
|
154
|
+
yield { config, checkpoint: remoteLatest.checkpoint, metadata: remoteLatest.metadata };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (const [key, val] of Object.entries(this.checkpoints)) {
|
|
158
|
+
if (key.startsWith(`${threadId}:`)) {
|
|
159
|
+
yield { config, checkpoint: val.checkpoint, metadata: val.metadata };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
64
162
|
}
|
|
65
163
|
}
|