@langchain/langgraph-api 0.0.19 → 0.0.21
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/dist/api/assistants.mjs +38 -36
- package/dist/api/runs.mjs +72 -30
- package/dist/api/store.mjs +28 -0
- package/dist/api/threads.mjs +13 -16
- package/dist/auth/custom.mjs +58 -0
- package/dist/auth/index.d.mts +40 -0
- package/dist/auth/index.mjs +120 -0
- package/dist/cli/spawn.d.mts +4 -0
- package/dist/cli/spawn.mjs +1 -0
- package/dist/graph/load.mjs +2 -2
- package/dist/queue.mjs +1 -1
- package/dist/schemas.mjs +1 -0
- package/dist/server.mjs +17 -4
- package/dist/storage/ops.mjs +318 -115
- package/dist/ui/load.mjs +7 -8
- package/package.json +15 -9
- package/dist/ui/bundler.d.mts +0 -21
- package/dist/ui/bundler.mjs +0 -86
- package/dist/ui/render.template.mts +0 -27
package/dist/storage/ops.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { logger } from "../logging.mjs";
|
|
|
7
7
|
import { serializeError } from "../utils/serde.mjs";
|
|
8
8
|
import { FileSystemPersistence } from "./persist.mjs";
|
|
9
9
|
import { getLangGraphCommand } from "../command.mjs";
|
|
10
|
+
import { handleAuthEvent, isAuthMatching } from "../auth/custom.mjs";
|
|
10
11
|
export const conn = new FileSystemPersistence(".langgraphjs_ops.json", () => ({
|
|
11
12
|
runs: {},
|
|
12
13
|
threads: {},
|
|
@@ -129,7 +130,13 @@ const isJsonbContained = (superset, subset) => {
|
|
|
129
130
|
return true;
|
|
130
131
|
};
|
|
131
132
|
export class Assistants {
|
|
132
|
-
static async *search(options) {
|
|
133
|
+
static async *search(options, auth) {
|
|
134
|
+
const [filters] = await handleAuthEvent(auth, "assistants:search", {
|
|
135
|
+
graph_id: options.graph_id,
|
|
136
|
+
metadata: options.metadata,
|
|
137
|
+
limit: options.limit,
|
|
138
|
+
offset: options.offset,
|
|
139
|
+
});
|
|
133
140
|
yield* conn.withGenerator(async function* (STORE) {
|
|
134
141
|
let filtered = Object.values(STORE.assistants)
|
|
135
142
|
.filter((assistant) => {
|
|
@@ -141,6 +148,9 @@ export class Assistants {
|
|
|
141
148
|
!isJsonbContained(assistant["metadata"], options.metadata)) {
|
|
142
149
|
return false;
|
|
143
150
|
}
|
|
151
|
+
if (!isAuthMatching(assistant["metadata"], filters)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
144
154
|
return true;
|
|
145
155
|
})
|
|
146
156
|
.sort((a, b) => {
|
|
@@ -153,55 +163,84 @@ export class Assistants {
|
|
|
153
163
|
}
|
|
154
164
|
});
|
|
155
165
|
}
|
|
156
|
-
static async get(
|
|
166
|
+
static async get(assistant_id, auth) {
|
|
167
|
+
const [filters] = await handleAuthEvent(auth, "assistants:read", {
|
|
168
|
+
assistant_id,
|
|
169
|
+
});
|
|
157
170
|
return conn.with((STORE) => {
|
|
158
|
-
const result = STORE.assistants[
|
|
171
|
+
const result = STORE.assistants[assistant_id];
|
|
159
172
|
if (result == null)
|
|
160
173
|
throw new HTTPException(404, { message: "Assistant not found" });
|
|
174
|
+
if (!isAuthMatching(result["metadata"], filters)) {
|
|
175
|
+
throw new HTTPException(404, { message: "Assistant not found" });
|
|
176
|
+
}
|
|
161
177
|
return { ...result, name: result.name ?? result.graph_id };
|
|
162
178
|
});
|
|
163
179
|
}
|
|
164
|
-
static async put(
|
|
180
|
+
static async put(assistant_id, options, auth) {
|
|
181
|
+
const [filters, mutable] = await handleAuthEvent(auth, "assistants:create", {
|
|
182
|
+
assistant_id,
|
|
183
|
+
config: options.config,
|
|
184
|
+
graph_id: options.graph_id,
|
|
185
|
+
metadata: options.metadata,
|
|
186
|
+
if_exists: options.if_exists,
|
|
187
|
+
name: options.name,
|
|
188
|
+
});
|
|
165
189
|
return conn.with((STORE) => {
|
|
166
|
-
if (STORE.assistants[
|
|
190
|
+
if (STORE.assistants[assistant_id] != null) {
|
|
191
|
+
const existingAssistant = STORE.assistants[assistant_id];
|
|
192
|
+
if (!isAuthMatching(existingAssistant?.metadata, filters)) {
|
|
193
|
+
throw new HTTPException(409, { message: "Assistant already exists" });
|
|
194
|
+
}
|
|
167
195
|
if (options.if_exists === "raise") {
|
|
168
196
|
throw new HTTPException(409, { message: "Assistant already exists" });
|
|
169
197
|
}
|
|
170
|
-
return
|
|
198
|
+
return existingAssistant;
|
|
171
199
|
}
|
|
172
200
|
const now = new Date();
|
|
173
|
-
STORE.assistants[
|
|
174
|
-
assistant_id:
|
|
201
|
+
STORE.assistants[assistant_id] ??= {
|
|
202
|
+
assistant_id: assistant_id,
|
|
175
203
|
version: 1,
|
|
176
204
|
config: options.config ?? {},
|
|
177
205
|
created_at: now,
|
|
178
206
|
updated_at: now,
|
|
179
207
|
graph_id: options.graph_id,
|
|
180
|
-
metadata:
|
|
208
|
+
metadata: mutable.metadata ?? {},
|
|
181
209
|
name: options.name || options.graph_id,
|
|
182
210
|
};
|
|
183
211
|
STORE.assistant_versions.push({
|
|
184
|
-
assistant_id:
|
|
212
|
+
assistant_id: assistant_id,
|
|
185
213
|
version: 1,
|
|
186
214
|
graph_id: options.graph_id,
|
|
187
215
|
config: options.config ?? {},
|
|
188
|
-
metadata:
|
|
216
|
+
metadata: mutable.metadata ?? {},
|
|
189
217
|
created_at: now,
|
|
190
218
|
name: options.name || options.graph_id,
|
|
191
219
|
});
|
|
192
|
-
return STORE.assistants[
|
|
220
|
+
return STORE.assistants[assistant_id];
|
|
193
221
|
});
|
|
194
222
|
}
|
|
195
|
-
static async patch(assistantId, options) {
|
|
223
|
+
static async patch(assistantId, options, auth) {
|
|
224
|
+
const [filters, mutable] = await handleAuthEvent(auth, "assistants:update", {
|
|
225
|
+
assistant_id: assistantId,
|
|
226
|
+
graph_id: options?.graph_id,
|
|
227
|
+
config: options?.config,
|
|
228
|
+
metadata: options?.metadata,
|
|
229
|
+
name: options?.name,
|
|
230
|
+
});
|
|
196
231
|
return conn.with((STORE) => {
|
|
197
232
|
const assistant = STORE.assistants[assistantId];
|
|
198
|
-
if (!assistant)
|
|
233
|
+
if (!assistant) {
|
|
199
234
|
throw new HTTPException(404, { message: "Assistant not found" });
|
|
235
|
+
}
|
|
236
|
+
if (!isAuthMatching(assistant["metadata"], filters)) {
|
|
237
|
+
throw new HTTPException(404, { message: "Assistant not found" });
|
|
238
|
+
}
|
|
200
239
|
const now = new Date();
|
|
201
|
-
const metadata =
|
|
240
|
+
const metadata = mutable.metadata != null
|
|
202
241
|
? {
|
|
203
242
|
...assistant["metadata"],
|
|
204
|
-
...
|
|
243
|
+
...mutable.metadata,
|
|
205
244
|
}
|
|
206
245
|
: null;
|
|
207
246
|
if (options?.graph_id != null) {
|
|
@@ -234,34 +273,49 @@ export class Assistants {
|
|
|
234
273
|
return assistant;
|
|
235
274
|
});
|
|
236
275
|
}
|
|
237
|
-
static async delete(
|
|
276
|
+
static async delete(assistant_id, auth) {
|
|
277
|
+
const [filters] = await handleAuthEvent(auth, "assistants:delete", {
|
|
278
|
+
assistant_id,
|
|
279
|
+
});
|
|
238
280
|
return conn.with((STORE) => {
|
|
239
|
-
const assistant = STORE.assistants[
|
|
240
|
-
if (!assistant)
|
|
281
|
+
const assistant = STORE.assistants[assistant_id];
|
|
282
|
+
if (!assistant) {
|
|
283
|
+
throw new HTTPException(404, { message: "Assistant not found" });
|
|
284
|
+
}
|
|
285
|
+
if (!isAuthMatching(assistant["metadata"], filters)) {
|
|
241
286
|
throw new HTTPException(404, { message: "Assistant not found" });
|
|
242
|
-
|
|
287
|
+
}
|
|
288
|
+
delete STORE.assistants[assistant_id];
|
|
243
289
|
// Cascade delete for assistant versions and crons
|
|
244
|
-
STORE.assistant_versions = STORE.assistant_versions.filter((v) => v["assistant_id"] !==
|
|
290
|
+
STORE.assistant_versions = STORE.assistant_versions.filter((v) => v["assistant_id"] !== assistant_id);
|
|
245
291
|
for (const run of Object.values(STORE.runs)) {
|
|
246
|
-
if (run["assistant_id"] ===
|
|
292
|
+
if (run["assistant_id"] === assistant_id) {
|
|
247
293
|
delete STORE.runs[run["run_id"]];
|
|
248
294
|
}
|
|
249
295
|
}
|
|
250
296
|
return [assistant.assistant_id];
|
|
251
297
|
});
|
|
252
298
|
}
|
|
253
|
-
static async setLatest(
|
|
299
|
+
static async setLatest(assistant_id, version, auth) {
|
|
300
|
+
const [filters] = await handleAuthEvent(auth, "assistants:update", {
|
|
301
|
+
assistant_id,
|
|
302
|
+
version,
|
|
303
|
+
});
|
|
254
304
|
return conn.with((STORE) => {
|
|
255
|
-
const assistant = STORE.assistants[
|
|
256
|
-
if (!assistant)
|
|
305
|
+
const assistant = STORE.assistants[assistant_id];
|
|
306
|
+
if (!assistant) {
|
|
307
|
+
throw new HTTPException(404, { message: "Assistant not found" });
|
|
308
|
+
}
|
|
309
|
+
if (!isAuthMatching(assistant["metadata"], filters)) {
|
|
257
310
|
throw new HTTPException(404, { message: "Assistant not found" });
|
|
258
|
-
|
|
311
|
+
}
|
|
312
|
+
const assistantVersion = STORE.assistant_versions.find((v) => v["assistant_id"] === assistant_id && v["version"] === version);
|
|
259
313
|
if (!assistantVersion)
|
|
260
314
|
throw new HTTPException(404, {
|
|
261
315
|
message: "Assistant version not found",
|
|
262
316
|
});
|
|
263
317
|
const now = new Date();
|
|
264
|
-
STORE.assistants[
|
|
318
|
+
STORE.assistants[assistant_id] = {
|
|
265
319
|
...assistant,
|
|
266
320
|
config: assistantVersion["config"],
|
|
267
321
|
metadata: assistantVersion["metadata"],
|
|
@@ -269,19 +323,25 @@ export class Assistants {
|
|
|
269
323
|
name: assistantVersion["name"],
|
|
270
324
|
updated_at: now,
|
|
271
325
|
};
|
|
272
|
-
return STORE.assistants[
|
|
326
|
+
return STORE.assistants[assistant_id];
|
|
273
327
|
});
|
|
274
328
|
}
|
|
275
|
-
static async getVersions(
|
|
329
|
+
static async getVersions(assistant_id, options, auth) {
|
|
330
|
+
const [filters] = await handleAuthEvent(auth, "assistants:read", {
|
|
331
|
+
assistant_id,
|
|
332
|
+
});
|
|
276
333
|
return conn.with((STORE) => {
|
|
277
334
|
const versions = STORE.assistant_versions
|
|
278
335
|
.filter((version) => {
|
|
279
|
-
if (version["assistant_id"] !==
|
|
336
|
+
if (version["assistant_id"] !== assistant_id)
|
|
280
337
|
return false;
|
|
281
338
|
if (options.metadata != null &&
|
|
282
339
|
!isJsonbContained(version["metadata"], options.metadata)) {
|
|
283
340
|
return false;
|
|
284
341
|
}
|
|
342
|
+
if (!isAuthMatching(version["metadata"], filters)) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
285
345
|
return true;
|
|
286
346
|
})
|
|
287
347
|
.sort((a, b) => b["version"] - a["version"]);
|
|
@@ -290,7 +350,14 @@ export class Assistants {
|
|
|
290
350
|
}
|
|
291
351
|
}
|
|
292
352
|
export class Threads {
|
|
293
|
-
static async *search(options) {
|
|
353
|
+
static async *search(options, auth) {
|
|
354
|
+
const [filters] = await handleAuthEvent(auth, "threads:search", {
|
|
355
|
+
metadata: options.metadata,
|
|
356
|
+
status: options.status,
|
|
357
|
+
values: options.values,
|
|
358
|
+
limit: options.limit,
|
|
359
|
+
offset: options.offset,
|
|
360
|
+
});
|
|
294
361
|
yield* conn.withGenerator(async function* (STORE) {
|
|
295
362
|
const filtered = Object.values(STORE.threads)
|
|
296
363
|
.filter((thread) => {
|
|
@@ -303,6 +370,8 @@ export class Threads {
|
|
|
303
370
|
return false;
|
|
304
371
|
if (options.status != null && thread["status"] !== options.status)
|
|
305
372
|
return false;
|
|
373
|
+
if (!isAuthMatching(thread["metadata"], filters))
|
|
374
|
+
return false;
|
|
306
375
|
return true;
|
|
307
376
|
})
|
|
308
377
|
.sort((a, b) => b["created_at"].getTime() - a["created_at"].getTime());
|
|
@@ -311,47 +380,75 @@ export class Threads {
|
|
|
311
380
|
}
|
|
312
381
|
});
|
|
313
382
|
}
|
|
314
|
-
|
|
383
|
+
// TODO: make this accept `undefined`
|
|
384
|
+
static async get(thread_id, auth) {
|
|
385
|
+
const [filters] = await handleAuthEvent(auth, "threads:read", {
|
|
386
|
+
thread_id,
|
|
387
|
+
});
|
|
315
388
|
return conn.with((STORE) => {
|
|
316
|
-
const result = STORE.threads[
|
|
317
|
-
if (result == null)
|
|
389
|
+
const result = STORE.threads[thread_id];
|
|
390
|
+
if (result == null) {
|
|
391
|
+
throw new HTTPException(404, {
|
|
392
|
+
message: `Thread with ID ${thread_id} not found`,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
if (!isAuthMatching(result["metadata"], filters)) {
|
|
318
396
|
throw new HTTPException(404, {
|
|
319
|
-
message: `Thread with ID ${
|
|
397
|
+
message: `Thread with ID ${thread_id} not found`,
|
|
320
398
|
});
|
|
399
|
+
}
|
|
321
400
|
return result;
|
|
322
401
|
});
|
|
323
402
|
}
|
|
324
|
-
static async put(
|
|
403
|
+
static async put(thread_id, options, auth) {
|
|
404
|
+
const [filters, mutable] = await handleAuthEvent(auth, "threads:create", {
|
|
405
|
+
thread_id,
|
|
406
|
+
metadata: options.metadata,
|
|
407
|
+
if_exists: options.if_exists,
|
|
408
|
+
});
|
|
325
409
|
return conn.with((STORE) => {
|
|
326
410
|
const now = new Date();
|
|
327
|
-
if (STORE.threads[
|
|
411
|
+
if (STORE.threads[thread_id] != null) {
|
|
412
|
+
const existingThread = STORE.threads[thread_id];
|
|
413
|
+
if (!isAuthMatching(existingThread["metadata"], filters)) {
|
|
414
|
+
throw new HTTPException(409, { message: "Thread already exists" });
|
|
415
|
+
}
|
|
328
416
|
if (options?.if_exists === "raise") {
|
|
329
417
|
throw new HTTPException(409, { message: "Thread already exists" });
|
|
330
418
|
}
|
|
331
|
-
return
|
|
419
|
+
return existingThread;
|
|
332
420
|
}
|
|
333
|
-
STORE.threads[
|
|
334
|
-
thread_id:
|
|
421
|
+
STORE.threads[thread_id] ??= {
|
|
422
|
+
thread_id: thread_id,
|
|
335
423
|
created_at: now,
|
|
336
424
|
updated_at: now,
|
|
337
|
-
metadata:
|
|
425
|
+
metadata: mutable?.metadata ?? {},
|
|
338
426
|
status: "idle",
|
|
339
427
|
config: {},
|
|
340
428
|
values: undefined,
|
|
341
429
|
};
|
|
342
|
-
return STORE.threads[
|
|
430
|
+
return STORE.threads[thread_id];
|
|
343
431
|
});
|
|
344
432
|
}
|
|
345
|
-
static async patch(threadId, options) {
|
|
433
|
+
static async patch(threadId, options, auth) {
|
|
434
|
+
const [filters, mutable] = await handleAuthEvent(auth, "threads:update", {
|
|
435
|
+
thread_id: threadId,
|
|
436
|
+
metadata: options.metadata,
|
|
437
|
+
});
|
|
346
438
|
return conn.with((STORE) => {
|
|
347
439
|
const thread = STORE.threads[threadId];
|
|
348
|
-
if (!thread)
|
|
440
|
+
if (!thread) {
|
|
441
|
+
throw new HTTPException(404, { message: "Thread not found" });
|
|
442
|
+
}
|
|
443
|
+
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
444
|
+
// TODO: is this correct status code?
|
|
349
445
|
throw new HTTPException(404, { message: "Thread not found" });
|
|
446
|
+
}
|
|
350
447
|
const now = new Date();
|
|
351
|
-
if (
|
|
448
|
+
if (mutable.metadata != null) {
|
|
352
449
|
thread["metadata"] = {
|
|
353
450
|
...thread["metadata"],
|
|
354
|
-
...
|
|
451
|
+
...mutable.metadata,
|
|
355
452
|
};
|
|
356
453
|
}
|
|
357
454
|
thread["updated_at"] = now;
|
|
@@ -393,28 +490,43 @@ export class Threads {
|
|
|
393
490
|
: undefined;
|
|
394
491
|
});
|
|
395
492
|
}
|
|
396
|
-
static async delete(
|
|
493
|
+
static async delete(thread_id, auth) {
|
|
494
|
+
const [filters] = await handleAuthEvent(auth, "threads:delete", {
|
|
495
|
+
thread_id,
|
|
496
|
+
});
|
|
397
497
|
return conn.with((STORE) => {
|
|
398
|
-
const thread = STORE.threads[
|
|
399
|
-
if (!thread)
|
|
498
|
+
const thread = STORE.threads[thread_id];
|
|
499
|
+
if (!thread) {
|
|
500
|
+
throw new HTTPException(404, {
|
|
501
|
+
message: `Thread with ID ${thread_id} not found`,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
400
505
|
throw new HTTPException(404, {
|
|
401
|
-
message: `Thread with ID ${
|
|
506
|
+
message: `Thread with ID ${thread_id} not found`,
|
|
402
507
|
});
|
|
403
|
-
|
|
508
|
+
}
|
|
509
|
+
delete STORE.threads[thread_id];
|
|
404
510
|
for (const run of Object.values(STORE.runs)) {
|
|
405
|
-
if (run["thread_id"] ===
|
|
511
|
+
if (run["thread_id"] === thread_id) {
|
|
406
512
|
delete STORE.runs[run["run_id"]];
|
|
407
513
|
}
|
|
408
514
|
}
|
|
409
|
-
checkpointer.delete(
|
|
515
|
+
checkpointer.delete(thread_id, null);
|
|
410
516
|
return [thread.thread_id];
|
|
411
517
|
});
|
|
412
518
|
}
|
|
413
|
-
static async copy(
|
|
519
|
+
static async copy(thread_id, auth) {
|
|
520
|
+
const [filters] = await handleAuthEvent(auth, "threads:read", {
|
|
521
|
+
thread_id,
|
|
522
|
+
});
|
|
414
523
|
return conn.with((STORE) => {
|
|
415
|
-
const thread = STORE.threads[
|
|
524
|
+
const thread = STORE.threads[thread_id];
|
|
416
525
|
if (!thread)
|
|
417
526
|
throw new HTTPException(409, { message: "Thread not found" });
|
|
527
|
+
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
528
|
+
throw new HTTPException(409, { message: "Thread not found" });
|
|
529
|
+
}
|
|
418
530
|
const newThreadId = uuid4();
|
|
419
531
|
const now = new Date();
|
|
420
532
|
STORE.threads[newThreadId] = {
|
|
@@ -425,15 +537,15 @@ export class Threads {
|
|
|
425
537
|
config: {},
|
|
426
538
|
status: "idle",
|
|
427
539
|
};
|
|
428
|
-
checkpointer.copy(
|
|
540
|
+
checkpointer.copy(thread_id, newThreadId);
|
|
429
541
|
return STORE.threads[newThreadId];
|
|
430
542
|
});
|
|
431
543
|
}
|
|
432
544
|
static State = class {
|
|
433
|
-
static async get(config, options) {
|
|
545
|
+
static async get(config, options, auth) {
|
|
434
546
|
const subgraphs = options.subgraphs ?? false;
|
|
435
547
|
const threadId = config.configurable?.thread_id;
|
|
436
|
-
const thread = threadId ? await Threads.get(threadId) : undefined;
|
|
548
|
+
const thread = threadId ? await Threads.get(threadId, auth) : undefined;
|
|
437
549
|
const metadata = thread?.metadata ?? {};
|
|
438
550
|
const graphId = metadata?.graph_id;
|
|
439
551
|
if (!thread || graphId == null) {
|
|
@@ -459,13 +571,19 @@ export class Threads {
|
|
|
459
571
|
}
|
|
460
572
|
return result;
|
|
461
573
|
}
|
|
462
|
-
static async post(config, values, asNode) {
|
|
574
|
+
static async post(config, values, asNode, auth) {
|
|
463
575
|
const threadId = config.configurable?.thread_id;
|
|
464
|
-
const
|
|
576
|
+
const [filters] = await handleAuthEvent(auth, "threads:update", {
|
|
577
|
+
thread_id: threadId,
|
|
578
|
+
});
|
|
579
|
+
const thread = threadId ? await Threads.get(threadId, auth) : undefined;
|
|
465
580
|
if (!thread)
|
|
466
581
|
throw new HTTPException(404, {
|
|
467
582
|
message: `Thread ${threadId} not found`,
|
|
468
583
|
});
|
|
584
|
+
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
585
|
+
throw new HTTPException(403);
|
|
586
|
+
}
|
|
469
587
|
const graphId = thread.metadata?.graph_id;
|
|
470
588
|
if (graphId == null) {
|
|
471
589
|
throw new HTTPException(400, {
|
|
@@ -482,7 +600,7 @@ export class Threads {
|
|
|
482
600
|
updateConfig.configurable ??= {};
|
|
483
601
|
updateConfig.configurable.checkpoint_ns ??= "";
|
|
484
602
|
const nextConfig = await graph.updateState(updateConfig, values, asNode);
|
|
485
|
-
const state = await Threads.State.get(config, { subgraphs: false });
|
|
603
|
+
const state = await Threads.State.get(config, { subgraphs: false }, auth);
|
|
486
604
|
// update thread values
|
|
487
605
|
await conn.with(async (STORE) => {
|
|
488
606
|
for (const thread of Object.values(STORE.threads)) {
|
|
@@ -494,11 +612,17 @@ export class Threads {
|
|
|
494
612
|
});
|
|
495
613
|
return { checkpoint: nextConfig.configurable };
|
|
496
614
|
}
|
|
497
|
-
static async bulk(config, supersteps) {
|
|
615
|
+
static async bulk(config, supersteps, auth) {
|
|
498
616
|
const threadId = config.configurable?.thread_id;
|
|
499
617
|
if (!threadId)
|
|
500
618
|
return [];
|
|
501
|
-
const
|
|
619
|
+
const [filters] = await handleAuthEvent(auth, "threads:update", {
|
|
620
|
+
thread_id: threadId,
|
|
621
|
+
});
|
|
622
|
+
const thread = await Threads.get(threadId, auth);
|
|
623
|
+
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
624
|
+
throw new HTTPException(403);
|
|
625
|
+
}
|
|
502
626
|
const graphId = thread.metadata?.graph_id;
|
|
503
627
|
if (graphId == null) {
|
|
504
628
|
throw new HTTPException(400, {
|
|
@@ -520,7 +644,7 @@ export class Threads {
|
|
|
520
644
|
asNode: j.as_node,
|
|
521
645
|
})),
|
|
522
646
|
})));
|
|
523
|
-
const state = await Threads.State.get(config, { subgraphs: false });
|
|
647
|
+
const state = await Threads.State.get(config, { subgraphs: false }, auth);
|
|
524
648
|
// update thread values
|
|
525
649
|
await conn.with(async (STORE) => {
|
|
526
650
|
for (const thread of Object.values(STORE.threads)) {
|
|
@@ -532,11 +656,16 @@ export class Threads {
|
|
|
532
656
|
});
|
|
533
657
|
return { checkpoint: nextConfig.configurable };
|
|
534
658
|
}
|
|
535
|
-
static async list(config, options) {
|
|
659
|
+
static async list(config, options, auth) {
|
|
536
660
|
const threadId = config.configurable?.thread_id;
|
|
537
661
|
if (!threadId)
|
|
538
662
|
return [];
|
|
539
|
-
const
|
|
663
|
+
const [filters] = await handleAuthEvent(auth, "threads:read", {
|
|
664
|
+
thread_id: threadId,
|
|
665
|
+
});
|
|
666
|
+
const thread = await Threads.get(threadId, auth);
|
|
667
|
+
if (!isAuthMatching(thread["metadata"], filters))
|
|
668
|
+
return [];
|
|
540
669
|
const graphId = thread.metadata?.graph_id;
|
|
541
670
|
if (graphId == null)
|
|
542
671
|
return [];
|
|
@@ -592,7 +721,7 @@ export class Runs {
|
|
|
592
721
|
}
|
|
593
722
|
});
|
|
594
723
|
}
|
|
595
|
-
static async put(runId, assistantId, kwargs, options) {
|
|
724
|
+
static async put(runId, assistantId, kwargs, options, auth) {
|
|
596
725
|
return conn.with(async (STORE) => {
|
|
597
726
|
const assistant = STORE.assistants[assistantId];
|
|
598
727
|
if (!assistant) {
|
|
@@ -605,16 +734,36 @@ export class Runs {
|
|
|
605
734
|
const afterSeconds = options?.afterSeconds ?? 0;
|
|
606
735
|
const status = options?.status ?? "pending";
|
|
607
736
|
let threadId = options?.threadId;
|
|
608
|
-
const
|
|
737
|
+
const [filters, mutable] = await handleAuthEvent(auth, "threads:create_run", {
|
|
738
|
+
thread_id: threadId,
|
|
739
|
+
assistant_id: assistantId,
|
|
740
|
+
run_id: runId,
|
|
741
|
+
status: status,
|
|
742
|
+
metadata: options?.metadata ?? {},
|
|
743
|
+
prevent_insert_if_inflight: options?.preventInsertInInflight,
|
|
744
|
+
multitask_strategy: multitaskStrategy,
|
|
745
|
+
if_not_exists: ifNotExists,
|
|
746
|
+
after_seconds: afterSeconds,
|
|
747
|
+
kwargs,
|
|
748
|
+
});
|
|
749
|
+
const metadata = mutable.metadata ?? {};
|
|
609
750
|
const config = kwargs.config ?? {};
|
|
610
751
|
const existingThread = Object.values(STORE.threads).find((thread) => thread.thread_id === threadId);
|
|
752
|
+
if (existingThread &&
|
|
753
|
+
!isAuthMatching(existingThread["metadata"], filters)) {
|
|
754
|
+
throw new HTTPException(404);
|
|
755
|
+
}
|
|
611
756
|
const now = new Date();
|
|
612
757
|
if (!existingThread && (threadId == null || ifNotExists === "create")) {
|
|
613
758
|
threadId ??= uuid4();
|
|
614
759
|
const thread = {
|
|
615
760
|
thread_id: threadId,
|
|
616
761
|
status: "busy",
|
|
617
|
-
metadata: {
|
|
762
|
+
metadata: {
|
|
763
|
+
graph_id: assistant.graph_id,
|
|
764
|
+
assistant_id: assistantId,
|
|
765
|
+
...metadata,
|
|
766
|
+
},
|
|
618
767
|
config: Object.assign({}, assistant.config, config, {
|
|
619
768
|
configurable: Object.assign({}, assistant.config?.configurable, config?.configurable),
|
|
620
769
|
}),
|
|
@@ -675,29 +824,47 @@ export class Runs {
|
|
|
675
824
|
return [newRun, ...inflightRuns];
|
|
676
825
|
});
|
|
677
826
|
}
|
|
678
|
-
static async get(runId,
|
|
827
|
+
static async get(runId, thread_id, auth) {
|
|
828
|
+
const [filters] = await handleAuthEvent(auth, "threads:read", {
|
|
829
|
+
thread_id,
|
|
830
|
+
});
|
|
679
831
|
return conn.with(async (STORE) => {
|
|
680
832
|
const run = STORE.runs[runId];
|
|
681
833
|
if (!run ||
|
|
682
834
|
run.run_id !== runId ||
|
|
683
|
-
(
|
|
835
|
+
(thread_id != null && run.thread_id !== thread_id))
|
|
684
836
|
return null;
|
|
837
|
+
if (filters != null) {
|
|
838
|
+
const thread = STORE.threads[run.thread_id];
|
|
839
|
+
if (!isAuthMatching(thread["metadata"], filters))
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
685
842
|
return run;
|
|
686
843
|
});
|
|
687
844
|
}
|
|
688
|
-
static async delete(
|
|
845
|
+
static async delete(run_id, thread_id, auth) {
|
|
846
|
+
const [filters] = await handleAuthEvent(auth, "threads:delete", {
|
|
847
|
+
run_id,
|
|
848
|
+
thread_id,
|
|
849
|
+
});
|
|
689
850
|
return conn.with(async (STORE) => {
|
|
690
|
-
const run = STORE.runs[
|
|
691
|
-
if (!run || (
|
|
692
|
-
throw new
|
|
693
|
-
if (
|
|
694
|
-
|
|
695
|
-
|
|
851
|
+
const run = STORE.runs[run_id];
|
|
852
|
+
if (!run || (thread_id != null && run.thread_id !== thread_id))
|
|
853
|
+
throw new HTTPException(404, { message: "Run not found" });
|
|
854
|
+
if (filters != null) {
|
|
855
|
+
const thread = STORE.threads[run.thread_id];
|
|
856
|
+
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
857
|
+
throw new HTTPException(404, { message: "Run not found" });
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
if (thread_id != null)
|
|
861
|
+
checkpointer.delete(thread_id, run_id);
|
|
862
|
+
delete STORE.runs[run_id];
|
|
696
863
|
return run.run_id;
|
|
697
864
|
});
|
|
698
865
|
}
|
|
699
|
-
static async wait(runId, threadId) {
|
|
700
|
-
const runStream = Runs.Stream.join(runId, threadId);
|
|
866
|
+
static async wait(runId, threadId, auth) {
|
|
867
|
+
const runStream = Runs.Stream.join(runId, threadId, { ignore404: threadId == null }, auth);
|
|
701
868
|
const lastChunk = new Promise(async (resolve, reject) => {
|
|
702
869
|
try {
|
|
703
870
|
let lastChunk = null;
|
|
@@ -717,24 +884,34 @@ export class Runs {
|
|
|
717
884
|
});
|
|
718
885
|
return lastChunk;
|
|
719
886
|
}
|
|
720
|
-
static async join(runId, threadId) {
|
|
887
|
+
static async join(runId, threadId, auth) {
|
|
721
888
|
// check if thread exists
|
|
722
|
-
await Threads.get(threadId);
|
|
723
|
-
const lastChunk = await Runs.wait(runId, threadId);
|
|
889
|
+
await Threads.get(threadId, auth);
|
|
890
|
+
const lastChunk = await Runs.wait(runId, threadId, auth);
|
|
724
891
|
if (lastChunk != null)
|
|
725
892
|
return lastChunk;
|
|
726
|
-
const thread = await Threads.get(threadId);
|
|
893
|
+
const thread = await Threads.get(threadId, auth);
|
|
727
894
|
return thread.values;
|
|
728
895
|
}
|
|
729
|
-
static async cancel(threadId, runIds, options) {
|
|
896
|
+
static async cancel(threadId, runIds, options, auth) {
|
|
730
897
|
return conn.with(async (STORE) => {
|
|
731
898
|
const action = options.action ?? "interrupt";
|
|
732
899
|
const promises = [];
|
|
900
|
+
const [filters] = await handleAuthEvent(auth, "threads:update", {
|
|
901
|
+
thread_id: threadId,
|
|
902
|
+
action,
|
|
903
|
+
metadata: { run_ids: runIds, status: "pending" },
|
|
904
|
+
});
|
|
733
905
|
let foundRunsCount = 0;
|
|
734
906
|
for (const runId of runIds) {
|
|
735
907
|
const run = STORE.runs[runId];
|
|
736
908
|
if (!run || (threadId != null && run.thread_id !== threadId))
|
|
737
909
|
continue;
|
|
910
|
+
if (filters != null) {
|
|
911
|
+
const thread = STORE.threads[run.thread_id];
|
|
912
|
+
if (!isAuthMatching(thread["metadata"], filters))
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
738
915
|
foundRunsCount += 1;
|
|
739
916
|
// send cancellation message
|
|
740
917
|
const control = StreamManager.getControl(runId);
|
|
@@ -749,7 +926,7 @@ export class Runs {
|
|
|
749
926
|
run_id: runId,
|
|
750
927
|
thread_id: threadId,
|
|
751
928
|
});
|
|
752
|
-
promises.push(Runs.delete(runId, threadId));
|
|
929
|
+
promises.push(Runs.delete(runId, threadId, auth));
|
|
753
930
|
}
|
|
754
931
|
}
|
|
755
932
|
else {
|
|
@@ -772,7 +949,12 @@ export class Runs {
|
|
|
772
949
|
}
|
|
773
950
|
});
|
|
774
951
|
}
|
|
775
|
-
static async search(threadId, options) {
|
|
952
|
+
static async search(threadId, options, auth) {
|
|
953
|
+
const [filters] = await handleAuthEvent(auth, "threads:search", {
|
|
954
|
+
thread_id: threadId,
|
|
955
|
+
metadata: options.metadata,
|
|
956
|
+
status: options.status,
|
|
957
|
+
});
|
|
776
958
|
return conn.with(async (STORE) => {
|
|
777
959
|
const runs = Object.values(STORE.runs).filter((run) => {
|
|
778
960
|
if (run.thread_id !== threadId)
|
|
@@ -782,6 +964,11 @@ export class Runs {
|
|
|
782
964
|
if (options?.metadata != null &&
|
|
783
965
|
!isJsonbContained(run.metadata, options.metadata))
|
|
784
966
|
return false;
|
|
967
|
+
if (filters != null) {
|
|
968
|
+
const thread = STORE.threads[run.thread_id];
|
|
969
|
+
if (!isAuthMatching(thread["metadata"], filters))
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
785
972
|
return true;
|
|
786
973
|
});
|
|
787
974
|
return runs.slice(options?.offset ?? 0, options?.limit ?? 10);
|
|
@@ -797,39 +984,55 @@ export class Runs {
|
|
|
797
984
|
});
|
|
798
985
|
}
|
|
799
986
|
static Stream = class {
|
|
800
|
-
static async *join(runId, threadId, options) {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
987
|
+
static async *join(runId, threadId, options, auth) {
|
|
988
|
+
yield* conn.withGenerator(async function* (STORE) {
|
|
989
|
+
// TODO: what if we're joining an already completed run? Should we check before?
|
|
990
|
+
const signal = options?.cancelOnDisconnect;
|
|
991
|
+
const queue = StreamManager.getQueue(runId, { ifNotFound: "create" });
|
|
992
|
+
const [filters] = await handleAuthEvent(auth, "threads:read", {
|
|
993
|
+
thread_id: threadId,
|
|
994
|
+
});
|
|
995
|
+
// TODO: consolidate into a single function
|
|
996
|
+
if (filters != null && threadId != null) {
|
|
997
|
+
const thread = STORE.threads[threadId];
|
|
998
|
+
if (!isAuthMatching(thread["metadata"], filters)) {
|
|
999
|
+
yield {
|
|
1000
|
+
event: "error",
|
|
1001
|
+
data: { error: "Error", message: "404: Thread not found" },
|
|
1002
|
+
};
|
|
1003
|
+
return;
|
|
814
1004
|
}
|
|
815
1005
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1006
|
+
while (!signal?.aborted) {
|
|
1007
|
+
try {
|
|
1008
|
+
const message = await queue.get({ timeout: 500, signal });
|
|
1009
|
+
if (message.topic === `run:${runId}:control`) {
|
|
1010
|
+
if (message.data === "done")
|
|
1011
|
+
break;
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
const streamTopic = message.topic.substring(`run:${runId}:stream:`.length);
|
|
1015
|
+
yield { event: streamTopic, data: message.data };
|
|
1016
|
+
}
|
|
824
1017
|
}
|
|
825
|
-
|
|
826
|
-
|
|
1018
|
+
catch (error) {
|
|
1019
|
+
if (error instanceof AbortError)
|
|
1020
|
+
break;
|
|
1021
|
+
const run = await Runs.get(runId, threadId, auth);
|
|
1022
|
+
if (run == null) {
|
|
1023
|
+
if (!options?.ignore404)
|
|
1024
|
+
yield { event: "error", data: "Run not found" };
|
|
1025
|
+
break;
|
|
1026
|
+
}
|
|
1027
|
+
else if (run.status !== "pending") {
|
|
1028
|
+
break;
|
|
1029
|
+
}
|
|
827
1030
|
}
|
|
828
1031
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
1032
|
+
if (signal?.aborted && threadId != null) {
|
|
1033
|
+
await Runs.cancel(threadId, [runId], { action: "interrupt" }, auth);
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
833
1036
|
}
|
|
834
1037
|
static async publish(runId, topic, data) {
|
|
835
1038
|
const queue = StreamManager.getQueue(runId, { ifNotFound: "create" });
|