@langchain/langgraph-api 0.0.33 → 0.0.35

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.
@@ -10,8 +10,14 @@ export interface GraphSpec {
10
10
  sourceFile: string;
11
11
  exportSymbol: string;
12
12
  }
13
+ type GraphSchemaWithSubgraphs = Record<string, GraphSchema>;
13
14
  export declare function getStaticGraphSchema(spec: GraphSpec, options?: {
14
15
  mainThread?: boolean;
15
16
  timeoutMs?: number;
16
- }): Promise<Record<string, GraphSchema>>;
17
+ }): Promise<GraphSchemaWithSubgraphs>;
18
+ export declare function getStaticGraphSchema(specMap: Record<string, GraphSpec>, options?: {
19
+ mainThread?: boolean;
20
+ timeoutMs?: number;
21
+ }): Promise<Record<string, GraphSchemaWithSubgraphs>>;
17
22
  export declare function getRuntimeGraphSchema(graph: Pregel<any, any, any, any, any>): Promise<GraphSchema | undefined>;
23
+ export {};
@@ -1,25 +1,42 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import { Worker } from "node:worker_threads";
3
- export async function getStaticGraphSchema(spec, options) {
4
- if (options?.mainThread) {
5
- const { SubgraphExtractor } = await import("./parser.mjs");
6
- return SubgraphExtractor.extractSchemas(spec.sourceFile, spec.exportSymbol, { strict: false });
7
- }
8
- return await new Promise((resolve, reject) => {
9
- const worker = new Worker(fileURLToPath(new URL("./parser.worker.mjs", import.meta.url)), { argv: process.argv.slice(-1) });
10
- // Set a timeout to reject if the worker takes too long
11
- const timeoutId = setTimeout(() => {
12
- worker.terminate();
13
- reject(new Error("Schema extract worker timed out"));
14
- }, options?.timeoutMs ?? 30000);
15
- worker.on("message", (result) => {
16
- worker.terminate();
17
- clearTimeout(timeoutId);
18
- resolve(result);
3
+ const isGraphSpec = (spec) => {
4
+ if (typeof spec !== "object" || spec == null)
5
+ return false;
6
+ if (!("sourceFile" in spec) || typeof spec.sourceFile !== "string")
7
+ return false;
8
+ if (!("exportSymbol" in spec) || typeof spec.exportSymbol !== "string")
9
+ return false;
10
+ return true;
11
+ };
12
+ export async function getStaticGraphSchema(input, options) {
13
+ async function execute(specs) {
14
+ if (options?.mainThread) {
15
+ const { SubgraphExtractor } = await import("./parser.mjs");
16
+ return SubgraphExtractor.extractSchemas(specs, { strict: false });
17
+ }
18
+ return await new Promise((resolve, reject) => {
19
+ const worker = new Worker(fileURLToPath(new URL("./parser.worker.mjs", import.meta.url)), { argv: process.argv.slice(-1) });
20
+ // Set a timeout to reject if the worker takes too long
21
+ const timeoutId = setTimeout(() => {
22
+ worker.terminate();
23
+ reject(new Error("Schema extract worker timed out"));
24
+ }, options?.timeoutMs ?? 30000);
25
+ worker.on("message", (result) => {
26
+ worker.terminate();
27
+ clearTimeout(timeoutId);
28
+ resolve(result);
29
+ });
30
+ worker.on("error", reject);
31
+ worker.postMessage(specs);
19
32
  });
20
- worker.on("error", reject);
21
- worker.postMessage(spec);
22
- });
33
+ }
34
+ const specs = isGraphSpec(input) ? [input] : Object.values(input);
35
+ const results = await execute(specs);
36
+ if (isGraphSpec(input)) {
37
+ return results[0];
38
+ }
39
+ return Object.fromEntries(Object.keys(input).map((graphId, idx) => [graphId, results[idx]]));
23
40
  }
24
41
  export async function getRuntimeGraphSchema(graph) {
25
42
  try {
@@ -35,8 +35,15 @@ export declare class SubgraphExtractor {
35
35
  node: ts.Node;
36
36
  };
37
37
  }[];
38
- getAugmentedSourceFile: (name: string) => {
39
- files: [filePath: string, contents: string][];
38
+ getAugmentedSourceFile: (suffix: string, name: string) => {
39
+ inferFile: {
40
+ fileName: string;
41
+ contents: string;
42
+ };
43
+ sourceFile: {
44
+ fileName: string;
45
+ contents: string;
46
+ };
40
47
  exports: {
41
48
  typeName: string;
42
49
  valueName: string;
@@ -53,11 +60,15 @@ export declare class SubgraphExtractor {
53
60
  protected isGraphOrPregelType: (type: ts.Type) => boolean;
54
61
  protected getText(node: ts.Node): string;
55
62
  protected reduceChildren<Acc>(node: ts.Node, fn: (acc: Acc, node: ts.Node) => Acc, initial: Acc): Acc;
56
- static extractSchemas(target: string | {
57
- contents: string;
58
- files?: [fileName: string, contents: string][];
59
- }, name: string, options?: {
63
+ static extractSchemas(target: {
64
+ sourceFile: string | {
65
+ name: string;
66
+ contents: string;
67
+ main?: boolean;
68
+ }[];
69
+ exportSymbol: string;
70
+ }[], options?: {
60
71
  strict?: boolean;
61
- }): Record<string, GraphSchema>;
72
+ }): Record<string, GraphSchema>[];
62
73
  }
63
74
  export {};
@@ -11,6 +11,7 @@ const OVERRIDE_RESOLVE = [
11
11
  new RegExp(`^@langchain\/langgraph(\/.+)?$`),
12
12
  new RegExp(`^@langchain\/langgraph-checkpoint(\/.+)?$`),
13
13
  ];
14
+ const INFER_TEMPLATE_PATH = path.resolve(__dirname, "./schema/types.template.mts");
14
15
  const compilerOptions = {
15
16
  noEmit: true,
16
17
  strict: true,
@@ -144,7 +145,7 @@ export class SubgraphExtractor {
144
145
  return this.findSubgraphs(varDecl.initializer, [name]);
145
146
  });
146
147
  };
147
- getAugmentedSourceFile = (name) => {
148
+ getAugmentedSourceFile = (suffix, name) => {
148
149
  const vars = this.getSubgraphsVariables(name);
149
150
  const typeExports = [
150
151
  { typeName: `__langgraph__${name}`, valueName: name, graphName: name },
@@ -156,14 +157,14 @@ export class SubgraphExtractor {
156
157
  graphName: [...namespace, node].join("|"),
157
158
  });
158
159
  }
159
- const sourceFilePath = "__langgraph__source.mts";
160
+ const sourceFilePath = `__langgraph__source_${suffix}.mts`;
160
161
  const sourceContents = [
161
162
  this.getText(this.sourceFile),
162
163
  ...typeExports.map(({ typeName, valueName }) => `export type ${typeName} = typeof ${valueName}`),
163
164
  ].join("\n\n");
164
- const inferFilePath = "__langgraph__infer.mts";
165
+ const inferFilePath = `__langgraph__infer_${suffix}.mts`;
165
166
  const inferContents = [
166
- ...typeExports.map(({ typeName }) => `import type { ${typeName}} from "./__langgraph__source.mts"`),
167
+ ...typeExports.map(({ typeName }) => `import type { ${typeName}} from "./__langgraph__source_${suffix}.mts"`),
167
168
  this.inferFile.getText(this.inferFile),
168
169
  ...typeExports.flatMap(({ typeName }) => {
169
170
  return [
@@ -181,10 +182,8 @@ export class SubgraphExtractor {
181
182
  }),
182
183
  ].join("\n\n");
183
184
  return {
184
- files: [
185
- [sourceFilePath, sourceContents],
186
- [inferFilePath, inferContents],
187
- ],
185
+ inferFile: { fileName: inferFilePath, contents: inferContents },
186
+ sourceFile: { fileName: sourceFilePath, contents: sourceContents },
188
187
  exports: typeExports,
189
188
  };
190
189
  };
@@ -228,11 +227,34 @@ export class SubgraphExtractor {
228
227
  ts.forEachChild(node, it.bind(this));
229
228
  return acc;
230
229
  }
231
- static extractSchemas(target, name, options) {
232
- const dirname = typeof target === "string" ? path.dirname(target) : __dirname;
230
+ static extractSchemas(target, options) {
231
+ if (!target.length)
232
+ throw new Error("No graphs found");
233
+ function getCommonPath(a, b) {
234
+ const aSeg = path.normalize(a).split(path.sep);
235
+ const bSeg = path.normalize(b).split(path.sep);
236
+ const maxIter = Math.min(aSeg.length, bSeg.length);
237
+ const result = [];
238
+ for (let i = 0; i < maxIter; ++i) {
239
+ if (aSeg[i] !== bSeg[i])
240
+ break;
241
+ result.push(aSeg[i]);
242
+ }
243
+ return result.join(path.sep);
244
+ }
245
+ const isTestTarget = (check) => {
246
+ return check.every((x) => typeof x.sourceFile === "string");
247
+ };
248
+ const projectDirname = isTestTarget(target)
249
+ ? target.reduce((acc, item) => {
250
+ if (!acc)
251
+ return path.dirname(item.sourceFile);
252
+ return getCommonPath(acc, path.dirname(item.sourceFile));
253
+ }, "")
254
+ : __dirname;
233
255
  // This API is not well made for Windows, ensure that the paths are UNIX slashes
234
256
  const fsMap = new Map();
235
- const system = vfs.createFSBackedSystem(fsMap, dirname, ts);
257
+ const system = vfs.createFSBackedSystem(fsMap, projectDirname, ts);
236
258
  // TODO: investigate if we should create a PR in @typescript/vfs
237
259
  const oldReadFile = system.readFile.bind(system);
238
260
  system.readFile = (fileName) => oldReadFile(fileName) ?? "// Non-existent file";
@@ -243,17 +265,24 @@ export class SubgraphExtractor {
243
265
  };
244
266
  const vfsHost = vfs.createVirtualCompilerHost(system, compilerOptions, ts);
245
267
  const host = vfsHost.compilerHost;
246
- const targetPath = typeof target === "string"
247
- ? target
248
- : path.resolve(dirname, "./__langgraph__target.mts");
249
- const inferTemplatePath = path.resolve(__dirname, "./schema/types.template.mts");
250
- if (typeof target !== "string") {
251
- fsMap.set(vfsPath(targetPath), target.contents);
252
- for (const [name, contents] of target.files ?? []) {
253
- fsMap.set(vfsPath(path.resolve(dirname, name)), contents);
268
+ const targetPaths = [];
269
+ for (const item of target) {
270
+ if (typeof item.sourceFile === "string") {
271
+ targetPaths.push({ ...item, sourceFile: item.sourceFile });
272
+ }
273
+ else {
274
+ for (const { name, contents, main } of item.sourceFile ?? []) {
275
+ fsMap.set(vfsPath(path.resolve(projectDirname, name)), contents);
276
+ if (main) {
277
+ targetPaths.push({
278
+ ...item,
279
+ sourceFile: path.resolve(projectDirname, name),
280
+ });
281
+ }
282
+ }
254
283
  }
255
284
  }
256
- const moduleCache = ts.createModuleResolutionCache(dirname, (x) => x);
285
+ const moduleCache = ts.createModuleResolutionCache(projectDirname, (x) => x);
257
286
  host.resolveModuleNameLiterals = (entries, containingFile, redirectedReference, options) => entries.flatMap((entry) => {
258
287
  const specifier = entry.text;
259
288
  // Force module resolution to use @langchain/langgraph from the local project
@@ -263,7 +292,7 @@ export class SubgraphExtractor {
263
292
  // check if we're not already importing from node_modules
264
293
  if (!containingFile.split(path.sep).includes("node_modules")) {
265
294
  // Doesn't matter if the file exists, only used to nudge `ts.resolveModuleName`
266
- targetFile = path.resolve(dirname, "__langgraph__resolve.mts");
295
+ targetFile = path.resolve(projectDirname, "__langgraph__resolve.mts");
267
296
  }
268
297
  }
269
298
  return [
@@ -271,17 +300,24 @@ export class SubgraphExtractor {
271
300
  ];
272
301
  });
273
302
  const research = ts.createProgram({
274
- rootNames: [inferTemplatePath, targetPath],
303
+ rootNames: [INFER_TEMPLATE_PATH, ...targetPaths.map((i) => i.sourceFile)],
275
304
  options: compilerOptions,
276
305
  host,
277
306
  });
278
- const extractor = new SubgraphExtractor(research, research.getSourceFile(targetPath), research.getSourceFile(inferTemplatePath), options);
279
- const { files, exports } = extractor.getAugmentedSourceFile(name);
280
- for (const [name, source] of files) {
281
- system.writeFile(vfsPath(path.resolve(dirname, name)), source);
307
+ const researchTargets = [];
308
+ for (const targetPath of targetPaths) {
309
+ const extractor = new SubgraphExtractor(research, research.getSourceFile(targetPath.sourceFile), research.getSourceFile(INFER_TEMPLATE_PATH), options);
310
+ const { sourceFile, inferFile, exports } = extractor.getAugmentedSourceFile(path.basename(targetPath.sourceFile), targetPath.exportSymbol);
311
+ for (const { fileName, contents } of [sourceFile, inferFile]) {
312
+ system.writeFile(vfsPath(path.resolve(projectDirname, fileName)), contents);
313
+ }
314
+ researchTargets.push({
315
+ rootName: path.resolve(projectDirname, inferFile.fileName),
316
+ exports,
317
+ });
282
318
  }
283
319
  const extract = ts.createProgram({
284
- rootNames: [path.resolve(dirname, "./__langgraph__infer.mts")],
320
+ rootNames: researchTargets.map((i) => i.rootName),
285
321
  options: compilerOptions,
286
322
  host,
287
323
  });
@@ -295,7 +331,7 @@ export class SubgraphExtractor {
295
331
  }
296
332
  return undefined;
297
333
  };
298
- return Object.fromEntries(exports.map(({ typeName, graphName }) => [
334
+ return researchTargets.map(({ exports }) => Object.fromEntries(exports.map(({ typeName, graphName }) => [
299
335
  graphName,
300
336
  {
301
337
  state: trySymbol(schemaGenerator, `${typeName}__update`),
@@ -303,6 +339,6 @@ export class SubgraphExtractor {
303
339
  output: trySymbol(schemaGenerator, `${typeName}__output`),
304
340
  config: trySymbol(schemaGenerator, `${typeName}__config`),
305
341
  },
306
- ]));
342
+ ])));
307
343
  }
308
344
  }
@@ -2,6 +2,6 @@ import { tsImport } from "tsx/esm/api";
2
2
  import { parentPort } from "node:worker_threads";
3
3
  parentPort?.on("message", async (payload) => {
4
4
  const { SubgraphExtractor } = await tsImport("./parser.mjs", import.meta.url);
5
- const result = SubgraphExtractor.extractSchemas(payload.sourceFile, payload.exportSymbol, { strict: false });
5
+ const result = SubgraphExtractor.extractSchemas(payload, { strict: false });
6
6
  parentPort?.postMessage(result);
7
7
  });
@@ -256,7 +256,7 @@ export declare class Runs {
256
256
  static get(runId: string, thread_id: string | undefined, auth: AuthContext | undefined): Promise<Run | null>;
257
257
  static delete(run_id: string, thread_id: string | undefined, auth: AuthContext | undefined): Promise<string | null>;
258
258
  static wait(runId: string, threadId: string | undefined, auth: AuthContext | undefined): Promise<unknown>;
259
- static join(runId: string, threadId: string, auth: AuthContext | undefined): Promise<{} | undefined>;
259
+ static join(runId: string, threadId: string, auth: AuthContext | undefined): Promise<{} | null>;
260
260
  static cancel(threadId: string | undefined, runIds: string[], options: {
261
261
  action?: "interrupt" | "rollback";
262
262
  }, auth: AuthContext | undefined): Promise<void>;
@@ -596,6 +596,13 @@ export class Threads {
596
596
  if (!isAuthMatching(thread["metadata"], filters)) {
597
597
  throw new HTTPException(403);
598
598
  }
599
+ // do a check if there are no pending runs
600
+ await conn.with(async (STORE) => {
601
+ if (Object.values(STORE.runs).some((run) => run.thread_id === threadId &&
602
+ (run.status === "pending" || run.status === "running"))) {
603
+ throw new HTTPException(409, { message: "Thread is busy" });
604
+ }
605
+ });
599
606
  const graphId = thread.metadata?.graph_id;
600
607
  if (graphId == null) {
601
608
  throw new HTTPException(400, {
@@ -903,7 +910,7 @@ export class Runs {
903
910
  if (lastChunk != null)
904
911
  return lastChunk;
905
912
  const thread = await Threads.get(threadId, auth);
906
- return thread.values;
913
+ return thread.values ?? null;
907
914
  }
908
915
  static async cancel(threadId, runIds, options, auth) {
909
916
  return conn.with(async (STORE) => {
@@ -932,6 +939,11 @@ export class Runs {
932
939
  if (control || action !== "rollback") {
933
940
  run.status = "interrupted";
934
941
  run.updated_at = new Date();
942
+ const thread = STORE.threads[run.thread_id];
943
+ if (thread) {
944
+ thread.status = "idle";
945
+ thread.updated_at = new Date();
946
+ }
935
947
  }
936
948
  else {
937
949
  logger.info("Eagerly deleting unscheduled run with rollback action", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph-api",
3
- "version": "0.0.33",
3
+ "version": "0.0.35",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": "^18.19.0 || >=20.16.0"
@@ -53,7 +53,7 @@
53
53
  "winston": "^3.17.0",
54
54
  "winston-console-format": "^1.0.8",
55
55
  "zod": "^3.23.8",
56
- "@langchain/langgraph-ui": "0.0.33"
56
+ "@langchain/langgraph-ui": "0.0.35"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@langchain/core": "^0.3.42",