langgraph-api 0.0.1__py3-none-any.whl

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.

Potentially problematic release.


This version of langgraph-api might be problematic. Click here for more details.

Files changed (86) hide show
  1. LICENSE +93 -0
  2. langgraph_api/__init__.py +0 -0
  3. langgraph_api/api/__init__.py +63 -0
  4. langgraph_api/api/assistants.py +326 -0
  5. langgraph_api/api/meta.py +71 -0
  6. langgraph_api/api/openapi.py +32 -0
  7. langgraph_api/api/runs.py +463 -0
  8. langgraph_api/api/store.py +116 -0
  9. langgraph_api/api/threads.py +263 -0
  10. langgraph_api/asyncio.py +201 -0
  11. langgraph_api/auth/__init__.py +0 -0
  12. langgraph_api/auth/langsmith/__init__.py +0 -0
  13. langgraph_api/auth/langsmith/backend.py +67 -0
  14. langgraph_api/auth/langsmith/client.py +145 -0
  15. langgraph_api/auth/middleware.py +41 -0
  16. langgraph_api/auth/noop.py +14 -0
  17. langgraph_api/cli.py +209 -0
  18. langgraph_api/config.py +70 -0
  19. langgraph_api/cron_scheduler.py +60 -0
  20. langgraph_api/errors.py +52 -0
  21. langgraph_api/graph.py +314 -0
  22. langgraph_api/http.py +168 -0
  23. langgraph_api/http_logger.py +89 -0
  24. langgraph_api/js/.gitignore +2 -0
  25. langgraph_api/js/build.mts +49 -0
  26. langgraph_api/js/client.mts +849 -0
  27. langgraph_api/js/global.d.ts +6 -0
  28. langgraph_api/js/package.json +33 -0
  29. langgraph_api/js/remote.py +673 -0
  30. langgraph_api/js/server_sent_events.py +126 -0
  31. langgraph_api/js/src/graph.mts +88 -0
  32. langgraph_api/js/src/hooks.mjs +12 -0
  33. langgraph_api/js/src/parser/parser.mts +443 -0
  34. langgraph_api/js/src/parser/parser.worker.mjs +12 -0
  35. langgraph_api/js/src/schema/types.mts +2136 -0
  36. langgraph_api/js/src/schema/types.template.mts +74 -0
  37. langgraph_api/js/src/utils/importMap.mts +85 -0
  38. langgraph_api/js/src/utils/pythonSchemas.mts +28 -0
  39. langgraph_api/js/src/utils/serde.mts +21 -0
  40. langgraph_api/js/tests/api.test.mts +1566 -0
  41. langgraph_api/js/tests/compose-postgres.yml +56 -0
  42. langgraph_api/js/tests/graphs/.gitignore +1 -0
  43. langgraph_api/js/tests/graphs/agent.mts +127 -0
  44. langgraph_api/js/tests/graphs/error.mts +17 -0
  45. langgraph_api/js/tests/graphs/langgraph.json +8 -0
  46. langgraph_api/js/tests/graphs/nested.mts +44 -0
  47. langgraph_api/js/tests/graphs/package.json +7 -0
  48. langgraph_api/js/tests/graphs/weather.mts +57 -0
  49. langgraph_api/js/tests/graphs/yarn.lock +159 -0
  50. langgraph_api/js/tests/parser.test.mts +870 -0
  51. langgraph_api/js/tests/utils.mts +17 -0
  52. langgraph_api/js/yarn.lock +1340 -0
  53. langgraph_api/lifespan.py +41 -0
  54. langgraph_api/logging.py +121 -0
  55. langgraph_api/metadata.py +101 -0
  56. langgraph_api/models/__init__.py +0 -0
  57. langgraph_api/models/run.py +229 -0
  58. langgraph_api/patch.py +42 -0
  59. langgraph_api/queue.py +245 -0
  60. langgraph_api/route.py +118 -0
  61. langgraph_api/schema.py +190 -0
  62. langgraph_api/serde.py +124 -0
  63. langgraph_api/server.py +48 -0
  64. langgraph_api/sse.py +118 -0
  65. langgraph_api/state.py +67 -0
  66. langgraph_api/stream.py +289 -0
  67. langgraph_api/utils.py +60 -0
  68. langgraph_api/validation.py +141 -0
  69. langgraph_api-0.0.1.dist-info/LICENSE +93 -0
  70. langgraph_api-0.0.1.dist-info/METADATA +26 -0
  71. langgraph_api-0.0.1.dist-info/RECORD +86 -0
  72. langgraph_api-0.0.1.dist-info/WHEEL +4 -0
  73. langgraph_api-0.0.1.dist-info/entry_points.txt +3 -0
  74. langgraph_license/__init__.py +0 -0
  75. langgraph_license/middleware.py +21 -0
  76. langgraph_license/validation.py +11 -0
  77. langgraph_storage/__init__.py +0 -0
  78. langgraph_storage/checkpoint.py +94 -0
  79. langgraph_storage/database.py +190 -0
  80. langgraph_storage/ops.py +1523 -0
  81. langgraph_storage/queue.py +108 -0
  82. langgraph_storage/retry.py +27 -0
  83. langgraph_storage/store.py +28 -0
  84. langgraph_storage/ttl_dict.py +54 -0
  85. logging.json +22 -0
  86. openapi.json +4304 -0
@@ -0,0 +1,126 @@
1
+ """Adapted from https://github.com/florimondmanca/httpx-sse"""
2
+
3
+ from collections.abc import AsyncIterator, Iterator
4
+ from contextlib import asynccontextmanager
5
+ from typing import Any, TypedDict
6
+
7
+ import httpx
8
+
9
+
10
+ class ServerSentEvent(TypedDict):
11
+ event: str | None
12
+ data: str | None
13
+ id: str | None
14
+ retry: int | None
15
+
16
+
17
+ class SSEDecoder:
18
+ def __init__(self) -> None:
19
+ self._event = ""
20
+ self._data: list[str] = []
21
+ self._last_event_id = ""
22
+ self._retry: int | None = None
23
+
24
+ def decode(self, line: str) -> ServerSentEvent | None:
25
+ # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501
26
+ if not line:
27
+ if (
28
+ not self._event
29
+ and not self._data
30
+ and not self._last_event_id
31
+ and self._retry is None
32
+ ):
33
+ return None
34
+
35
+ sse = {
36
+ "event": self._event,
37
+ "data": "\n".join(self._data),
38
+ "id": self._last_event_id,
39
+ "retry": self._retry,
40
+ }
41
+
42
+ # NOTE: as per the SSE spec, do not reset last_event_id.
43
+ self._event = ""
44
+ self._data = []
45
+ self._retry = None
46
+
47
+ return sse
48
+
49
+ if line.startswith(":"):
50
+ return None
51
+
52
+ fieldname, _, value = line.partition(":")
53
+
54
+ if value.startswith(" "):
55
+ value = value[1:]
56
+
57
+ if fieldname == "event":
58
+ self._event = value
59
+ elif fieldname == "data":
60
+ self._data.append(value)
61
+ elif fieldname == "id":
62
+ if "\0" in value:
63
+ pass
64
+ else:
65
+ self._last_event_id = value
66
+ elif fieldname == "retry":
67
+ try:
68
+ self._retry = int(value)
69
+ except (TypeError, ValueError):
70
+ pass
71
+ else:
72
+ pass # Field is ignored.
73
+
74
+ return None
75
+
76
+
77
+ class EventSource:
78
+ def __init__(self, response: httpx.Response) -> None:
79
+ self._response = response
80
+
81
+ def _check_content_type(self) -> None:
82
+ """Check that the response content type is 'text/event-stream'."""
83
+ self._response.raise_for_status()
84
+ content_type = self._response.headers.get("content-type", "").partition(";")[0]
85
+ if "text/event-stream" not in content_type:
86
+ raise AssertionError(
87
+ "Expected response header Content-Type to contain 'text/event-stream', "
88
+ f"got {content_type!r}"
89
+ )
90
+
91
+ @property
92
+ def response(self) -> httpx.Response:
93
+ return self._response
94
+
95
+ def iter_sse(self) -> Iterator[ServerSentEvent]:
96
+ self._check_content_type()
97
+ decoder = SSEDecoder()
98
+ for line in self._response.iter_lines():
99
+ line = line.rstrip("\n")
100
+ sse = decoder.decode(line)
101
+ if sse is not None:
102
+ yield sse
103
+
104
+ async def aiter_sse(self) -> AsyncIterator[ServerSentEvent]:
105
+ self._check_content_type()
106
+ decoder = SSEDecoder()
107
+ async for line in self._response.aiter_lines():
108
+ line = line.rstrip("\n")
109
+ sse = decoder.decode(line)
110
+ if sse is not None:
111
+ yield sse
112
+
113
+
114
+ @asynccontextmanager
115
+ async def aconnect_sse(
116
+ client: httpx.AsyncClient,
117
+ method: str,
118
+ url: str,
119
+ **kwargs: Any,
120
+ ) -> AsyncIterator[EventSource]:
121
+ headers = kwargs.pop("headers", {})
122
+ headers["Accept"] = "text/event-stream"
123
+ headers["Cache-Control"] = "no-store"
124
+
125
+ async with client.stream(method, url, headers=headers, **kwargs) as response:
126
+ yield EventSource(response)
@@ -0,0 +1,88 @@
1
+ import { Worker } from "node:worker_threads";
2
+ import { register } from "node:module";
3
+ import * as fs from "node:fs/promises";
4
+ import type { CompiledGraph, Graph } from "@langchain/langgraph";
5
+ import * as path from "node:path";
6
+ import type { JSONSchema7 } from "json-schema";
7
+
8
+ // enforce API @langchain/langgraph precedence
9
+ register("./hooks.mjs", import.meta.url);
10
+
11
+ export interface GraphSchema {
12
+ state: JSONSchema7 | undefined;
13
+ input: JSONSchema7 | undefined;
14
+ output: JSONSchema7 | undefined;
15
+ config: JSONSchema7 | undefined;
16
+ }
17
+
18
+ export interface GraphSpec {
19
+ sourceFile: string;
20
+ exportSymbol: string;
21
+ }
22
+
23
+ export async function resolveGraph(
24
+ spec: string,
25
+ options?: { onlyFilePresence?: boolean }
26
+ ) {
27
+ const [userFile, exportSymbol] = spec.split(":", 2);
28
+
29
+ // validate file exists
30
+ await fs.stat(userFile);
31
+ if (options?.onlyFilePresence) {
32
+ return { sourceFile: userFile, exportSymbol, resolved: undefined };
33
+ }
34
+
35
+ const sourceFile = path.resolve(process.cwd(), userFile);
36
+ type GraphLike = CompiledGraph<string> | Graph<string>;
37
+
38
+ type GraphUnknown =
39
+ | GraphLike
40
+ | Promise<GraphLike>
41
+ | (() => GraphLike | Promise<GraphLike>)
42
+ | undefined;
43
+
44
+ const isGraph = (graph: GraphLike): graph is Graph<string> => {
45
+ if (typeof graph !== "object" || graph == null) return false;
46
+ return "compile" in graph && typeof graph.compile === "function";
47
+ };
48
+
49
+ const graph: GraphUnknown = await import(sourceFile).then(
50
+ (module) => module[exportSymbol || "default"]
51
+ );
52
+
53
+ // obtain the graph, and if not compiled, compile it
54
+ const resolved: CompiledGraph<string> = await (async () => {
55
+ if (!graph) throw new Error("Failed to load graph: graph is nullush");
56
+ const graphLike = typeof graph === "function" ? await graph() : await graph;
57
+
58
+ if (isGraph(graphLike)) return graphLike.compile();
59
+ return graphLike;
60
+ })();
61
+
62
+ return { sourceFile, exportSymbol, resolved };
63
+ }
64
+
65
+ export async function runGraphSchemaWorker(spec: GraphSpec) {
66
+ const SCHEMA_RESOLVE_TIMEOUT_MS = 30_000;
67
+
68
+ return await new Promise<Record<string, GraphSchema>>((resolve, reject) => {
69
+ const worker = new Worker(
70
+ new URL("./parser/parser.worker.mjs", import.meta.url).pathname
71
+ );
72
+
73
+ // Set a timeout to reject if the worker takes too long
74
+ const timeoutId = setTimeout(() => {
75
+ worker.terminate();
76
+ reject(new Error("Schema extract worker timed out"));
77
+ }, SCHEMA_RESOLVE_TIMEOUT_MS);
78
+
79
+ worker.on("message", (result) => {
80
+ worker.terminate();
81
+ clearTimeout(timeoutId);
82
+ resolve(result);
83
+ });
84
+
85
+ worker.on("error", reject);
86
+ worker.postMessage(spec);
87
+ });
88
+ }
@@ -0,0 +1,12 @@
1
+ // This hook is to ensure that @langchain/langgraph package
2
+ // found in /api folder has precendence compared to user-provided package
3
+ // found in /deps. Does not attempt to semver check for too old packages.
4
+ export async function resolve(specifier, context, nextResolve) {
5
+ const parentURL = new URL("./graph.mts", import.meta.url).toString();
6
+
7
+ if (specifier.startsWith("@langchain/langgraph")) {
8
+ return nextResolve(specifier, { ...context, parentURL });
9
+ }
10
+
11
+ return nextResolve(specifier, context);
12
+ }
@@ -0,0 +1,443 @@
1
+ import * as ts from "typescript";
2
+ import * as vfs from "@typescript/vfs";
3
+ import * as path from "node:path";
4
+ import dedent from "dedent";
5
+ import { buildGenerator } from "../schema/types.mts";
6
+
7
+ const __dirname = new URL(".", import.meta.url).pathname;
8
+
9
+ const compilerOptions = {
10
+ noEmit: true,
11
+ strict: true,
12
+ allowUnusedLabels: true,
13
+ };
14
+
15
+ export class SubgraphExtractor {
16
+ protected program: ts.Program;
17
+ protected checker: ts.TypeChecker;
18
+ protected sourceFile: ts.SourceFile;
19
+ protected inferFile: ts.SourceFile;
20
+
21
+ protected anyPregelType: ts.Type;
22
+ protected anyGraphType: ts.Type;
23
+
24
+ protected strict: boolean;
25
+
26
+ constructor(
27
+ program: ts.Program,
28
+ sourceFile: ts.SourceFile,
29
+ inferFile: ts.SourceFile,
30
+ options?: { strict?: boolean }
31
+ ) {
32
+ this.program = program;
33
+ this.sourceFile = sourceFile;
34
+ this.inferFile = inferFile;
35
+
36
+ this.checker = program.getTypeChecker();
37
+ this.strict = options?.strict ?? false;
38
+
39
+ this.anyPregelType = this.findTypeByName("AnyPregel");
40
+ this.anyGraphType = this.findTypeByName("AnyGraph");
41
+ }
42
+
43
+ private findTypeByName = (needle: string) => {
44
+ let result: ts.Type | undefined = undefined;
45
+
46
+ const visit = (node: ts.Node) => {
47
+ if (ts.isTypeAliasDeclaration(node)) {
48
+ const symbol = (node as any).symbol;
49
+
50
+ if (symbol != null) {
51
+ const name = this.checker
52
+ .getFullyQualifiedName(symbol)
53
+ .replace(/".*"\./, "");
54
+ if (name === needle) result = this.checker.getTypeAtLocation(node);
55
+ }
56
+ }
57
+ if (result == null) ts.forEachChild(node, visit);
58
+ };
59
+
60
+ ts.forEachChild(this.inferFile, visit);
61
+ if (!result) throw new Error(`Failed to find "${needle}" type`);
62
+ return result;
63
+ };
64
+
65
+ private find = (
66
+ root: ts.Node,
67
+ predicate: (node: ts.Node) => boolean
68
+ ): ts.Node | undefined => {
69
+ let result: ts.Node | undefined = undefined;
70
+
71
+ const visit = (node: ts.Node) => {
72
+ if (predicate(node)) {
73
+ result = node;
74
+ } else {
75
+ ts.forEachChild(node, visit);
76
+ }
77
+ };
78
+
79
+ if (predicate(root)) return root;
80
+ ts.forEachChild(root, visit);
81
+ return result;
82
+ };
83
+
84
+ protected findSubgraphs = (
85
+ node: ts.Node,
86
+ namespace: string[] = []
87
+ ): {
88
+ node: string;
89
+ namespace: string[];
90
+ subgraph: { name: string; node: ts.Node };
91
+ }[] => {
92
+ const findAllAddNodeCalls = (
93
+ acc: {
94
+ node: string;
95
+ namespace: string[];
96
+ subgraph: { name: string; node: ts.Node };
97
+ }[],
98
+ node: ts.Node
99
+ ) => {
100
+ if (ts.isCallExpression(node)) {
101
+ const firstChild = node.getChildAt(0);
102
+
103
+ if (
104
+ ts.isPropertyAccessExpression(firstChild) &&
105
+ this.getText(firstChild.name) === "addNode"
106
+ ) {
107
+ let nodeName: string = "unknown";
108
+ let variables: { node: ts.Node; name: string }[] = [];
109
+
110
+ const [subgraphNode, callArg] = node.arguments;
111
+
112
+ if (subgraphNode && ts.isStringLiteralLike(subgraphNode)) {
113
+ nodeName = this.getText(subgraphNode);
114
+ if (
115
+ (nodeName.startsWith(`"`) && nodeName.endsWith(`"`)) ||
116
+ (nodeName.startsWith(`'`) && nodeName.endsWith(`'`))
117
+ ) {
118
+ nodeName = nodeName.slice(1, -1);
119
+ }
120
+ }
121
+
122
+ if (callArg) {
123
+ if (
124
+ ts.isFunctionLike(callArg) ||
125
+ ts.isCallLikeExpression(callArg)
126
+ ) {
127
+ variables = this.reduceChildren(
128
+ callArg,
129
+ this.findSubgraphIdentifiers,
130
+ []
131
+ );
132
+ } else if (ts.isIdentifier(callArg)) {
133
+ variables = this.findSubgraphIdentifiers([], callArg);
134
+ }
135
+ }
136
+
137
+ if (variables.length > 0) {
138
+ if (variables.length > 1) {
139
+ const targetName = [...namespace, nodeName].join("|");
140
+ const errMsg = `Multiple unique subgraph invocations found for "${targetName}"`;
141
+ if (this.strict) throw new Error(errMsg);
142
+ console.warn(errMsg);
143
+ }
144
+
145
+ acc.push({
146
+ namespace: namespace,
147
+ node: nodeName,
148
+ subgraph: variables[0],
149
+ });
150
+ }
151
+ }
152
+ }
153
+
154
+ return acc;
155
+ };
156
+
157
+ let subgraphs = this.reduceChildren(node, findAllAddNodeCalls, []);
158
+
159
+ // TODO: make this more strict, only traverse the flow graph only
160
+ // if no `addNode` calls were found
161
+ if (!subgraphs.length) {
162
+ // internal property, however relied upon by ts-ast-viewer et all
163
+ // so that we don't need to traverse the control flow ourselves
164
+ // https://github.com/microsoft/TypeScript/pull/58036
165
+ type InternalFlowNode = ts.Node & { flowNode?: { node: ts.Node } };
166
+ const candidate = this.find(
167
+ node,
168
+ (node: any) => node && "flowNode" in node && node.flowNode
169
+ ) as InternalFlowNode | undefined;
170
+
171
+ if (
172
+ candidate?.flowNode &&
173
+ this.isGraphOrPregelType(
174
+ this.checker.getTypeAtLocation(candidate.flowNode.node)
175
+ )
176
+ ) {
177
+ subgraphs = this.findSubgraphs(candidate.flowNode.node, namespace);
178
+ }
179
+ }
180
+
181
+ // handle recursive behaviour
182
+ if (subgraphs.length > 0) {
183
+ return [
184
+ ...subgraphs,
185
+ ...subgraphs.map(({ subgraph, node }) =>
186
+ this.findSubgraphs(subgraph.node, [...namespace, node])
187
+ ),
188
+ ].flat();
189
+ }
190
+
191
+ return subgraphs;
192
+ };
193
+
194
+ protected getSubgraphsVariables = (name: string) => {
195
+ const sourceSymbol = this.checker.getSymbolAtLocation(this.sourceFile)!;
196
+ const exports = this.checker.getExportsOfModule(sourceSymbol);
197
+
198
+ const targetExport = exports.find((item) => item.name === name);
199
+ if (!targetExport) throw new Error(`Failed to find export "${name}"`);
200
+ const varDecls = (targetExport.declarations ?? []).filter(
201
+ ts.isVariableDeclaration
202
+ );
203
+
204
+ return varDecls.flatMap((varDecl) => {
205
+ if (!varDecl.initializer) return [];
206
+ return this.findSubgraphs(varDecl.initializer, [name]);
207
+ });
208
+ };
209
+
210
+ public getAugmentedSourceFile = (
211
+ name: string
212
+ ): {
213
+ files: [filePath: string, contents: string][];
214
+ exports: { typeName: string; valueName: string; graphName: string }[];
215
+ } => {
216
+ const vars = this.getSubgraphsVariables(name);
217
+ type TypeExport = {
218
+ typeName: `__langgraph__${string}`;
219
+ valueName: string;
220
+ graphName: string;
221
+ };
222
+
223
+ const typeExports: TypeExport[] = [
224
+ { typeName: `__langgraph__${name}`, valueName: name, graphName: name },
225
+ ];
226
+
227
+ for (const { subgraph, node, namespace } of vars) {
228
+ typeExports.push({
229
+ typeName: `__langgraph__${namespace.join("_")}_${node}`,
230
+ valueName: subgraph.name,
231
+ graphName: [...namespace, node].join("|"),
232
+ });
233
+ }
234
+
235
+ const sourceFilePath = "__langgraph__source.mts";
236
+ const sourceContents = [
237
+ this.getText(this.sourceFile),
238
+ ...typeExports.map(
239
+ ({ typeName, valueName }) =>
240
+ `export type ${typeName} = typeof ${valueName}`
241
+ ),
242
+ ].join("\n\n");
243
+
244
+ const inferFilePath = "__langraph__infer.mts";
245
+ const inferContents = [
246
+ ...typeExports.map(
247
+ ({ typeName }) =>
248
+ `import type { ${typeName}} from "./__langgraph__source.mts"`
249
+ ),
250
+ this.inferFile.getText(this.inferFile),
251
+
252
+ ...typeExports.flatMap(({ typeName }) => {
253
+ return [
254
+ dedent`
255
+ type ${typeName}__reflect = Reflect<${typeName}>;
256
+ export type ${typeName}__state = Inspect<${typeName}__reflect["state"]>;
257
+ export type ${typeName}__update = Inspect<${typeName}__reflect["update"]>;
258
+
259
+ type ${typeName}__builder = BuilderReflect<${typeName}>;
260
+ export type ${typeName}__input = Inspect<FilterAny<${typeName}__builder["input"]>>;
261
+ export type ${typeName}__output = Inspect<FilterAny<${typeName}__builder["output"]>>;
262
+ export type ${typeName}__config = Inspect<FilterAny<${typeName}__builder["config"]>>;
263
+ `,
264
+ ];
265
+ }),
266
+ ].join("\n\n");
267
+
268
+ return {
269
+ files: [
270
+ [sourceFilePath, sourceContents],
271
+ [inferFilePath, inferContents],
272
+ ],
273
+ exports: typeExports,
274
+ };
275
+ };
276
+
277
+ protected findSubgraphIdentifiers = (
278
+ acc: { node: ts.Node; name: string }[],
279
+ node: ts.Node
280
+ ) => {
281
+ if (ts.isIdentifier(node)) {
282
+ const smb = this.checker.getSymbolAtLocation(node);
283
+
284
+ if (
285
+ smb?.valueDeclaration &&
286
+ ts.isVariableDeclaration(smb.valueDeclaration)
287
+ ) {
288
+ const target = smb.valueDeclaration;
289
+ const targetType = this.checker.getTypeAtLocation(target);
290
+
291
+ if (this.isGraphOrPregelType(targetType)) {
292
+ acc.push({ name: this.getText(target.name), node: target });
293
+ }
294
+ }
295
+
296
+ if (smb?.declarations) {
297
+ const target = smb.declarations.find(ts.isImportSpecifier);
298
+ if (target) {
299
+ const targetType = this.checker.getTypeAtLocation(target);
300
+ if (this.isGraphOrPregelType(targetType)) {
301
+ acc.push({ name: this.getText(target.name), node: target });
302
+ }
303
+ }
304
+ }
305
+ }
306
+
307
+ return acc;
308
+ };
309
+
310
+ protected isGraphOrPregelType = (type: ts.Type) => {
311
+ return (
312
+ this.checker.isTypeAssignableTo(type, this.anyPregelType) ||
313
+ this.checker.isTypeAssignableTo(type, this.anyGraphType)
314
+ );
315
+ };
316
+
317
+ protected getText(node: ts.Node) {
318
+ return node.getText(this.sourceFile);
319
+ }
320
+
321
+ protected reduceChildren<Acc>(
322
+ node: ts.Node,
323
+ fn: (acc: Acc, node: ts.Node) => Acc,
324
+ initial: Acc
325
+ ): Acc {
326
+ let acc = initial;
327
+ function it(node: ts.Node) {
328
+ acc = fn(acc, node);
329
+ ts.forEachChild(node, it.bind(this));
330
+ }
331
+
332
+ ts.forEachChild(node, it.bind(this));
333
+ return acc;
334
+ }
335
+
336
+ static extractSchemas(
337
+ target:
338
+ | string
339
+ | {
340
+ contents: string;
341
+ files?: [fileName: string, contents: string][];
342
+ },
343
+ name: string,
344
+ options?: { strict?: boolean }
345
+ ) {
346
+ const dirname =
347
+ typeof target === "string" ? path.dirname(target) : __dirname;
348
+
349
+ const fsMap = new Map<string, string>();
350
+ const system = vfs.createFSBackedSystem(fsMap, dirname, ts);
351
+ const host = vfs.createVirtualCompilerHost(system, compilerOptions, ts);
352
+
353
+ const targetPath =
354
+ typeof target === "string"
355
+ ? target
356
+ : path.resolve(dirname, "./__langgraph__target.mts");
357
+
358
+ const inferTemplatePath = path.resolve(
359
+ __dirname,
360
+ "../schema/types.template.mts"
361
+ );
362
+
363
+ if (typeof target !== "string") {
364
+ fsMap.set(targetPath, target.contents);
365
+ for (const [name, contents] of target.files ?? []) {
366
+ fsMap.set(path.resolve(dirname, name), contents);
367
+ }
368
+ }
369
+
370
+ host.compilerHost.resolveModuleNames = (moduleNames, containingFile) => {
371
+ const resolvedModules: (ts.ResolvedModule | undefined)[] = [];
372
+ for (const moduleName of moduleNames) {
373
+ let target = containingFile;
374
+ const relative = path.relative(dirname, containingFile);
375
+ if (
376
+ moduleName.startsWith("@langchain/langgraph") &&
377
+ relative &&
378
+ !relative.startsWith("..") &&
379
+ !path.isAbsolute(relative)
380
+ ) {
381
+ target = path.resolve(__dirname, relative);
382
+ }
383
+
384
+ resolvedModules.push(
385
+ ts.resolveModuleName(
386
+ moduleName,
387
+ target,
388
+ compilerOptions,
389
+ host.compilerHost
390
+ ).resolvedModule
391
+ );
392
+ }
393
+
394
+ return resolvedModules;
395
+ };
396
+
397
+ const research = ts.createProgram({
398
+ rootNames: [inferTemplatePath, targetPath],
399
+ options: compilerOptions,
400
+ host: host.compilerHost,
401
+ });
402
+
403
+ const extractor = new SubgraphExtractor(
404
+ research,
405
+ research.getSourceFile(targetPath)!,
406
+ research.getSourceFile(inferTemplatePath)!,
407
+ options
408
+ );
409
+
410
+ const { files, exports } = extractor.getAugmentedSourceFile(name);
411
+ for (const [name, source] of files) {
412
+ system.writeFile(path.resolve(dirname, name), source);
413
+ }
414
+
415
+ const extract = ts.createProgram({
416
+ rootNames: [path.resolve(dirname, "./__langraph__infer.mts")],
417
+ options: compilerOptions,
418
+ host: host.compilerHost,
419
+ });
420
+
421
+ const schemaGenerator = buildGenerator(extract);
422
+ const trySymbol = (schema: typeof schemaGenerator, symbol: string) => {
423
+ try {
424
+ return schema?.getSchemaForSymbol(symbol) ?? undefined;
425
+ } catch (e) {
426
+ console.warn(`Failed to obtain symbol "${symbol}":`, e?.message);
427
+ }
428
+ return undefined;
429
+ };
430
+
431
+ return Object.fromEntries(
432
+ exports.map(({ typeName, graphName }) => [
433
+ graphName,
434
+ {
435
+ input: trySymbol(schemaGenerator, `${typeName}__input`),
436
+ output: trySymbol(schemaGenerator, `${typeName}__output`),
437
+ state: trySymbol(schemaGenerator, `${typeName}__update`),
438
+ config: trySymbol(schemaGenerator, `${typeName}__config`),
439
+ },
440
+ ])
441
+ );
442
+ }
443
+ }
@@ -0,0 +1,12 @@
1
+ import { tsImport } from "tsx/esm/api";
2
+ import { parentPort } from "node:worker_threads";
3
+
4
+ parentPort?.on("message", async (payload) => {
5
+ const { SubgraphExtractor } = await tsImport("./parser.mts", import.meta.url);
6
+ const result = SubgraphExtractor.extractSchemas(
7
+ payload.sourceFile,
8
+ payload.exportSymbol,
9
+ { strict: false }
10
+ );
11
+ parentPort?.postMessage(result);
12
+ });