@langchain/langgraph-api 0.0.55 → 0.0.57

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @langchain/langgraph-api
2
2
 
3
+ ## 0.0.57
4
+
5
+ ### Patch Changes
6
+
7
+ - 31cc9f7: support description property for `langgraph.json`
8
+ - @langchain/langgraph-ui@0.0.57
9
+
10
+ ## 0.0.56
11
+
12
+ ### Patch Changes
13
+
14
+ - 3c390c9: fix(api): parser: sanitise generated symbol names, honor typescript extension
15
+ - @langchain/langgraph-ui@0.0.56
16
+
3
17
  ## 0.0.55
4
18
 
5
19
  ### Patch Changes
@@ -4,7 +4,10 @@ export declare function spawnServer(args: {
4
4
  nJobsPerWorker: string;
5
5
  }, context: {
6
6
  config: {
7
- graphs: Record<string, string>;
7
+ graphs: Record<string, string | {
8
+ path: string;
9
+ description?: string;
10
+ }>;
8
11
  ui?: Record<string, string>;
9
12
  ui_config?: {
10
13
  shared?: string[];
@@ -35,7 +35,9 @@ export declare class SubgraphExtractor {
35
35
  node: ts.Node;
36
36
  };
37
37
  }[];
38
- getAugmentedSourceFile: (suffix: string, name: string) => {
38
+ getAugmentedSourceFile: (sourcePath: string, name: string, options: {
39
+ allowImportingTsExtensions: boolean;
40
+ }) => {
39
41
  inferFile: {
40
42
  fileName: string;
41
43
  contents: string;
@@ -12,11 +12,6 @@ const OVERRIDE_RESOLVE = [
12
12
  new RegExp(`^@langchain\/langgraph-checkpoint(\/.+)?$`),
13
13
  ];
14
14
  const INFER_TEMPLATE_PATH = path.resolve(__dirname, "./schema/types.template.mts");
15
- const compilerOptions = {
16
- noEmit: true,
17
- strict: true,
18
- allowUnusedLabels: true,
19
- };
20
15
  export class SubgraphExtractor {
21
16
  program;
22
17
  checker;
@@ -145,50 +140,67 @@ export class SubgraphExtractor {
145
140
  return this.findSubgraphs(varDecl.initializer, [name]);
146
141
  });
147
142
  };
148
- getAugmentedSourceFile = (suffix, name) => {
143
+ getAugmentedSourceFile = (sourcePath, name, options) => {
144
+ function sanitize(input) {
145
+ return input.replace(/[^a-zA-Z0-9]/g, "_");
146
+ }
149
147
  const vars = this.getSubgraphsVariables(name);
150
- const typeSuffix = suffix.replace(/[^a-zA-Z0-9]/g, "_");
151
- const typeExports = [
148
+ const ext = path.extname(sourcePath);
149
+ const suffix = sourcePath.slice(0, -ext.length);
150
+ let typeExports = [
152
151
  {
153
- typeName: `__langgraph__${name}_${typeSuffix}`,
152
+ typeName: sanitize(`__langgraph__${name}_${suffix}`),
154
153
  valueName: name,
155
154
  graphName: name,
156
155
  },
157
156
  ];
157
+ const seenTypeName = new Set();
158
158
  for (const { subgraph, node, namespace } of vars) {
159
+ if (seenTypeName.has(subgraph.name))
160
+ continue;
161
+ seenTypeName.add(subgraph.name);
159
162
  typeExports.push({
160
- typeName: `__langgraph__${namespace.join("_")}_${node}_${typeSuffix}`,
163
+ typeName: sanitize(`__langgraph__${namespace.join("_")}_${node}_${suffix}`),
161
164
  valueName: subgraph.name,
162
165
  graphName: [...namespace, node].join("|"),
163
166
  });
164
167
  }
165
- const sourceFilePath = `__langgraph__source_${suffix}.mts`;
168
+ typeExports = typeExports.map(({ typeName, ...rest }) => ({
169
+ ...rest,
170
+ typeName: sanitize(typeName),
171
+ }));
172
+ const sourceFilePath = `__langgraph__source_${sanitize(suffix)}${ext}`;
166
173
  const sourceContents = [
167
174
  this.getText(this.sourceFile),
168
- ...typeExports.map(({ typeName, valueName }) => `export type ${typeName} = typeof ${valueName}`),
169
- ].join("\n\n");
170
- const inferFilePath = `__langgraph__infer_${suffix}.mts`;
175
+ typeExports.map((type) => `export type ${type.typeName} = typeof ${type.valueName}`),
176
+ ];
177
+ const inferFilePath = `__langgraph__infer_${sanitize(suffix)}${ext}`;
178
+ const sourceFileImportPath = options.allowImportingTsExtensions
179
+ ? sourceFilePath
180
+ : sourceFilePath.slice(0, -ext.length) + ext.replace("ts", "js");
171
181
  const inferContents = [
172
- ...typeExports.map(({ typeName }) => `import type { ${typeName}} from "./__langgraph__source_${suffix}.mts"`),
182
+ typeExports.map((type) => `import type { ${type.typeName} } from "./${sourceFileImportPath}"`),
173
183
  this.inferFile.getText(this.inferFile),
174
- ...typeExports.flatMap(({ typeName }) => {
175
- return [
176
- dedent `
177
- type ${typeName}__reflect = Reflect<${typeName}>;
178
- export type ${typeName}__state = Inspect<${typeName}__reflect["state"]>;
179
- export type ${typeName}__update = Inspect<${typeName}__reflect["update"]>;
184
+ typeExports.map((type) => dedent `
185
+ type ${type.typeName}__reflect = Reflect<${type.typeName}>;
186
+ export type ${type.typeName}__state = Inspect<${type.typeName}__reflect["state"]>;
187
+ export type ${type.typeName}__update = Inspect<${type.typeName}__reflect["update"]>;
180
188
 
181
- type ${typeName}__builder = BuilderReflect<${typeName}>;
182
- export type ${typeName}__input = Inspect<FilterAny<${typeName}__builder["input"]>>;
183
- export type ${typeName}__output = Inspect<FilterAny<${typeName}__builder["output"]>>;
184
- export type ${typeName}__config = Inspect<FilterAny<${typeName}__builder["config"]>>;
185
- `,
186
- ];
187
- }),
188
- ].join("\n\n");
189
+ type ${type.typeName}__builder = BuilderReflect<${type.typeName}>;
190
+ export type ${type.typeName}__input = Inspect<FilterAny<${type.typeName}__builder["input"]>>;
191
+ export type ${type.typeName}__output = Inspect<FilterAny<${type.typeName}__builder["output"]>>;
192
+ export type ${type.typeName}__config = Inspect<FilterAny<${type.typeName}__builder["config"]>>;
193
+ `),
194
+ ];
189
195
  return {
190
- inferFile: { fileName: inferFilePath, contents: inferContents },
191
- sourceFile: { fileName: sourceFilePath, contents: sourceContents },
196
+ inferFile: {
197
+ fileName: inferFilePath,
198
+ contents: inferContents.flat(1).join("\n\n"),
199
+ },
200
+ sourceFile: {
201
+ fileName: sourceFilePath,
202
+ contents: sourceContents.flat(1).join("\n\n"),
203
+ },
192
204
  exports: typeExports,
193
205
  };
194
206
  };
@@ -268,6 +280,19 @@ export class SubgraphExtractor {
268
280
  return inputPath.replace(/\\/g, "/");
269
281
  return inputPath;
270
282
  };
283
+ let compilerOptions = {
284
+ noEmit: true,
285
+ strict: true,
286
+ allowUnusedLabels: true,
287
+ };
288
+ // Find tsconfig.json file
289
+ const tsconfigPath = ts.findConfigFile(projectDirname, ts.sys.fileExists, "tsconfig.json");
290
+ // Read tsconfig.json file
291
+ if (tsconfigPath != null) {
292
+ const tsconfigFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
293
+ const parsedTsconfig = ts.parseJsonConfigFileContent(tsconfigFile.config, ts.sys, path.dirname(tsconfigPath));
294
+ compilerOptions = { ...parsedTsconfig.options, ...compilerOptions };
295
+ }
271
296
  const vfsHost = vfs.createVirtualCompilerHost(system, compilerOptions, ts);
272
297
  const host = vfsHost.compilerHost;
273
298
  const targetPaths = [];
@@ -313,12 +338,10 @@ export class SubgraphExtractor {
313
338
  const researchTargets = [];
314
339
  for (const targetPath of targetPaths) {
315
340
  const extractor = new SubgraphExtractor(research, research.getSourceFile(targetPath.sourceFile), research.getSourceFile(INFER_TEMPLATE_PATH), options);
316
- const suffix = path
317
- .relative(projectDirname, targetPath.sourceFile)
318
- .split(path.sep)
319
- .join("__");
320
341
  const graphDirname = path.dirname(targetPath.sourceFile);
321
- const { sourceFile, inferFile, exports } = extractor.getAugmentedSourceFile(suffix, targetPath.exportSymbol);
342
+ const { sourceFile, inferFile, exports } = extractor.getAugmentedSourceFile(path.relative(projectDirname, targetPath.sourceFile), targetPath.exportSymbol, {
343
+ allowImportingTsExtensions: compilerOptions.allowImportingTsExtensions ?? false,
344
+ });
322
345
  for (const { fileName, contents } of [sourceFile, inferFile]) {
323
346
  system.writeFile(vfsPath(path.resolve(graphDirname, fileName)), contents);
324
347
  }
@@ -332,23 +355,70 @@ export class SubgraphExtractor {
332
355
  options: compilerOptions,
333
356
  host,
334
357
  });
358
+ // Print out any diagnostics file that were detected before emitting
359
+ // This may explain why sometimes the schema is invalid.
360
+ const allDiagnostics = ts.getPreEmitDiagnostics(extract);
361
+ for (const diagnostic of allDiagnostics) {
362
+ let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n") + "\n";
363
+ if (diagnostic.file) {
364
+ const fileName = diagnostic.file.fileName;
365
+ const { line, character } = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
366
+ const fileLoc = `(${line + 1},${character + 1})`;
367
+ message = `${fileName} ${fileLoc}: ${message}`;
368
+ }
369
+ console.log(message);
370
+ }
335
371
  const schemaGenerator = buildGenerator(extract);
336
- const trySymbol = (schema, symbol) => {
372
+ const trySymbol = (symbol) => {
373
+ let schema = undefined;
337
374
  try {
338
- return schema?.getSchemaForSymbol(symbol) ?? undefined;
375
+ schema = schemaGenerator?.getSchemaForSymbol(symbol) ?? undefined;
339
376
  }
340
377
  catch (e) {
341
378
  console.warn(`Failed to obtain symbol "${symbol}":`, e?.message);
342
379
  }
343
- return undefined;
380
+ if (schema == null)
381
+ return undefined;
382
+ const definitions = schema.definitions;
383
+ if (definitions == null)
384
+ return schema;
385
+ const toReplace = Object.keys(definitions).flatMap((key) => {
386
+ const replacedKey = key.includes("import(")
387
+ ? key.replace(/import\(.+@langchain[\\/]core.+\)\./, "")
388
+ : key;
389
+ if (key !== replacedKey && definitions[replacedKey] == null) {
390
+ return [
391
+ {
392
+ source: key,
393
+ target: replacedKey,
394
+ sourceRef: `#/definitions/${key}`,
395
+ targetRef: `#/definitions/${replacedKey}`,
396
+ },
397
+ ];
398
+ }
399
+ return [];
400
+ });
401
+ for (const { source, target } of toReplace) {
402
+ definitions[target] = definitions[source];
403
+ delete definitions[source];
404
+ }
405
+ const refMap = toReplace.reduce((acc, item) => {
406
+ acc[item.sourceRef] = item.targetRef;
407
+ return acc;
408
+ }, {});
409
+ return JSON.parse(JSON.stringify(schema, (_, value) => {
410
+ if (typeof value === "string" && refMap[value])
411
+ return refMap[value];
412
+ return value;
413
+ }));
344
414
  };
345
415
  return researchTargets.map(({ exports }) => Object.fromEntries(exports.map(({ typeName, graphName }) => [
346
416
  graphName,
347
417
  {
348
- state: trySymbol(schemaGenerator, `${typeName}__update`),
349
- input: trySymbol(schemaGenerator, `${typeName}__input`),
350
- output: trySymbol(schemaGenerator, `${typeName}__output`),
351
- config: trySymbol(schemaGenerator, `${typeName}__config`),
418
+ state: trySymbol(`${typeName}__update`),
419
+ input: trySymbol(`${typeName}__input`),
420
+ output: trySymbol(`${typeName}__output`),
421
+ config: trySymbol(`${typeName}__config`),
352
422
  },
353
423
  ])));
354
424
  }
@@ -5,14 +5,14 @@ import type {
5
5
  StateDefinition,
6
6
  } from "@langchain/langgraph";
7
7
 
8
- // @ts-expect-error
8
+ // @ts-ignore
9
9
  type AnyPregel = {
10
10
  lg_is_pregel: boolean;
11
11
  stream: (...args: any[]) => any;
12
12
  invoke: (...args: any[]) => any;
13
13
  };
14
14
 
15
- // @ts-expect-error
15
+ // @ts-ignore
16
16
  type AnyGraph = {
17
17
  compiled: boolean;
18
18
  compile: (...args: any[]) => any;
@@ -30,7 +30,7 @@ type Defactorify<T> = T extends (...args: any[]) => infer R
30
30
  ? Awaited<R>
31
31
  : Awaited<T>;
32
32
 
33
- // @ts-expect-error
33
+ // @ts-ignore
34
34
  type Inspect<T, TDepth extends Array<0> = []> = TDepth extends [0, 0, 0]
35
35
  ? any
36
36
  : T extends unknown
@@ -51,7 +51,7 @@ type ReflectCompiled<T> = T extends { RunInput: infer S; RunOutput: infer U }
51
51
  ? { state: OutputType; update: InputType }
52
52
  : never;
53
53
 
54
- // @ts-expect-error
54
+ // @ts-ignore
55
55
  type Reflect<T> = Defactorify<T> extends infer DT
56
56
  ? DT extends {
57
57
  compile(...args: any[]): infer Compiled;
@@ -74,7 +74,7 @@ type BuilderReflectCompiled<T> = T extends {
74
74
  }
75
75
  : never;
76
76
 
77
- // @ts-expect-error
77
+ // @ts-ignore
78
78
  type BuilderReflect<T> = Defactorify<T> extends infer DT
79
79
  ? DT extends {
80
80
  compile(...args: any[]): infer Compiled;
@@ -83,5 +83,5 @@ type BuilderReflect<T> = Defactorify<T> extends infer DT
83
83
  : BuilderReflectCompiled<DT>
84
84
  : never;
85
85
 
86
- // @ts-expect-error
86
+ // @ts-ignore
87
87
  type FilterAny<T> = 0 extends 1 & T ? never : T;
package/dist/preload.mjs CHANGED
@@ -8,9 +8,18 @@ const options = JSON.parse(lastArg || "{}");
8
8
  // find the first file, as `parentURL` needs to be a valid file URL
9
9
  // if no graph found, just assume a dummy default file, which should
10
10
  // be working fine as well.
11
- const firstGraphFile = Object.values(options.graphs)
12
- .flatMap((i) => i.split(":").at(0))
13
- .at(0) || "index.mts";
11
+ const graphFiles = Object.values(options.graphs)
12
+ .map((i) => {
13
+ if (typeof i === "string") {
14
+ return i.split(":").at(0);
15
+ }
16
+ else if (i && typeof i === "object" && i.path) {
17
+ return i.path.split(":").at(0);
18
+ }
19
+ return null;
20
+ })
21
+ .filter(Boolean);
22
+ const firstGraphFile = graphFiles.at(0) || "index.mts";
14
23
  // enforce API @langchain/langgraph resolution
15
24
  register("./graph/load.hooks.mjs", import.meta.url, {
16
25
  parentURL: "data:",
package/dist/server.d.mts CHANGED
@@ -4,7 +4,16 @@ export declare const StartServerSchema: z.ZodObject<{
4
4
  nWorkers: z.ZodNumber;
5
5
  host: z.ZodString;
6
6
  cwd: z.ZodString;
7
- graphs: z.ZodRecord<z.ZodString, z.ZodString>;
7
+ graphs: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodObject<{
8
+ path: z.ZodString;
9
+ description: z.ZodOptional<z.ZodString>;
10
+ }, "strip", z.ZodTypeAny, {
11
+ path: string;
12
+ description?: string | undefined;
13
+ }, {
14
+ path: string;
15
+ description?: string | undefined;
16
+ }>]>>;
8
17
  auth: z.ZodOptional<z.ZodObject<{
9
18
  path: z.ZodOptional<z.ZodString>;
10
19
  disable_studio_auth: z.ZodDefault<z.ZodBoolean>;
@@ -93,7 +102,10 @@ export declare const StartServerSchema: z.ZodObject<{
93
102
  host: string;
94
103
  port: number;
95
104
  nWorkers: number;
96
- graphs: Record<string, string>;
105
+ graphs: Record<string, string | {
106
+ path: string;
107
+ description?: string | undefined;
108
+ }>;
97
109
  auth?: {
98
110
  disable_studio_auth: boolean;
99
111
  path?: string | undefined;
@@ -124,7 +136,10 @@ export declare const StartServerSchema: z.ZodObject<{
124
136
  host: string;
125
137
  port: number;
126
138
  nWorkers: number;
127
- graphs: Record<string, string>;
139
+ graphs: Record<string, string | {
140
+ path: string;
141
+ description?: string | undefined;
142
+ }>;
128
143
  auth?: {
129
144
  path?: string | undefined;
130
145
  disable_studio_auth?: boolean | undefined;
package/dist/server.mjs CHANGED
@@ -25,7 +25,10 @@ export const StartServerSchema = z.object({
25
25
  nWorkers: z.number(),
26
26
  host: z.string(),
27
27
  cwd: z.string(),
28
- graphs: z.record(z.string()),
28
+ graphs: z.record(z.union([
29
+ z.string(),
30
+ z.object({ path: z.string(), description: z.string().optional() }),
31
+ ])),
29
32
  auth: z
30
33
  .object({
31
34
  path: z.string().optional(),
@@ -79,7 +82,20 @@ export async function startServer(options) {
79
82
  // We need to do this before we load the graphs in-case the logger is obtained at top-level.
80
83
  registerSdkLogger();
81
84
  logger.info(`Registering graphs from ${options.cwd}`);
82
- await registerFromEnv(options.graphs, { cwd: options.cwd });
85
+ let hasGraphDescriptions = false;
86
+ const graphPaths = Object.fromEntries(Object.entries(options.graphs).map(([graphId, rawSpec]) => {
87
+ if (typeof rawSpec === "string") {
88
+ return [graphId, rawSpec];
89
+ }
90
+ if (rawSpec.description) {
91
+ hasGraphDescriptions = true;
92
+ }
93
+ return [graphId, rawSpec.path];
94
+ }));
95
+ if (hasGraphDescriptions) {
96
+ logger.warn("A graph definition in `langgraph.json` has a `description` property. Local MCP features are not yet supported with the JS CLI and will be ignored.");
97
+ }
98
+ await registerFromEnv(graphPaths, { cwd: options.cwd });
83
99
  registerRuntimeLogFormatter((info) => {
84
100
  const config = getConfig();
85
101
  if (config == null)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph-api",
3
- "version": "0.0.55",
3
+ "version": "0.0.57",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": "^18.19.0 || >=20.16.0"
@@ -52,7 +52,7 @@
52
52
  "@babel/code-frame": "^7.26.2",
53
53
  "@hono/node-server": "^1.12.0",
54
54
  "@hono/zod-validator": "^0.2.2",
55
- "@langchain/langgraph-ui": "0.0.55",
55
+ "@langchain/langgraph-ui": "0.0.57",
56
56
  "@types/json-schema": "^7.0.15",
57
57
  "@typescript/vfs": "^1.6.0",
58
58
  "dedent": "^1.5.3",
@@ -84,7 +84,7 @@
84
84
  },
85
85
  "devDependencies": {
86
86
  "@langchain/core": "^0.3.59",
87
- "@langchain/langgraph": "0.4.1",
87
+ "@langchain/langgraph": "0.4.3",
88
88
  "@langchain/langgraph-checkpoint": "0.1.0",
89
89
  "@langchain/langgraph-sdk": "0.0.105",
90
90
  "@types/babel__code-frame": "^7.0.6",