@langchain/langgraph-api 1.1.2 → 1.1.8
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 +9 -8
- package/CHANGELOG.md +0 -297
- package/dist/api/assistants.d.mts +0 -3
- package/dist/api/assistants.mjs +0 -193
- package/dist/api/meta.d.mts +0 -3
- package/dist/api/meta.mjs +0 -65
- package/dist/api/runs.d.mts +0 -3
- package/dist/api/runs.mjs +0 -324
- package/dist/api/store.d.mts +0 -3
- package/dist/api/store.mjs +0 -111
- package/dist/api/threads.d.mts +0 -3
- package/dist/api/threads.mjs +0 -143
- package/dist/auth/custom.d.mts +0 -9
- package/dist/auth/custom.mjs +0 -32
- package/dist/auth/index.d.mts +0 -43
- package/dist/auth/index.mjs +0 -163
- package/dist/cli/entrypoint.d.mts +0 -1
- package/dist/cli/entrypoint.mjs +0 -41
- package/dist/cli/spawn.d.mts +0 -42
- package/dist/cli/spawn.mjs +0 -47
- package/dist/cli/utils/ipc/client.d.mts +0 -5
- package/dist/cli/utils/ipc/client.mjs +0 -47
- package/dist/cli/utils/ipc/utils/get-pipe-path.d.mts +0 -1
- package/dist/cli/utils/ipc/utils/get-pipe-path.mjs +0 -29
- package/dist/cli/utils/ipc/utils/temporary-directory.d.mts +0 -5
- package/dist/cli/utils/ipc/utils/temporary-directory.mjs +0 -40
- package/dist/command.d.mts +0 -11
- package/dist/command.mjs +0 -15
- package/dist/experimental/embed.d.mts +0 -42
- package/dist/experimental/embed.mjs +0 -299
- package/dist/graph/api.d.mts +0 -1
- package/dist/graph/api.mjs +0 -2
- package/dist/graph/load.d.mts +0 -19
- package/dist/graph/load.hooks.d.mts +0 -2
- package/dist/graph/load.hooks.mjs +0 -52
- package/dist/graph/load.mjs +0 -96
- package/dist/graph/load.utils.d.mts +0 -22
- package/dist/graph/load.utils.mjs +0 -49
- package/dist/graph/parser/index.d.mts +0 -23
- package/dist/graph/parser/index.mjs +0 -58
- package/dist/graph/parser/parser.d.mts +0 -77
- package/dist/graph/parser/parser.mjs +0 -429
- package/dist/graph/parser/parser.worker.d.mts +0 -1
- package/dist/graph/parser/parser.worker.mjs +0 -7
- package/dist/graph/parser/schema/types.d.mts +0 -154
- package/dist/graph/parser/schema/types.mjs +0 -1496
- package/dist/graph/parser/schema/types.template.d.mts +0 -1
- package/dist/graph/parser/schema/types.template.mts +0 -92
- package/dist/http/custom.d.mts +0 -6
- package/dist/http/custom.mjs +0 -10
- package/dist/http/middleware.d.mts +0 -11
- package/dist/http/middleware.mjs +0 -57
- package/dist/logging.d.mts +0 -10
- package/dist/logging.mjs +0 -115
- package/dist/loopback.d.mts +0 -4
- package/dist/loopback.mjs +0 -10
- package/dist/preload.d.mts +0 -1
- package/dist/preload.mjs +0 -29
- package/dist/queue.d.mts +0 -2
- package/dist/queue.mjs +0 -119
- package/dist/schemas.d.mts +0 -1552
- package/dist/schemas.mjs +0 -492
- package/dist/semver/index.d.mts +0 -15
- package/dist/semver/index.mjs +0 -46
- package/dist/server.d.mts +0 -175
- package/dist/server.mjs +0 -181
- package/dist/state.d.mts +0 -3
- package/dist/state.mjs +0 -30
- package/dist/storage/checkpoint.d.mts +0 -19
- package/dist/storage/checkpoint.mjs +0 -127
- package/dist/storage/context.d.mts +0 -3
- package/dist/storage/context.mjs +0 -11
- package/dist/storage/importMap.d.mts +0 -55
- package/dist/storage/importMap.mjs +0 -55
- package/dist/storage/ops.d.mts +0 -169
- package/dist/storage/ops.mjs +0 -1262
- package/dist/storage/persist.d.mts +0 -18
- package/dist/storage/persist.mjs +0 -81
- package/dist/storage/store.d.mts +0 -17
- package/dist/storage/store.mjs +0 -41
- package/dist/storage/types.d.mts +0 -301
- package/dist/storage/types.mjs +0 -1
- package/dist/stream.d.mts +0 -43
- package/dist/stream.mjs +0 -235
- package/dist/ui/load.d.mts +0 -8
- package/dist/ui/load.mjs +0 -53
- package/dist/utils/abort.d.mts +0 -1
- package/dist/utils/abort.mjs +0 -8
- package/dist/utils/hono.d.mts +0 -5
- package/dist/utils/hono.mjs +0 -24
- package/dist/utils/importMap.d.mts +0 -55
- package/dist/utils/importMap.mjs +0 -55
- package/dist/utils/runnableConfig.d.mts +0 -3
- package/dist/utils/runnableConfig.mjs +0 -45
- package/dist/utils/serde.d.mts +0 -5
- package/dist/utils/serde.mjs +0 -20
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -12
- package/dist/webhook.d.mts +0 -11
- package/dist/webhook.mjs +0 -30
package/dist/storage/ops.mjs
DELETED
|
@@ -1,1262 +0,0 @@
|
|
|
1
|
-
import { HTTPException } from "hono/http-exception";
|
|
2
|
-
import { v4 as uuid4, v5 as uuid5 } from "uuid";
|
|
3
|
-
import { handleAuthEvent, isAuthMatching } from "../auth/index.mjs";
|
|
4
|
-
import { getLangGraphCommand } from "../command.mjs";
|
|
5
|
-
import { getGraph, NAMESPACE_GRAPH } from "../graph/load.mjs";
|
|
6
|
-
import { logger } from "../logging.mjs";
|
|
7
|
-
import { serializeError } from "../utils/serde.mjs";
|
|
8
|
-
import { checkpointer } from "./checkpoint.mjs";
|
|
9
|
-
import { store } from "./store.mjs";
|
|
10
|
-
export class FileSystemOps {
|
|
11
|
-
conn;
|
|
12
|
-
assistants;
|
|
13
|
-
runs;
|
|
14
|
-
threads;
|
|
15
|
-
constructor(conn) {
|
|
16
|
-
this.conn = conn;
|
|
17
|
-
this.assistants = new FileSystemAssistants(this.conn);
|
|
18
|
-
this.runs = new FileSystemRuns(this.conn);
|
|
19
|
-
this.threads = new FileSystemThreads(this.conn);
|
|
20
|
-
}
|
|
21
|
-
truncate(flags) {
|
|
22
|
-
return this.conn.with((STORE) => {
|
|
23
|
-
if (flags.runs)
|
|
24
|
-
STORE.runs = {};
|
|
25
|
-
if (flags.threads)
|
|
26
|
-
STORE.threads = {};
|
|
27
|
-
if (flags.assistants) {
|
|
28
|
-
STORE.assistants = Object.fromEntries(Object.entries(STORE.assistants).filter(([key, assistant]) => assistant.metadata?.created_by === "system" &&
|
|
29
|
-
uuid5(assistant.graph_id, NAMESPACE_GRAPH) === key));
|
|
30
|
-
}
|
|
31
|
-
if (flags.checkpointer)
|
|
32
|
-
checkpointer.clear();
|
|
33
|
-
if (flags.store)
|
|
34
|
-
store.clear();
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
class TimeoutError extends Error {
|
|
39
|
-
}
|
|
40
|
-
class AbortError extends Error {
|
|
41
|
-
}
|
|
42
|
-
class Queue {
|
|
43
|
-
log = [];
|
|
44
|
-
listeners = [];
|
|
45
|
-
nextId = 0;
|
|
46
|
-
resumable = false;
|
|
47
|
-
constructor(options) {
|
|
48
|
-
this.resumable = options.resumable;
|
|
49
|
-
}
|
|
50
|
-
push(item) {
|
|
51
|
-
this.log.push(item);
|
|
52
|
-
for (const listener of this.listeners)
|
|
53
|
-
listener(this.nextId);
|
|
54
|
-
this.nextId += 1;
|
|
55
|
-
}
|
|
56
|
-
async get(options) {
|
|
57
|
-
if (this.resumable) {
|
|
58
|
-
const lastEventId = options.lastEventId;
|
|
59
|
-
// Generator stores internal state of the read head index,
|
|
60
|
-
let targetId = lastEventId != null ? +lastEventId + 1 : null;
|
|
61
|
-
if (targetId == null ||
|
|
62
|
-
isNaN(targetId) ||
|
|
63
|
-
targetId < 0 ||
|
|
64
|
-
targetId >= this.log.length) {
|
|
65
|
-
targetId = null;
|
|
66
|
-
}
|
|
67
|
-
if (targetId != null)
|
|
68
|
-
return [String(targetId), this.log[targetId]];
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
if (this.log.length) {
|
|
72
|
-
const nextId = this.nextId - this.log.length;
|
|
73
|
-
const nextItem = this.log.shift();
|
|
74
|
-
return [String(nextId), nextItem];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
let timeout = undefined;
|
|
78
|
-
let resolver = undefined;
|
|
79
|
-
const clean = new AbortController();
|
|
80
|
-
// listen to new item
|
|
81
|
-
return await new Promise((resolve, reject) => {
|
|
82
|
-
timeout = setTimeout(() => reject(new TimeoutError()), options.timeout);
|
|
83
|
-
resolver = resolve;
|
|
84
|
-
options.signal?.addEventListener("abort", () => reject(new AbortError()), { signal: clean.signal });
|
|
85
|
-
this.listeners.push(resolver);
|
|
86
|
-
})
|
|
87
|
-
.then((idx) => {
|
|
88
|
-
if (this.resumable) {
|
|
89
|
-
return [String(idx), this.log[idx]];
|
|
90
|
-
}
|
|
91
|
-
const nextId = this.nextId - this.log.length;
|
|
92
|
-
const nextItem = this.log.shift();
|
|
93
|
-
return [String(nextId), nextItem];
|
|
94
|
-
})
|
|
95
|
-
.finally(() => {
|
|
96
|
-
this.listeners = this.listeners.filter((l) => l !== resolver);
|
|
97
|
-
clearTimeout(timeout);
|
|
98
|
-
clean.abort();
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
class CancellationAbortController extends AbortController {
|
|
103
|
-
abort(reason) {
|
|
104
|
-
super.abort(reason);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
class StreamManagerImpl {
|
|
108
|
-
readers = {};
|
|
109
|
-
control = {};
|
|
110
|
-
getQueue(runId, options) {
|
|
111
|
-
if (this.readers[runId] == null) {
|
|
112
|
-
this.readers[runId] = new Queue(options);
|
|
113
|
-
}
|
|
114
|
-
return this.readers[runId];
|
|
115
|
-
}
|
|
116
|
-
getControl(runId) {
|
|
117
|
-
if (this.control[runId] == null)
|
|
118
|
-
return undefined;
|
|
119
|
-
return this.control[runId];
|
|
120
|
-
}
|
|
121
|
-
isLocked(runId) {
|
|
122
|
-
return this.control[runId] != null;
|
|
123
|
-
}
|
|
124
|
-
lock(runId) {
|
|
125
|
-
if (this.control[runId] != null) {
|
|
126
|
-
logger.warn("Run already locked", { run_id: runId });
|
|
127
|
-
}
|
|
128
|
-
this.control[runId] = new CancellationAbortController();
|
|
129
|
-
return this.control[runId].signal;
|
|
130
|
-
}
|
|
131
|
-
unlock(runId) {
|
|
132
|
-
delete this.control[runId];
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
export const StreamManager = new StreamManagerImpl();
|
|
136
|
-
const isObject = (value) => {
|
|
137
|
-
return typeof value === "object" && value !== null;
|
|
138
|
-
};
|
|
139
|
-
const isJsonbContained = (superset, subset) => {
|
|
140
|
-
if (superset == null || subset == null)
|
|
141
|
-
return true;
|
|
142
|
-
for (const [key, value] of Object.entries(subset)) {
|
|
143
|
-
if (superset[key] == null)
|
|
144
|
-
return false;
|
|
145
|
-
if (isObject(value) && isObject(superset[key])) {
|
|
146
|
-
if (!isJsonbContained(superset[key], value))
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
else if (superset[key] !== value) {
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return true;
|
|
154
|
-
};
|
|
155
|
-
export class FileSystemAssistants {
|
|
156
|
-
conn;
|
|
157
|
-
constructor(conn) {
|
|
158
|
-
this.conn = conn;
|
|
159
|
-
}
|
|
160
|
-
async *search(options, auth) {
|
|
161
|
-
const [filters] = await handleAuthEvent(auth, "assistants:search", {
|
|
162
|
-
graph_id: options.graph_id,
|
|
163
|
-
metadata: options.metadata,
|
|
164
|
-
limit: options.limit,
|
|
165
|
-
offset: options.offset,
|
|
166
|
-
});
|
|
167
|
-
yield* this.conn.withGenerator(async function* (STORE) {
|
|
168
|
-
let filtered = Object.values(STORE.assistants)
|
|
169
|
-
.filter((assistant) => {
|
|
170
|
-
if (options.graph_id != null &&
|
|
171
|
-
assistant["graph_id"] !== options.graph_id) {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
if (options.name != null &&
|
|
175
|
-
!assistant["name"]
|
|
176
|
-
.toLowerCase()
|
|
177
|
-
.includes(options.name.toLowerCase())) {
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
if (options.metadata != null &&
|
|
181
|
-
!isJsonbContained(assistant["metadata"], options.metadata)) {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
if (!isAuthMatching(assistant["metadata"], filters)) {
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
return true;
|
|
188
|
-
})
|
|
189
|
-
.sort((a, b) => {
|
|
190
|
-
const aCreatedAt = a["created_at"]?.getTime() ?? 0;
|
|
191
|
-
const bCreatedAt = b["created_at"]?.getTime() ?? 0;
|
|
192
|
-
return bCreatedAt - aCreatedAt;
|
|
193
|
-
});
|
|
194
|
-
// Calculate total count before pagination
|
|
195
|
-
const total = filtered.length;
|
|
196
|
-
for (const assistant of filtered.slice(options.offset, options.offset + options.limit)) {
|
|
197
|
-
yield {
|
|
198
|
-
assistant: {
|
|
199
|
-
...assistant,
|
|
200
|
-
name: assistant.name ?? assistant.graph_id,
|
|
201
|
-
},
|
|
202
|
-
total,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
async get(assistant_id, auth) {
|
|
208
|
-
const [filters] = await handleAuthEvent(auth, "assistants:read", {
|
|
209
|
-
assistant_id,
|
|
210
|
-
});
|
|
211
|
-
return this.conn.with((STORE) => {
|
|
212
|
-
const result = STORE.assistants[assistant_id];
|
|
213
|
-
if (result == null)
|
|
214
|
-
throw new HTTPException(404, { message: "Assistant not found" });
|
|
215
|
-
if (!isAuthMatching(result["metadata"], filters)) {
|
|
216
|
-
throw new HTTPException(404, { message: "Assistant not found" });
|
|
217
|
-
}
|
|
218
|
-
return { ...result, name: result.name ?? result.graph_id };
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
async put(assistant_id, options, auth) {
|
|
222
|
-
const [filters, mutable] = await handleAuthEvent(auth, "assistants:create", {
|
|
223
|
-
assistant_id,
|
|
224
|
-
config: options.config,
|
|
225
|
-
context: options.context,
|
|
226
|
-
graph_id: options.graph_id,
|
|
227
|
-
metadata: options.metadata,
|
|
228
|
-
if_exists: options.if_exists,
|
|
229
|
-
name: options.name,
|
|
230
|
-
description: options.description,
|
|
231
|
-
});
|
|
232
|
-
return this.conn.with((STORE) => {
|
|
233
|
-
if (STORE.assistants[assistant_id] != null) {
|
|
234
|
-
const existingAssistant = STORE.assistants[assistant_id];
|
|
235
|
-
if (!isAuthMatching(existingAssistant?.metadata, filters)) {
|
|
236
|
-
throw new HTTPException(409, { message: "Assistant already exists" });
|
|
237
|
-
}
|
|
238
|
-
if (options.if_exists === "raise") {
|
|
239
|
-
throw new HTTPException(409, { message: "Assistant already exists" });
|
|
240
|
-
}
|
|
241
|
-
return existingAssistant;
|
|
242
|
-
}
|
|
243
|
-
const now = new Date();
|
|
244
|
-
STORE.assistants[assistant_id] ??= {
|
|
245
|
-
assistant_id: assistant_id,
|
|
246
|
-
version: 1,
|
|
247
|
-
config: options.config ?? {},
|
|
248
|
-
context: options.context ?? {},
|
|
249
|
-
created_at: now,
|
|
250
|
-
updated_at: now,
|
|
251
|
-
graph_id: options.graph_id,
|
|
252
|
-
metadata: mutable.metadata ?? {},
|
|
253
|
-
name: options.name || options.graph_id,
|
|
254
|
-
description: options.description ?? null,
|
|
255
|
-
};
|
|
256
|
-
STORE.assistant_versions.push({
|
|
257
|
-
assistant_id: assistant_id,
|
|
258
|
-
version: 1,
|
|
259
|
-
graph_id: options.graph_id,
|
|
260
|
-
config: options.config ?? {},
|
|
261
|
-
context: options.context ?? {},
|
|
262
|
-
metadata: mutable.metadata ?? {},
|
|
263
|
-
created_at: now,
|
|
264
|
-
name: options.name || options.graph_id,
|
|
265
|
-
description: options.description ?? null,
|
|
266
|
-
});
|
|
267
|
-
return STORE.assistants[assistant_id];
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
async patch(assistantId, options, auth) {
|
|
271
|
-
const [filters, mutable] = await handleAuthEvent(auth, "assistants:update", {
|
|
272
|
-
assistant_id: assistantId,
|
|
273
|
-
graph_id: options?.graph_id,
|
|
274
|
-
config: options?.config,
|
|
275
|
-
metadata: options?.metadata,
|
|
276
|
-
name: options?.name,
|
|
277
|
-
description: options?.description,
|
|
278
|
-
});
|
|
279
|
-
return this.conn.with((STORE) => {
|
|
280
|
-
const assistant = STORE.assistants[assistantId];
|
|
281
|
-
if (!assistant) {
|
|
282
|
-
throw new HTTPException(404, { message: "Assistant not found" });
|
|
283
|
-
}
|
|
284
|
-
if (!isAuthMatching(assistant["metadata"], filters)) {
|
|
285
|
-
throw new HTTPException(404, { message: "Assistant not found" });
|
|
286
|
-
}
|
|
287
|
-
const now = new Date();
|
|
288
|
-
const metadata = mutable.metadata != null
|
|
289
|
-
? {
|
|
290
|
-
...assistant["metadata"],
|
|
291
|
-
...mutable.metadata,
|
|
292
|
-
}
|
|
293
|
-
: null;
|
|
294
|
-
if (options?.graph_id != null) {
|
|
295
|
-
assistant["graph_id"] = options?.graph_id ?? assistant["graph_id"];
|
|
296
|
-
}
|
|
297
|
-
if (options?.config != null) {
|
|
298
|
-
assistant["config"] = options?.config ?? assistant["config"];
|
|
299
|
-
}
|
|
300
|
-
if (options?.context != null) {
|
|
301
|
-
assistant["context"] = options?.context ?? assistant["context"];
|
|
302
|
-
}
|
|
303
|
-
if (options?.name != null) {
|
|
304
|
-
assistant["name"] = options?.name ?? assistant["name"];
|
|
305
|
-
}
|
|
306
|
-
if (options?.description != null) {
|
|
307
|
-
assistant["description"] =
|
|
308
|
-
options?.description ?? assistant["description"];
|
|
309
|
-
}
|
|
310
|
-
if (metadata != null) {
|
|
311
|
-
assistant["metadata"] = metadata ?? assistant["metadata"];
|
|
312
|
-
}
|
|
313
|
-
assistant["updated_at"] = now;
|
|
314
|
-
const newVersion = Math.max(...STORE.assistant_versions
|
|
315
|
-
.filter((v) => v["assistant_id"] === assistantId)
|
|
316
|
-
.map((v) => v["version"])) + 1;
|
|
317
|
-
assistant.version = newVersion;
|
|
318
|
-
const newVersionEntry = {
|
|
319
|
-
assistant_id: assistantId,
|
|
320
|
-
version: newVersion,
|
|
321
|
-
graph_id: options?.graph_id ?? assistant["graph_id"],
|
|
322
|
-
config: options?.config ?? assistant["config"],
|
|
323
|
-
context: options?.context ?? assistant["context"],
|
|
324
|
-
name: options?.name ?? assistant["name"],
|
|
325
|
-
description: options?.description ?? assistant["description"],
|
|
326
|
-
metadata: metadata ?? assistant["metadata"],
|
|
327
|
-
created_at: now,
|
|
328
|
-
};
|
|
329
|
-
STORE.assistant_versions.push(newVersionEntry);
|
|
330
|
-
return assistant;
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
async delete(assistant_id, auth) {
|
|
334
|
-
const [filters] = await handleAuthEvent(auth, "assistants:delete", {
|
|
335
|
-
assistant_id,
|
|
336
|
-
});
|
|
337
|
-
return this.conn.with((STORE) => {
|
|
338
|
-
const assistant = STORE.assistants[assistant_id];
|
|
339
|
-
if (!assistant) {
|
|
340
|
-
throw new HTTPException(404, { message: "Assistant not found" });
|
|
341
|
-
}
|
|
342
|
-
if (!isAuthMatching(assistant["metadata"], filters)) {
|
|
343
|
-
throw new HTTPException(404, { message: "Assistant not found" });
|
|
344
|
-
}
|
|
345
|
-
delete STORE.assistants[assistant_id];
|
|
346
|
-
// Cascade delete for assistant versions and crons
|
|
347
|
-
STORE.assistant_versions = STORE.assistant_versions.filter((v) => v["assistant_id"] !== assistant_id);
|
|
348
|
-
for (const run of Object.values(STORE.runs)) {
|
|
349
|
-
if (run["assistant_id"] === assistant_id) {
|
|
350
|
-
delete STORE.runs[run["run_id"]];
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
return [assistant.assistant_id];
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
async setLatest(assistant_id, version, auth) {
|
|
357
|
-
const [filters] = await handleAuthEvent(auth, "assistants:update", {
|
|
358
|
-
assistant_id,
|
|
359
|
-
version,
|
|
360
|
-
});
|
|
361
|
-
return this.conn.with((STORE) => {
|
|
362
|
-
const assistant = STORE.assistants[assistant_id];
|
|
363
|
-
if (!assistant) {
|
|
364
|
-
throw new HTTPException(404, { message: "Assistant not found" });
|
|
365
|
-
}
|
|
366
|
-
if (!isAuthMatching(assistant["metadata"], filters)) {
|
|
367
|
-
throw new HTTPException(404, { message: "Assistant not found" });
|
|
368
|
-
}
|
|
369
|
-
const assistantVersion = STORE.assistant_versions.find((v) => v["assistant_id"] === assistant_id && v["version"] === version);
|
|
370
|
-
if (!assistantVersion)
|
|
371
|
-
throw new HTTPException(404, {
|
|
372
|
-
message: "Assistant version not found",
|
|
373
|
-
});
|
|
374
|
-
const now = new Date();
|
|
375
|
-
STORE.assistants[assistant_id] = {
|
|
376
|
-
...assistant,
|
|
377
|
-
config: assistantVersion["config"],
|
|
378
|
-
metadata: assistantVersion["metadata"],
|
|
379
|
-
version: assistantVersion["version"],
|
|
380
|
-
name: assistantVersion["name"],
|
|
381
|
-
updated_at: now,
|
|
382
|
-
};
|
|
383
|
-
return STORE.assistants[assistant_id];
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
async getVersions(assistant_id, options, auth) {
|
|
387
|
-
const [filters] = await handleAuthEvent(auth, "assistants:read", {
|
|
388
|
-
assistant_id,
|
|
389
|
-
});
|
|
390
|
-
return this.conn.with((STORE) => {
|
|
391
|
-
const versions = STORE.assistant_versions
|
|
392
|
-
.filter((version) => {
|
|
393
|
-
if (version["assistant_id"] !== assistant_id)
|
|
394
|
-
return false;
|
|
395
|
-
if (options.metadata != null &&
|
|
396
|
-
!isJsonbContained(version["metadata"], options.metadata)) {
|
|
397
|
-
return false;
|
|
398
|
-
}
|
|
399
|
-
if (!isAuthMatching(version["metadata"], filters)) {
|
|
400
|
-
return false;
|
|
401
|
-
}
|
|
402
|
-
return true;
|
|
403
|
-
})
|
|
404
|
-
.sort((a, b) => b["version"] - a["version"]);
|
|
405
|
-
return versions.slice(options.offset, options.offset + options.limit);
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
async count(options, auth) {
|
|
409
|
-
const [filters] = await handleAuthEvent(auth, "assistants:search", {
|
|
410
|
-
graph_id: options.graph_id,
|
|
411
|
-
metadata: options.metadata,
|
|
412
|
-
limit: 0,
|
|
413
|
-
offset: 0,
|
|
414
|
-
});
|
|
415
|
-
return this.conn.with((STORE) => {
|
|
416
|
-
return Object.values(STORE.assistants).filter((assistant) => {
|
|
417
|
-
if (options.graph_id != null &&
|
|
418
|
-
assistant["graph_id"] !== options.graph_id) {
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
421
|
-
if (options.name != null &&
|
|
422
|
-
!assistant["name"].toLowerCase().includes(options.name.toLowerCase())) {
|
|
423
|
-
return false;
|
|
424
|
-
}
|
|
425
|
-
if (options.metadata != null &&
|
|
426
|
-
!isJsonbContained(assistant["metadata"], options.metadata)) {
|
|
427
|
-
return false;
|
|
428
|
-
}
|
|
429
|
-
if (!isAuthMatching(assistant["metadata"], filters)) {
|
|
430
|
-
return false;
|
|
431
|
-
}
|
|
432
|
-
return true;
|
|
433
|
-
}).length;
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
export class FileSystemThreads {
|
|
438
|
-
conn;
|
|
439
|
-
state;
|
|
440
|
-
constructor(conn) {
|
|
441
|
-
this.conn = conn;
|
|
442
|
-
this.state = new FileSystemThreads.State(conn, this);
|
|
443
|
-
}
|
|
444
|
-
async *search(options, auth) {
|
|
445
|
-
const [filters] = await handleAuthEvent(auth, "threads:search", {
|
|
446
|
-
metadata: options.metadata,
|
|
447
|
-
ids: options.ids,
|
|
448
|
-
status: options.status,
|
|
449
|
-
values: options.values,
|
|
450
|
-
limit: options.limit,
|
|
451
|
-
offset: options.offset,
|
|
452
|
-
});
|
|
453
|
-
yield* this.conn.withGenerator(async function* (STORE) {
|
|
454
|
-
const filtered = Object.values(STORE.threads)
|
|
455
|
-
.filter((thread) => {
|
|
456
|
-
if (options.ids != null && options.ids.length > 0) {
|
|
457
|
-
if (!options.ids.includes(thread["thread_id"]))
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
if (options.metadata != null &&
|
|
461
|
-
!isJsonbContained(thread["metadata"], options.metadata))
|
|
462
|
-
return false;
|
|
463
|
-
if (options.values != null &&
|
|
464
|
-
typeof thread["values"] !== "undefined" &&
|
|
465
|
-
!isJsonbContained(thread["values"], options.values))
|
|
466
|
-
return false;
|
|
467
|
-
if (options.status != null && thread["status"] !== options.status)
|
|
468
|
-
return false;
|
|
469
|
-
if (!isAuthMatching(thread["metadata"], filters))
|
|
470
|
-
return false;
|
|
471
|
-
return true;
|
|
472
|
-
})
|
|
473
|
-
.sort((a, b) => {
|
|
474
|
-
const sortBy = options.sort_by ?? "created_at";
|
|
475
|
-
const sortOrder = options.sort_order ?? "desc";
|
|
476
|
-
if (sortBy === "created_at" || sortBy === "updated_at") {
|
|
477
|
-
const aTime = a[sortBy].getTime();
|
|
478
|
-
const bTime = b[sortBy].getTime();
|
|
479
|
-
return sortOrder === "desc" ? bTime - aTime : aTime - bTime;
|
|
480
|
-
}
|
|
481
|
-
if (sortBy === "thread_id" || sortBy === "status") {
|
|
482
|
-
const aVal = a[sortBy];
|
|
483
|
-
const bVal = b[sortBy];
|
|
484
|
-
return sortOrder === "desc"
|
|
485
|
-
? bVal.localeCompare(aVal)
|
|
486
|
-
: aVal.localeCompare(bVal);
|
|
487
|
-
}
|
|
488
|
-
return 0;
|
|
489
|
-
});
|
|
490
|
-
// Calculate total count before pagination
|
|
491
|
-
const total = filtered.length;
|
|
492
|
-
for (const thread of filtered.slice(options.offset, options.offset + options.limit)) {
|
|
493
|
-
yield { thread, total };
|
|
494
|
-
}
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
// TODO: make this accept `undefined`
|
|
498
|
-
async get(thread_id, auth) {
|
|
499
|
-
const [filters] = await handleAuthEvent(auth, "threads:read", {
|
|
500
|
-
thread_id,
|
|
501
|
-
});
|
|
502
|
-
return this.conn.with((STORE) => {
|
|
503
|
-
const result = STORE.threads[thread_id];
|
|
504
|
-
if (result == null) {
|
|
505
|
-
throw new HTTPException(404, {
|
|
506
|
-
message: `Thread with ID ${thread_id} not found`,
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
if (!isAuthMatching(result["metadata"], filters)) {
|
|
510
|
-
throw new HTTPException(404, {
|
|
511
|
-
message: `Thread with ID ${thread_id} not found`,
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
return result;
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
async put(thread_id, options, auth) {
|
|
518
|
-
const [filters, mutable] = await handleAuthEvent(auth, "threads:create", {
|
|
519
|
-
thread_id,
|
|
520
|
-
metadata: options.metadata,
|
|
521
|
-
if_exists: options.if_exists,
|
|
522
|
-
});
|
|
523
|
-
return this.conn.with((STORE) => {
|
|
524
|
-
const now = new Date();
|
|
525
|
-
if (STORE.threads[thread_id] != null) {
|
|
526
|
-
const existingThread = STORE.threads[thread_id];
|
|
527
|
-
if (!isAuthMatching(existingThread["metadata"], filters)) {
|
|
528
|
-
throw new HTTPException(409, { message: "Thread already exists" });
|
|
529
|
-
}
|
|
530
|
-
if (options?.if_exists === "raise") {
|
|
531
|
-
throw new HTTPException(409, { message: "Thread already exists" });
|
|
532
|
-
}
|
|
533
|
-
return existingThread;
|
|
534
|
-
}
|
|
535
|
-
STORE.threads[thread_id] ??= {
|
|
536
|
-
thread_id: thread_id,
|
|
537
|
-
created_at: now,
|
|
538
|
-
updated_at: now,
|
|
539
|
-
metadata: mutable?.metadata ?? {},
|
|
540
|
-
status: "idle",
|
|
541
|
-
config: {},
|
|
542
|
-
values: undefined,
|
|
543
|
-
};
|
|
544
|
-
return STORE.threads[thread_id];
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
async patch(threadId, options, auth) {
|
|
548
|
-
const [filters, mutable] = await handleAuthEvent(auth, "threads:update", {
|
|
549
|
-
thread_id: threadId,
|
|
550
|
-
metadata: options.metadata,
|
|
551
|
-
});
|
|
552
|
-
return this.conn.with((STORE) => {
|
|
553
|
-
const thread = STORE.threads[threadId];
|
|
554
|
-
if (!thread) {
|
|
555
|
-
throw new HTTPException(404, { message: "Thread not found" });
|
|
556
|
-
}
|
|
557
|
-
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
558
|
-
// TODO: is this correct status code?
|
|
559
|
-
throw new HTTPException(404, { message: "Thread not found" });
|
|
560
|
-
}
|
|
561
|
-
const now = new Date();
|
|
562
|
-
if (mutable.metadata != null) {
|
|
563
|
-
thread["metadata"] = {
|
|
564
|
-
...thread["metadata"],
|
|
565
|
-
...mutable.metadata,
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
thread["updated_at"] = now;
|
|
569
|
-
return thread;
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
async setStatus(threadId, options) {
|
|
573
|
-
return this.conn.with((STORE) => {
|
|
574
|
-
const thread = STORE.threads[threadId];
|
|
575
|
-
if (!thread)
|
|
576
|
-
throw new HTTPException(404, { message: "Thread not found" });
|
|
577
|
-
let hasNext = false;
|
|
578
|
-
if (options.checkpoint != null) {
|
|
579
|
-
hasNext = options.checkpoint.next.length > 0;
|
|
580
|
-
}
|
|
581
|
-
const hasPendingRuns = Object.values(STORE.runs).some((run) => run["thread_id"] === threadId && run["status"] === "pending");
|
|
582
|
-
let status = "idle";
|
|
583
|
-
if (options.exception != null) {
|
|
584
|
-
status = "error";
|
|
585
|
-
}
|
|
586
|
-
else if (hasNext) {
|
|
587
|
-
status = "interrupted";
|
|
588
|
-
}
|
|
589
|
-
else if (hasPendingRuns) {
|
|
590
|
-
status = "busy";
|
|
591
|
-
}
|
|
592
|
-
const now = new Date();
|
|
593
|
-
thread.updated_at = now;
|
|
594
|
-
thread.status = status;
|
|
595
|
-
thread.values =
|
|
596
|
-
options.checkpoint != null ? options.checkpoint.values : undefined;
|
|
597
|
-
thread.interrupts =
|
|
598
|
-
options.checkpoint != null
|
|
599
|
-
? options.checkpoint.tasks.reduce((acc, task) => {
|
|
600
|
-
if (task.interrupts)
|
|
601
|
-
acc[task.id] = task.interrupts;
|
|
602
|
-
return acc;
|
|
603
|
-
}, {})
|
|
604
|
-
: undefined;
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
async delete(thread_id, auth) {
|
|
608
|
-
const [filters] = await handleAuthEvent(auth, "threads:delete", {
|
|
609
|
-
thread_id,
|
|
610
|
-
});
|
|
611
|
-
return this.conn.with((STORE) => {
|
|
612
|
-
const thread = STORE.threads[thread_id];
|
|
613
|
-
if (!thread) {
|
|
614
|
-
throw new HTTPException(404, {
|
|
615
|
-
message: `Thread with ID ${thread_id} not found`,
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
619
|
-
throw new HTTPException(404, {
|
|
620
|
-
message: `Thread with ID ${thread_id} not found`,
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
delete STORE.threads[thread_id];
|
|
624
|
-
for (const run of Object.values(STORE.runs)) {
|
|
625
|
-
if (run["thread_id"] === thread_id) {
|
|
626
|
-
delete STORE.runs[run["run_id"]];
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
checkpointer.delete(thread_id, null);
|
|
630
|
-
return [thread.thread_id];
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
async copy(thread_id, auth) {
|
|
634
|
-
const [filters] = await handleAuthEvent(auth, "threads:read", {
|
|
635
|
-
thread_id,
|
|
636
|
-
});
|
|
637
|
-
const fromThread = await this.conn.with((STORE) => {
|
|
638
|
-
const thread = STORE.threads[thread_id];
|
|
639
|
-
if (!thread)
|
|
640
|
-
throw new HTTPException(409, { message: "Thread not found" });
|
|
641
|
-
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
642
|
-
throw new HTTPException(409, { message: "Thread not found" });
|
|
643
|
-
}
|
|
644
|
-
return thread;
|
|
645
|
-
});
|
|
646
|
-
const newThreadId = uuid4();
|
|
647
|
-
const now = new Date();
|
|
648
|
-
const newMetadata = { ...fromThread.metadata, thread_id: newThreadId };
|
|
649
|
-
await handleAuthEvent(auth, "threads:create", {
|
|
650
|
-
thread_id: newThreadId,
|
|
651
|
-
metadata: newMetadata,
|
|
652
|
-
});
|
|
653
|
-
return this.conn.with((STORE) => {
|
|
654
|
-
STORE.threads[newThreadId] = {
|
|
655
|
-
thread_id: newThreadId,
|
|
656
|
-
created_at: now,
|
|
657
|
-
updated_at: now,
|
|
658
|
-
metadata: newMetadata,
|
|
659
|
-
config: {},
|
|
660
|
-
status: "idle",
|
|
661
|
-
};
|
|
662
|
-
checkpointer.copy(thread_id, newThreadId);
|
|
663
|
-
return STORE.threads[newThreadId];
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
async count(options, auth) {
|
|
667
|
-
const [filters] = await handleAuthEvent(auth, "threads:search", {
|
|
668
|
-
metadata: options.metadata,
|
|
669
|
-
values: options.values,
|
|
670
|
-
status: options.status,
|
|
671
|
-
limit: 0,
|
|
672
|
-
offset: 0,
|
|
673
|
-
});
|
|
674
|
-
return this.conn.with((STORE) => {
|
|
675
|
-
return Object.values(STORE.threads).filter((thread) => {
|
|
676
|
-
if (options.metadata != null &&
|
|
677
|
-
!isJsonbContained(thread["metadata"], options.metadata)) {
|
|
678
|
-
return false;
|
|
679
|
-
}
|
|
680
|
-
if (options.values != null &&
|
|
681
|
-
typeof thread["values"] !== "undefined" &&
|
|
682
|
-
!isJsonbContained(thread["values"], options.values)) {
|
|
683
|
-
return false;
|
|
684
|
-
}
|
|
685
|
-
if (options.status != null && thread["status"] !== options.status) {
|
|
686
|
-
return false;
|
|
687
|
-
}
|
|
688
|
-
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
689
|
-
return false;
|
|
690
|
-
}
|
|
691
|
-
return true;
|
|
692
|
-
}).length;
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
static State = class {
|
|
696
|
-
conn;
|
|
697
|
-
threads;
|
|
698
|
-
constructor(conn, threads) {
|
|
699
|
-
this.conn = conn;
|
|
700
|
-
this.threads = threads;
|
|
701
|
-
}
|
|
702
|
-
async get(config, options, auth) {
|
|
703
|
-
const subgraphs = options.subgraphs ?? false;
|
|
704
|
-
const threadId = config.configurable?.thread_id;
|
|
705
|
-
const thread = threadId
|
|
706
|
-
? await this.threads.get(threadId, auth)
|
|
707
|
-
: undefined;
|
|
708
|
-
const metadata = thread?.metadata ?? {};
|
|
709
|
-
const graphId = metadata?.graph_id;
|
|
710
|
-
if (!thread || graphId == null) {
|
|
711
|
-
return {
|
|
712
|
-
values: {},
|
|
713
|
-
next: [],
|
|
714
|
-
config: {},
|
|
715
|
-
metadata: undefined,
|
|
716
|
-
createdAt: undefined,
|
|
717
|
-
parentConfig: undefined,
|
|
718
|
-
tasks: [],
|
|
719
|
-
};
|
|
720
|
-
}
|
|
721
|
-
const graph = await getGraph(graphId, thread.config, {
|
|
722
|
-
checkpointer,
|
|
723
|
-
store,
|
|
724
|
-
});
|
|
725
|
-
const result = await graph.getState(config, { subgraphs });
|
|
726
|
-
if (result.metadata != null &&
|
|
727
|
-
"checkpoint_ns" in result.metadata &&
|
|
728
|
-
result.metadata["checkpoint_ns"] === "") {
|
|
729
|
-
delete result.metadata["checkpoint_ns"];
|
|
730
|
-
}
|
|
731
|
-
return result;
|
|
732
|
-
}
|
|
733
|
-
async post(config, values, asNode, auth) {
|
|
734
|
-
const threadId = config.configurable?.thread_id;
|
|
735
|
-
const [filters] = await handleAuthEvent(auth, "threads:update", {
|
|
736
|
-
thread_id: threadId,
|
|
737
|
-
});
|
|
738
|
-
const thread = threadId
|
|
739
|
-
? await this.threads.get(threadId, auth)
|
|
740
|
-
: undefined;
|
|
741
|
-
if (!thread)
|
|
742
|
-
throw new HTTPException(404, {
|
|
743
|
-
message: `Thread ${threadId} not found`,
|
|
744
|
-
});
|
|
745
|
-
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
746
|
-
throw new HTTPException(403);
|
|
747
|
-
}
|
|
748
|
-
// do a check if there are no pending runs
|
|
749
|
-
await this.conn.with(async (STORE) => {
|
|
750
|
-
if (Object.values(STORE.runs).some((run) => run.thread_id === threadId &&
|
|
751
|
-
(run.status === "pending" || run.status === "running"))) {
|
|
752
|
-
throw new HTTPException(409, { message: "Thread is busy" });
|
|
753
|
-
}
|
|
754
|
-
});
|
|
755
|
-
const graphId = thread.metadata?.graph_id;
|
|
756
|
-
if (graphId == null) {
|
|
757
|
-
throw new HTTPException(400, {
|
|
758
|
-
message: `Thread ${threadId} has no graph ID`,
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
config.configurable ??= {};
|
|
762
|
-
config.configurable.graph_id ??= graphId;
|
|
763
|
-
const graph = await getGraph(graphId, thread.config, {
|
|
764
|
-
checkpointer,
|
|
765
|
-
store,
|
|
766
|
-
});
|
|
767
|
-
const updateConfig = structuredClone(config);
|
|
768
|
-
updateConfig.configurable ??= {};
|
|
769
|
-
updateConfig.configurable.checkpoint_ns ??= "";
|
|
770
|
-
const nextConfig = await graph.updateState(updateConfig, values, asNode);
|
|
771
|
-
const state = await this.get(config, { subgraphs: false }, auth);
|
|
772
|
-
// update thread values
|
|
773
|
-
await this.conn.with(async (STORE) => {
|
|
774
|
-
for (const thread of Object.values(STORE.threads)) {
|
|
775
|
-
if (thread.thread_id === threadId) {
|
|
776
|
-
thread.values = state.values;
|
|
777
|
-
break;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
|
-
return { checkpoint: nextConfig.configurable };
|
|
782
|
-
}
|
|
783
|
-
async bulk(config, supersteps, auth) {
|
|
784
|
-
const threadId = config.configurable?.thread_id;
|
|
785
|
-
if (!threadId)
|
|
786
|
-
return [];
|
|
787
|
-
const [filters] = await handleAuthEvent(auth, "threads:update", {
|
|
788
|
-
thread_id: threadId,
|
|
789
|
-
});
|
|
790
|
-
const thread = await this.threads.get(threadId, auth);
|
|
791
|
-
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
792
|
-
throw new HTTPException(403);
|
|
793
|
-
}
|
|
794
|
-
const graphId = thread.metadata?.graph_id;
|
|
795
|
-
if (graphId == null) {
|
|
796
|
-
throw new HTTPException(400, {
|
|
797
|
-
message: `Thread ${threadId} has no graph ID`,
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
config.configurable ??= {};
|
|
801
|
-
config.configurable.graph_id ??= graphId;
|
|
802
|
-
const graph = await getGraph(graphId, thread.config, {
|
|
803
|
-
checkpointer,
|
|
804
|
-
store,
|
|
805
|
-
});
|
|
806
|
-
const updateConfig = structuredClone(config);
|
|
807
|
-
updateConfig.configurable ??= {};
|
|
808
|
-
updateConfig.configurable.checkpoint_ns ??= "";
|
|
809
|
-
const nextConfig = await graph.bulkUpdateState(updateConfig, supersteps.map((i) => ({
|
|
810
|
-
updates: i.updates.map((j) => ({
|
|
811
|
-
values: j.command != null ? getLangGraphCommand(j.command) : j.values,
|
|
812
|
-
asNode: j.as_node,
|
|
813
|
-
})),
|
|
814
|
-
})));
|
|
815
|
-
const state = await this.get(config, { subgraphs: false }, auth);
|
|
816
|
-
// update thread values
|
|
817
|
-
await this.conn.with(async (STORE) => {
|
|
818
|
-
for (const thread of Object.values(STORE.threads)) {
|
|
819
|
-
if (thread.thread_id === threadId) {
|
|
820
|
-
thread.values = state.values;
|
|
821
|
-
break;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
return { checkpoint: nextConfig.configurable };
|
|
826
|
-
}
|
|
827
|
-
async list(config, options, auth) {
|
|
828
|
-
const threadId = config.configurable?.thread_id;
|
|
829
|
-
if (!threadId)
|
|
830
|
-
return [];
|
|
831
|
-
const [filters] = await handleAuthEvent(auth, "threads:read", {
|
|
832
|
-
thread_id: threadId,
|
|
833
|
-
});
|
|
834
|
-
const thread = await this.threads.get(threadId, auth);
|
|
835
|
-
if (!isAuthMatching(thread["metadata"], filters))
|
|
836
|
-
return [];
|
|
837
|
-
const graphId = thread.metadata?.graph_id;
|
|
838
|
-
if (graphId == null)
|
|
839
|
-
return [];
|
|
840
|
-
const graph = await getGraph(graphId, thread.config, {
|
|
841
|
-
checkpointer,
|
|
842
|
-
store,
|
|
843
|
-
});
|
|
844
|
-
const before = typeof options?.before === "string"
|
|
845
|
-
? { configurable: { checkpoint_id: options.before } }
|
|
846
|
-
: options?.before;
|
|
847
|
-
const states = [];
|
|
848
|
-
for await (const state of graph.getStateHistory(config, {
|
|
849
|
-
limit: options?.limit ?? 10,
|
|
850
|
-
before,
|
|
851
|
-
filter: options?.metadata,
|
|
852
|
-
})) {
|
|
853
|
-
states.push(state);
|
|
854
|
-
}
|
|
855
|
-
return states;
|
|
856
|
-
}
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
export class FileSystemRuns {
|
|
860
|
-
conn;
|
|
861
|
-
threads;
|
|
862
|
-
stream;
|
|
863
|
-
constructor(conn) {
|
|
864
|
-
this.conn = conn;
|
|
865
|
-
this.threads = new FileSystemThreads(conn);
|
|
866
|
-
this.stream = new FileSystemRuns.Stream(conn, this);
|
|
867
|
-
}
|
|
868
|
-
async *next() {
|
|
869
|
-
yield* this.conn.withGenerator(async function* (STORE, options) {
|
|
870
|
-
const now = new Date();
|
|
871
|
-
const pendingRunIds = Object.values(STORE.runs)
|
|
872
|
-
.filter((run) => run.status === "pending" && run.created_at < now)
|
|
873
|
-
.sort((a, b) => a.created_at.getTime() - b.created_at.getTime())
|
|
874
|
-
.map((run) => run.run_id);
|
|
875
|
-
if (!pendingRunIds.length) {
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
for (const runId of pendingRunIds) {
|
|
879
|
-
if (StreamManager.isLocked(runId))
|
|
880
|
-
continue;
|
|
881
|
-
try {
|
|
882
|
-
const signal = StreamManager.lock(runId);
|
|
883
|
-
const run = STORE.runs[runId];
|
|
884
|
-
if (!run)
|
|
885
|
-
continue;
|
|
886
|
-
const threadId = run.thread_id;
|
|
887
|
-
const thread = STORE.threads[threadId];
|
|
888
|
-
if (!thread) {
|
|
889
|
-
logger.warn(`Unexpected missing thread in Runs.next: ${threadId}`);
|
|
890
|
-
continue;
|
|
891
|
-
}
|
|
892
|
-
// is the run still valid?
|
|
893
|
-
if (run.status !== "pending")
|
|
894
|
-
continue;
|
|
895
|
-
if (Object.values(STORE.runs).some((run) => run.thread_id === threadId && run.status === "running")) {
|
|
896
|
-
continue;
|
|
897
|
-
}
|
|
898
|
-
options.schedulePersist();
|
|
899
|
-
STORE.retry_counter[runId] ??= 0;
|
|
900
|
-
STORE.retry_counter[runId] += 1;
|
|
901
|
-
STORE.runs[runId].status = "running";
|
|
902
|
-
yield { run, attempt: STORE.retry_counter[runId], signal };
|
|
903
|
-
}
|
|
904
|
-
finally {
|
|
905
|
-
StreamManager.unlock(runId);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
async put(runId, assistantId, kwargs, options, auth) {
|
|
911
|
-
return this.conn.with(async (STORE) => {
|
|
912
|
-
const assistant = STORE.assistants[assistantId];
|
|
913
|
-
if (!assistant) {
|
|
914
|
-
throw new HTTPException(404, {
|
|
915
|
-
message: `No assistant found for "${assistantId}". Make sure the assistant ID is for a valid assistant or a valid graph ID.`,
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
const ifNotExists = options?.ifNotExists ?? "reject";
|
|
919
|
-
const multitaskStrategy = options?.multitaskStrategy ?? "reject";
|
|
920
|
-
const afterSeconds = options?.afterSeconds ?? 0;
|
|
921
|
-
const status = options?.status ?? "pending";
|
|
922
|
-
let threadId = options?.threadId;
|
|
923
|
-
const [filters, mutable] = await handleAuthEvent(auth, "threads:create_run", {
|
|
924
|
-
thread_id: threadId,
|
|
925
|
-
assistant_id: assistantId,
|
|
926
|
-
run_id: runId,
|
|
927
|
-
status: status,
|
|
928
|
-
metadata: options?.metadata ?? {},
|
|
929
|
-
prevent_insert_if_inflight: options?.preventInsertInInflight,
|
|
930
|
-
multitask_strategy: multitaskStrategy,
|
|
931
|
-
if_not_exists: ifNotExists,
|
|
932
|
-
after_seconds: afterSeconds,
|
|
933
|
-
kwargs,
|
|
934
|
-
});
|
|
935
|
-
const metadata = mutable.metadata ?? {};
|
|
936
|
-
const config = kwargs.config ?? {};
|
|
937
|
-
const existingThread = Object.values(STORE.threads).find((thread) => thread.thread_id === threadId);
|
|
938
|
-
if (existingThread &&
|
|
939
|
-
!isAuthMatching(existingThread["metadata"], filters)) {
|
|
940
|
-
throw new HTTPException(404);
|
|
941
|
-
}
|
|
942
|
-
const now = new Date();
|
|
943
|
-
if (!existingThread && (threadId == null || ifNotExists === "create")) {
|
|
944
|
-
threadId ??= uuid4();
|
|
945
|
-
const thread = {
|
|
946
|
-
thread_id: threadId,
|
|
947
|
-
status: "busy",
|
|
948
|
-
metadata: {
|
|
949
|
-
graph_id: assistant.graph_id,
|
|
950
|
-
assistant_id: assistantId,
|
|
951
|
-
...metadata,
|
|
952
|
-
},
|
|
953
|
-
config: Object.assign({}, assistant.config, config, {
|
|
954
|
-
configurable: Object.assign({}, assistant.config?.configurable, config?.configurable),
|
|
955
|
-
}),
|
|
956
|
-
created_at: now,
|
|
957
|
-
updated_at: now,
|
|
958
|
-
};
|
|
959
|
-
STORE.threads[threadId] = thread;
|
|
960
|
-
}
|
|
961
|
-
else if (existingThread) {
|
|
962
|
-
if (existingThread.status !== "busy") {
|
|
963
|
-
existingThread.status = "busy";
|
|
964
|
-
existingThread.metadata = Object.assign({}, existingThread.metadata, {
|
|
965
|
-
graph_id: assistant.graph_id,
|
|
966
|
-
assistant_id: assistantId,
|
|
967
|
-
});
|
|
968
|
-
existingThread.config = Object.assign({}, assistant.config, existingThread.config, config, {
|
|
969
|
-
configurable: Object.assign({}, assistant.config?.configurable, existingThread?.config?.configurable, config?.configurable),
|
|
970
|
-
});
|
|
971
|
-
existingThread.updated_at = now;
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
else {
|
|
975
|
-
return [];
|
|
976
|
-
}
|
|
977
|
-
// if multitask_mode = reject, check for inflight runs
|
|
978
|
-
// and if there are any, return them to reject putting a new run
|
|
979
|
-
const inflightRuns = Object.values(STORE.runs).filter((run) => run.thread_id === threadId &&
|
|
980
|
-
(run.status === "pending" || run.status === "running"));
|
|
981
|
-
if (options?.preventInsertInInflight) {
|
|
982
|
-
if (inflightRuns.length > 0)
|
|
983
|
-
return inflightRuns;
|
|
984
|
-
}
|
|
985
|
-
// create new run
|
|
986
|
-
const configurable = Object.assign({}, assistant.config?.configurable, existingThread?.config?.configurable, config?.configurable, {
|
|
987
|
-
run_id: runId,
|
|
988
|
-
thread_id: threadId,
|
|
989
|
-
graph_id: assistant.graph_id,
|
|
990
|
-
assistant_id: assistantId,
|
|
991
|
-
user_id: config.configurable?.user_id ??
|
|
992
|
-
existingThread?.config?.configurable?.user_id ??
|
|
993
|
-
assistant.config?.configurable?.user_id ??
|
|
994
|
-
options?.userId,
|
|
995
|
-
});
|
|
996
|
-
const mergedMetadata = Object.assign({}, assistant.metadata, existingThread?.metadata, metadata);
|
|
997
|
-
const newRun = {
|
|
998
|
-
run_id: runId,
|
|
999
|
-
thread_id: threadId,
|
|
1000
|
-
assistant_id: assistantId,
|
|
1001
|
-
metadata: mergedMetadata,
|
|
1002
|
-
status: status,
|
|
1003
|
-
kwargs: Object.assign({}, kwargs, {
|
|
1004
|
-
config: Object.assign({}, assistant.config, config, { configurable }, { metadata: mergedMetadata }),
|
|
1005
|
-
context: typeof assistant.context !== "object" && assistant.context != null
|
|
1006
|
-
? assistant.context ?? kwargs.context
|
|
1007
|
-
: Object.assign({}, assistant.context, kwargs.context),
|
|
1008
|
-
}),
|
|
1009
|
-
multitask_strategy: multitaskStrategy,
|
|
1010
|
-
created_at: new Date(now.valueOf() + afterSeconds * 1000),
|
|
1011
|
-
updated_at: now,
|
|
1012
|
-
};
|
|
1013
|
-
STORE.runs[runId] = newRun;
|
|
1014
|
-
return [newRun, ...inflightRuns];
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
1017
|
-
async get(runId, thread_id, auth) {
|
|
1018
|
-
const [filters] = await handleAuthEvent(auth, "threads:read", {
|
|
1019
|
-
thread_id,
|
|
1020
|
-
});
|
|
1021
|
-
return this.conn.with(async (STORE) => {
|
|
1022
|
-
const run = STORE.runs[runId];
|
|
1023
|
-
if (!run ||
|
|
1024
|
-
run.run_id !== runId ||
|
|
1025
|
-
(thread_id != null && run.thread_id !== thread_id))
|
|
1026
|
-
return null;
|
|
1027
|
-
if (filters != null) {
|
|
1028
|
-
const thread = STORE.threads[run.thread_id];
|
|
1029
|
-
if (!isAuthMatching(thread["metadata"], filters))
|
|
1030
|
-
return null;
|
|
1031
|
-
}
|
|
1032
|
-
return run;
|
|
1033
|
-
});
|
|
1034
|
-
}
|
|
1035
|
-
async delete(run_id, thread_id, auth) {
|
|
1036
|
-
const [filters] = await handleAuthEvent(auth, "threads:delete", {
|
|
1037
|
-
run_id,
|
|
1038
|
-
thread_id,
|
|
1039
|
-
});
|
|
1040
|
-
return this.conn.with(async (STORE) => {
|
|
1041
|
-
const run = STORE.runs[run_id];
|
|
1042
|
-
if (!run || (thread_id != null && run.thread_id !== thread_id))
|
|
1043
|
-
throw new HTTPException(404, { message: "Run not found" });
|
|
1044
|
-
if (filters != null) {
|
|
1045
|
-
const thread = STORE.threads[run.thread_id];
|
|
1046
|
-
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
1047
|
-
throw new HTTPException(404, { message: "Run not found" });
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
if (thread_id != null)
|
|
1051
|
-
checkpointer.delete(thread_id, run_id);
|
|
1052
|
-
delete STORE.runs[run_id];
|
|
1053
|
-
return run.run_id;
|
|
1054
|
-
});
|
|
1055
|
-
}
|
|
1056
|
-
async wait(runId, threadId, auth) {
|
|
1057
|
-
const runStream = this.stream.join(runId, threadId, { ignore404: threadId == null, lastEventId: undefined }, auth);
|
|
1058
|
-
const lastChunk = new Promise(async (resolve, reject) => {
|
|
1059
|
-
try {
|
|
1060
|
-
let lastChunk = null;
|
|
1061
|
-
for await (const { event, data } of runStream) {
|
|
1062
|
-
if (event === "values") {
|
|
1063
|
-
lastChunk = data;
|
|
1064
|
-
}
|
|
1065
|
-
else if (event === "error") {
|
|
1066
|
-
lastChunk = { __error__: serializeError(data) };
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
resolve(lastChunk);
|
|
1070
|
-
}
|
|
1071
|
-
catch (error) {
|
|
1072
|
-
reject(error);
|
|
1073
|
-
}
|
|
1074
|
-
});
|
|
1075
|
-
return lastChunk;
|
|
1076
|
-
}
|
|
1077
|
-
async join(runId, threadId, auth) {
|
|
1078
|
-
// check if thread exists
|
|
1079
|
-
await this.threads.get(threadId, auth);
|
|
1080
|
-
const lastChunk = await this.wait(runId, threadId, auth);
|
|
1081
|
-
if (lastChunk != null)
|
|
1082
|
-
return lastChunk;
|
|
1083
|
-
const thread = await this.threads.get(threadId, auth);
|
|
1084
|
-
return thread.values ?? null;
|
|
1085
|
-
}
|
|
1086
|
-
async cancel(threadId, runIds, options, auth) {
|
|
1087
|
-
return this.conn.with(async (STORE) => {
|
|
1088
|
-
const action = options.action ?? "interrupt";
|
|
1089
|
-
const promises = [];
|
|
1090
|
-
const [filters] = await handleAuthEvent(auth, "threads:update", {
|
|
1091
|
-
thread_id: threadId,
|
|
1092
|
-
action,
|
|
1093
|
-
metadata: { run_ids: runIds, status: "pending" },
|
|
1094
|
-
});
|
|
1095
|
-
let foundRunsCount = 0;
|
|
1096
|
-
for (const runId of runIds) {
|
|
1097
|
-
const run = STORE.runs[runId];
|
|
1098
|
-
if (!run || (threadId != null && run.thread_id !== threadId))
|
|
1099
|
-
continue;
|
|
1100
|
-
if (filters != null) {
|
|
1101
|
-
const thread = STORE.threads[run.thread_id];
|
|
1102
|
-
if (!isAuthMatching(thread["metadata"], filters))
|
|
1103
|
-
continue;
|
|
1104
|
-
}
|
|
1105
|
-
foundRunsCount += 1;
|
|
1106
|
-
// send cancellation message
|
|
1107
|
-
const control = StreamManager.getControl(runId);
|
|
1108
|
-
control?.abort(options.action ?? "interrupt");
|
|
1109
|
-
if (run.status === "pending") {
|
|
1110
|
-
if (control || action !== "rollback") {
|
|
1111
|
-
run.status = "interrupted";
|
|
1112
|
-
run.updated_at = new Date();
|
|
1113
|
-
const thread = STORE.threads[run.thread_id];
|
|
1114
|
-
if (thread) {
|
|
1115
|
-
thread.status = "idle";
|
|
1116
|
-
thread.updated_at = new Date();
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
else {
|
|
1120
|
-
logger.info("Eagerly deleting unscheduled run with rollback action", {
|
|
1121
|
-
run_id: runId,
|
|
1122
|
-
thread_id: threadId,
|
|
1123
|
-
});
|
|
1124
|
-
promises.push(this.delete(runId, threadId, auth));
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
else {
|
|
1128
|
-
logger.warn("Attempted to cancel non-pending run.", {
|
|
1129
|
-
run_id: runId,
|
|
1130
|
-
status: run.status,
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
await Promise.all(promises);
|
|
1135
|
-
if (foundRunsCount === runIds.length) {
|
|
1136
|
-
logger.info("Cancelled runs", {
|
|
1137
|
-
run_ids: runIds,
|
|
1138
|
-
thread_id: threadId,
|
|
1139
|
-
action,
|
|
1140
|
-
});
|
|
1141
|
-
}
|
|
1142
|
-
else {
|
|
1143
|
-
throw new HTTPException(404, { message: "Run not found" });
|
|
1144
|
-
}
|
|
1145
|
-
});
|
|
1146
|
-
}
|
|
1147
|
-
async search(threadId, options, auth) {
|
|
1148
|
-
const [filters] = await handleAuthEvent(auth, "threads:search", {
|
|
1149
|
-
thread_id: threadId,
|
|
1150
|
-
metadata: options.metadata,
|
|
1151
|
-
status: options.status,
|
|
1152
|
-
});
|
|
1153
|
-
return this.conn.with(async (STORE) => {
|
|
1154
|
-
const runs = Object.values(STORE.runs).filter((run) => {
|
|
1155
|
-
if (run.thread_id !== threadId)
|
|
1156
|
-
return false;
|
|
1157
|
-
if (options?.status != null && run.status !== options.status)
|
|
1158
|
-
return false;
|
|
1159
|
-
if (options?.metadata != null &&
|
|
1160
|
-
!isJsonbContained(run.metadata, options.metadata))
|
|
1161
|
-
return false;
|
|
1162
|
-
if (filters != null) {
|
|
1163
|
-
const thread = STORE.threads[run.thread_id];
|
|
1164
|
-
if (!isAuthMatching(thread["metadata"], filters))
|
|
1165
|
-
return false;
|
|
1166
|
-
}
|
|
1167
|
-
return true;
|
|
1168
|
-
});
|
|
1169
|
-
return runs.slice(options?.offset ?? 0, options?.limit ?? 10);
|
|
1170
|
-
});
|
|
1171
|
-
}
|
|
1172
|
-
async setStatus(runId, status) {
|
|
1173
|
-
return this.conn.with(async (STORE) => {
|
|
1174
|
-
const run = STORE.runs[runId];
|
|
1175
|
-
if (!run)
|
|
1176
|
-
throw new Error(`Run ${runId} not found`);
|
|
1177
|
-
run.status = status;
|
|
1178
|
-
run.updated_at = new Date();
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
static Stream = class {
|
|
1182
|
-
conn;
|
|
1183
|
-
runs;
|
|
1184
|
-
constructor(conn, runs) {
|
|
1185
|
-
this.conn = conn;
|
|
1186
|
-
this.runs = runs;
|
|
1187
|
-
}
|
|
1188
|
-
async *join(runId, threadId, options, auth) {
|
|
1189
|
-
const conn = this.conn;
|
|
1190
|
-
const runs = this.runs;
|
|
1191
|
-
yield* conn.withGenerator(async function* (STORE) {
|
|
1192
|
-
// TODO: what if we're joining an already completed run? Should we check before?
|
|
1193
|
-
const signal = options?.cancelOnDisconnect;
|
|
1194
|
-
const queue = StreamManager.getQueue(runId, {
|
|
1195
|
-
ifNotFound: "create",
|
|
1196
|
-
resumable: options.lastEventId != null,
|
|
1197
|
-
});
|
|
1198
|
-
const [filters] = await handleAuthEvent(auth, "threads:read", {
|
|
1199
|
-
thread_id: threadId,
|
|
1200
|
-
});
|
|
1201
|
-
// TODO: consolidate into a single function
|
|
1202
|
-
if (filters != null && threadId != null) {
|
|
1203
|
-
const thread = STORE.threads[threadId];
|
|
1204
|
-
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
1205
|
-
yield {
|
|
1206
|
-
event: "error",
|
|
1207
|
-
data: { error: "Error", message: "404: Thread not found" },
|
|
1208
|
-
};
|
|
1209
|
-
return;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
let lastEventId = options?.lastEventId;
|
|
1213
|
-
while (!signal?.aborted) {
|
|
1214
|
-
try {
|
|
1215
|
-
const [id, message] = await queue.get({
|
|
1216
|
-
timeout: 500,
|
|
1217
|
-
signal,
|
|
1218
|
-
lastEventId,
|
|
1219
|
-
});
|
|
1220
|
-
lastEventId = id;
|
|
1221
|
-
if (message.topic === `run:${runId}:control`) {
|
|
1222
|
-
if (message.data === "done")
|
|
1223
|
-
break;
|
|
1224
|
-
}
|
|
1225
|
-
else {
|
|
1226
|
-
const streamTopic = message.topic.substring(`run:${runId}:stream:`.length);
|
|
1227
|
-
yield { id, event: streamTopic, data: message.data };
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
catch (error) {
|
|
1231
|
-
if (error instanceof AbortError)
|
|
1232
|
-
break;
|
|
1233
|
-
const run = await runs.get(runId, threadId, auth);
|
|
1234
|
-
if (run == null) {
|
|
1235
|
-
if (!options?.ignore404)
|
|
1236
|
-
yield { event: "error", data: "Run not found" };
|
|
1237
|
-
break;
|
|
1238
|
-
}
|
|
1239
|
-
else if (run.status !== "pending" && run.status !== "running") {
|
|
1240
|
-
break;
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
if (signal?.aborted && threadId != null) {
|
|
1245
|
-
await runs.cancel(threadId, [runId], { action: "interrupt" }, auth);
|
|
1246
|
-
}
|
|
1247
|
-
});
|
|
1248
|
-
}
|
|
1249
|
-
async publish(payload) {
|
|
1250
|
-
const queue = StreamManager.getQueue(payload.runId, {
|
|
1251
|
-
ifNotFound: "create",
|
|
1252
|
-
resumable: payload.resumable,
|
|
1253
|
-
});
|
|
1254
|
-
queue.push({
|
|
1255
|
-
topic: `run:${payload.runId}:stream:${payload.event}`,
|
|
1256
|
-
data: payload.data,
|
|
1257
|
-
});
|
|
1258
|
-
}
|
|
1259
|
-
};
|
|
1260
|
-
}
|
|
1261
|
-
export class Crons {
|
|
1262
|
-
}
|