@langchain/langgraph-api 0.0.34 → 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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph-api",
3
- "version": "0.0.34",
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.34"
56
+ "@langchain/langgraph-ui": "0.0.35"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@langchain/core": "^0.3.42",