@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.
@@ -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(assistantId) {
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[assistantId];
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(assistantId, options) {
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[assistantId] != null) {
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 STORE.assistants[assistantId];
198
+ return existingAssistant;
171
199
  }
172
200
  const now = new Date();
173
- STORE.assistants[assistantId] ??= {
174
- assistant_id: assistantId,
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: options.metadata ?? {},
208
+ metadata: mutable.metadata ?? {},
181
209
  name: options.name || options.graph_id,
182
210
  };
183
211
  STORE.assistant_versions.push({
184
- assistant_id: assistantId,
212
+ assistant_id: assistant_id,
185
213
  version: 1,
186
214
  graph_id: options.graph_id,
187
215
  config: options.config ?? {},
188
- metadata: options.metadata ?? {},
216
+ metadata: mutable.metadata ?? {},
189
217
  created_at: now,
190
218
  name: options.name || options.graph_id,
191
219
  });
192
- return STORE.assistants[assistantId];
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 = options?.metadata != null
240
+ const metadata = mutable.metadata != null
202
241
  ? {
203
242
  ...assistant["metadata"],
204
- ...options.metadata,
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(assistantId) {
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[assistantId];
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
- delete STORE.assistants[assistantId];
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"] !== assistantId);
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"] === assistantId) {
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(assistantId, version) {
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[assistantId];
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
- const assistantVersion = STORE.assistant_versions.find((v) => v["assistant_id"] === assistantId && v["version"] === version);
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[assistantId] = {
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[assistantId];
326
+ return STORE.assistants[assistant_id];
273
327
  });
274
328
  }
275
- static async getVersions(assistantId, options) {
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"] !== assistantId)
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
- static async get(threadId) {
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[threadId];
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 ${threadId} not found`,
397
+ message: `Thread with ID ${thread_id} not found`,
320
398
  });
399
+ }
321
400
  return result;
322
401
  });
323
402
  }
324
- static async put(threadId, options) {
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[threadId] != null) {
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 STORE.threads[threadId];
419
+ return existingThread;
332
420
  }
333
- STORE.threads[threadId] ??= {
334
- thread_id: threadId,
421
+ STORE.threads[thread_id] ??= {
422
+ thread_id: thread_id,
335
423
  created_at: now,
336
424
  updated_at: now,
337
- metadata: options?.metadata ?? {},
425
+ metadata: mutable?.metadata ?? {},
338
426
  status: "idle",
339
427
  config: {},
340
428
  values: undefined,
341
429
  };
342
- return STORE.threads[threadId];
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 (options?.metadata != null) {
448
+ if (mutable.metadata != null) {
352
449
  thread["metadata"] = {
353
450
  ...thread["metadata"],
354
- ...options.metadata,
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(threadId) {
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[threadId];
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 ${threadId} not found`,
506
+ message: `Thread with ID ${thread_id} not found`,
402
507
  });
403
- delete STORE.threads[threadId];
508
+ }
509
+ delete STORE.threads[thread_id];
404
510
  for (const run of Object.values(STORE.runs)) {
405
- if (run["thread_id"] === threadId) {
511
+ if (run["thread_id"] === thread_id) {
406
512
  delete STORE.runs[run["run_id"]];
407
513
  }
408
514
  }
409
- checkpointer.delete(threadId, null);
515
+ checkpointer.delete(thread_id, null);
410
516
  return [thread.thread_id];
411
517
  });
412
518
  }
413
- static async copy(threadId) {
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[threadId];
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(threadId, newThreadId);
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 thread = threadId ? await Threads.get(threadId) : undefined;
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 thread = await Threads.get(threadId);
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 thread = await Threads.get(threadId);
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 metadata = options?.metadata ?? {};
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: { graph_id: assistant.graph_id, assistant_id: assistantId },
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, threadId) {
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
- (threadId != null && run.thread_id !== threadId))
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(runId, threadId) {
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[runId];
691
- if (!run || (threadId != null && run.thread_id !== threadId))
692
- throw new Error("Run not found");
693
- if (threadId != null)
694
- checkpointer.delete(threadId, runId);
695
- delete STORE.runs[runId];
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
- // TODO: what if we're joining an already completed run? Should we check before?
802
- const signal = options?.cancelOnDisconnect;
803
- const queue = StreamManager.getQueue(runId, { ifNotFound: "create" });
804
- while (!signal?.aborted) {
805
- try {
806
- const message = await queue.get({ timeout: 500, signal });
807
- if (message.topic === `run:${runId}:control`) {
808
- if (message.data === "done")
809
- break;
810
- }
811
- else {
812
- const streamTopic = message.topic.substring(`run:${runId}:stream:`.length);
813
- yield { event: streamTopic, data: message.data };
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
- catch (error) {
817
- if (error instanceof AbortError)
818
- break;
819
- const run = await Runs.get(runId, threadId);
820
- if (run == null) {
821
- if (!options?.ignore404)
822
- yield { event: "error", data: "Run not found" };
823
- break;
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
- else if (run.status !== "pending") {
826
- break;
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
- if (signal?.aborted && threadId != null) {
831
- await Runs.cancel(threadId, [runId], { action: "interrupt" });
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" });