@langchain/langgraph-api 1.1.8 → 1.1.10
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/README.md +3 -3
- package/dist/api/assistants.d.mts +3 -0
- package/dist/api/assistants.mjs +193 -0
- package/dist/api/meta.d.mts +3 -0
- package/dist/api/meta.mjs +65 -0
- package/dist/api/runs.d.mts +3 -0
- package/dist/api/runs.mjs +324 -0
- package/dist/api/store.d.mts +3 -0
- package/dist/api/store.mjs +111 -0
- package/dist/api/threads.d.mts +3 -0
- package/dist/api/threads.mjs +143 -0
- package/dist/auth/custom.d.mts +9 -0
- package/dist/auth/custom.mjs +32 -0
- package/dist/auth/index.d.mts +43 -0
- package/dist/auth/index.mjs +163 -0
- package/dist/cli/entrypoint.d.mts +1 -0
- package/dist/cli/entrypoint.mjs +41 -0
- package/dist/cli/spawn.d.mts +42 -0
- package/dist/cli/spawn.mjs +47 -0
- package/dist/cli/utils/ipc/client.d.mts +5 -0
- package/dist/cli/utils/ipc/client.mjs +47 -0
- package/dist/cli/utils/ipc/utils/get-pipe-path.d.mts +1 -0
- package/dist/cli/utils/ipc/utils/get-pipe-path.mjs +29 -0
- package/dist/cli/utils/ipc/utils/temporary-directory.d.mts +5 -0
- package/dist/cli/utils/ipc/utils/temporary-directory.mjs +40 -0
- package/dist/command.d.mts +11 -0
- package/dist/command.mjs +15 -0
- package/dist/experimental/embed.d.mts +42 -0
- package/dist/experimental/embed.mjs +299 -0
- package/dist/graph/api.d.mts +1 -0
- package/dist/graph/api.mjs +2 -0
- package/dist/graph/load.d.mts +19 -0
- package/dist/graph/load.hooks.d.mts +2 -0
- package/dist/graph/load.hooks.mjs +52 -0
- package/dist/graph/load.mjs +96 -0
- package/dist/graph/load.utils.d.mts +22 -0
- package/dist/graph/load.utils.mjs +49 -0
- package/dist/graph/parser/index.d.mts +23 -0
- package/dist/graph/parser/index.mjs +58 -0
- package/dist/graph/parser/parser.d.mts +77 -0
- package/dist/graph/parser/parser.mjs +429 -0
- package/dist/graph/parser/parser.worker.d.mts +1 -0
- package/dist/graph/parser/parser.worker.mjs +7 -0
- package/dist/graph/parser/schema/types.d.mts +154 -0
- package/dist/graph/parser/schema/types.mjs +1496 -0
- package/dist/graph/parser/schema/types.template.d.mts +1 -0
- package/dist/graph/parser/schema/types.template.mts +92 -0
- package/dist/http/custom.d.mts +6 -0
- package/dist/http/custom.mjs +10 -0
- package/dist/http/middleware.d.mts +11 -0
- package/dist/http/middleware.mjs +57 -0
- package/dist/logging.d.mts +10 -0
- package/dist/logging.mjs +115 -0
- package/dist/loopback.d.mts +4 -0
- package/dist/loopback.mjs +10 -0
- package/dist/preload.d.mts +1 -0
- package/dist/preload.mjs +29 -0
- package/dist/queue.d.mts +2 -0
- package/dist/queue.mjs +119 -0
- package/dist/schemas.d.mts +1552 -0
- package/dist/schemas.mjs +492 -0
- package/dist/semver/index.d.mts +15 -0
- package/dist/semver/index.mjs +46 -0
- package/dist/server.d.mts +175 -0
- package/dist/server.mjs +181 -0
- package/dist/state.d.mts +3 -0
- package/dist/state.mjs +30 -0
- package/dist/storage/checkpoint.d.mts +19 -0
- package/dist/storage/checkpoint.mjs +127 -0
- package/dist/storage/context.d.mts +3 -0
- package/dist/storage/context.mjs +11 -0
- package/dist/storage/importMap.d.mts +55 -0
- package/dist/storage/importMap.mjs +55 -0
- package/dist/storage/ops.d.mts +169 -0
- package/dist/storage/ops.mjs +1262 -0
- package/dist/storage/persist.d.mts +18 -0
- package/dist/storage/persist.mjs +81 -0
- package/dist/storage/store.d.mts +17 -0
- package/dist/storage/store.mjs +41 -0
- package/dist/storage/types.d.mts +301 -0
- package/dist/storage/types.mjs +1 -0
- package/dist/stream.d.mts +43 -0
- package/dist/stream.mjs +235 -0
- package/dist/ui/load.d.mts +8 -0
- package/dist/ui/load.mjs +53 -0
- package/dist/utils/abort.d.mts +1 -0
- package/dist/utils/abort.mjs +8 -0
- package/dist/utils/hono.d.mts +5 -0
- package/dist/utils/hono.mjs +24 -0
- package/dist/utils/importMap.d.mts +55 -0
- package/dist/utils/importMap.mjs +55 -0
- package/dist/utils/runnableConfig.d.mts +3 -0
- package/dist/utils/runnableConfig.mjs +45 -0
- package/dist/utils/serde.d.mts +5 -0
- package/dist/utils/serde.mjs +20 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +11 -0
- package/dist/webhook.d.mts +11 -0
- package/dist/webhook.mjs +30 -0
- package/package.json +19 -19
|
@@ -0,0 +1,429 @@
|
|
|
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.mjs";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
8
|
+
const OVERRIDE_RESOLVE = [
|
|
9
|
+
// Override `@langchain/langgraph` or `@langchain/langgraph/prebuilt`,
|
|
10
|
+
// but not `@langchain/langgraph-sdk`
|
|
11
|
+
new RegExp(`^@langchain\/langgraph(\/.+)?$`),
|
|
12
|
+
new RegExp(`^@langchain\/langgraph-checkpoint(\/.+)?$`),
|
|
13
|
+
];
|
|
14
|
+
const INFER_TEMPLATE_PATH = path.resolve(__dirname, "./schema/types.template.mts");
|
|
15
|
+
export class SubgraphExtractor {
|
|
16
|
+
program;
|
|
17
|
+
checker;
|
|
18
|
+
sourceFile;
|
|
19
|
+
inferFile;
|
|
20
|
+
anyPregelType;
|
|
21
|
+
anyGraphType;
|
|
22
|
+
strict;
|
|
23
|
+
constructor(program, sourceFile, inferFile, options) {
|
|
24
|
+
this.program = program;
|
|
25
|
+
this.sourceFile = sourceFile;
|
|
26
|
+
this.inferFile = inferFile;
|
|
27
|
+
this.checker = program.getTypeChecker();
|
|
28
|
+
this.strict = options?.strict ?? false;
|
|
29
|
+
this.anyPregelType = this.findTypeByName("AnyPregel");
|
|
30
|
+
this.anyGraphType = this.findTypeByName("AnyGraph");
|
|
31
|
+
}
|
|
32
|
+
findTypeByName = (needle) => {
|
|
33
|
+
let result = undefined;
|
|
34
|
+
const visit = (node) => {
|
|
35
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
36
|
+
const symbol = node.symbol;
|
|
37
|
+
if (symbol != null) {
|
|
38
|
+
const name = this.checker
|
|
39
|
+
.getFullyQualifiedName(symbol)
|
|
40
|
+
.replace(/".*"\./, "");
|
|
41
|
+
if (name === needle)
|
|
42
|
+
result = this.checker.getTypeAtLocation(node);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (result == null)
|
|
46
|
+
ts.forEachChild(node, visit);
|
|
47
|
+
};
|
|
48
|
+
ts.forEachChild(this.inferFile, visit);
|
|
49
|
+
if (!result)
|
|
50
|
+
throw new Error(`Failed to find "${needle}" type`);
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
find = (root, predicate) => {
|
|
54
|
+
let result = undefined;
|
|
55
|
+
const visit = (node) => {
|
|
56
|
+
if (predicate(node)) {
|
|
57
|
+
result = node;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
ts.forEachChild(node, visit);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
if (predicate(root))
|
|
64
|
+
return root;
|
|
65
|
+
ts.forEachChild(root, visit);
|
|
66
|
+
return result;
|
|
67
|
+
};
|
|
68
|
+
findSubgraphs = (node, namespace = []) => {
|
|
69
|
+
const findAllAddNodeCalls = (acc, node) => {
|
|
70
|
+
if (ts.isCallExpression(node)) {
|
|
71
|
+
const firstChild = node.getChildAt(0);
|
|
72
|
+
if (ts.isPropertyAccessExpression(firstChild) &&
|
|
73
|
+
this.getText(firstChild.name) === "addNode") {
|
|
74
|
+
let nodeName = "unknown";
|
|
75
|
+
let variables = [];
|
|
76
|
+
const [subgraphNode, callArg] = node.arguments;
|
|
77
|
+
if (subgraphNode && ts.isStringLiteralLike(subgraphNode)) {
|
|
78
|
+
nodeName = this.getText(subgraphNode);
|
|
79
|
+
if ((nodeName.startsWith(`"`) && nodeName.endsWith(`"`)) ||
|
|
80
|
+
(nodeName.startsWith(`'`) && nodeName.endsWith(`'`))) {
|
|
81
|
+
nodeName = nodeName.slice(1, -1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (callArg) {
|
|
85
|
+
if (ts.isFunctionLike(callArg) ||
|
|
86
|
+
ts.isCallLikeExpression(callArg)) {
|
|
87
|
+
variables = this.reduceChildren(callArg, this.findSubgraphIdentifiers, []);
|
|
88
|
+
}
|
|
89
|
+
else if (ts.isIdentifier(callArg)) {
|
|
90
|
+
variables = this.findSubgraphIdentifiers([], callArg);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (variables.length > 0) {
|
|
94
|
+
if (variables.length > 1) {
|
|
95
|
+
const targetName = [...namespace, nodeName].join("|");
|
|
96
|
+
const errMsg = `Multiple unique subgraph invocations found for "${targetName}"`;
|
|
97
|
+
if (this.strict)
|
|
98
|
+
throw new Error(errMsg);
|
|
99
|
+
console.warn(errMsg);
|
|
100
|
+
}
|
|
101
|
+
acc.push({
|
|
102
|
+
namespace: namespace,
|
|
103
|
+
node: nodeName,
|
|
104
|
+
subgraph: variables[0],
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return acc;
|
|
110
|
+
};
|
|
111
|
+
let subgraphs = this.reduceChildren(node, findAllAddNodeCalls, []);
|
|
112
|
+
// TODO: make this more strict, only traverse the flow graph only
|
|
113
|
+
// if no `addNode` calls were found
|
|
114
|
+
if (!subgraphs.length) {
|
|
115
|
+
const candidate = this.find(node, (node) => node && "flowNode" in node && node.flowNode);
|
|
116
|
+
if (candidate?.flowNode &&
|
|
117
|
+
this.isGraphOrPregelType(this.checker.getTypeAtLocation(candidate.flowNode.node))) {
|
|
118
|
+
subgraphs = this.findSubgraphs(candidate.flowNode.node, namespace);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// handle recursive behaviour
|
|
122
|
+
if (subgraphs.length > 0) {
|
|
123
|
+
return [
|
|
124
|
+
...subgraphs,
|
|
125
|
+
...subgraphs.map(({ subgraph, node }) => this.findSubgraphs(subgraph.node, [...namespace, node])),
|
|
126
|
+
].flat();
|
|
127
|
+
}
|
|
128
|
+
return subgraphs;
|
|
129
|
+
};
|
|
130
|
+
getSubgraphsVariables = (name) => {
|
|
131
|
+
const sourceSymbol = this.checker.getSymbolAtLocation(this.sourceFile);
|
|
132
|
+
const exports = this.checker.getExportsOfModule(sourceSymbol);
|
|
133
|
+
const targetExport = exports.find((item) => item.name === name);
|
|
134
|
+
if (!targetExport)
|
|
135
|
+
throw new Error(`Failed to find export "${name}"`);
|
|
136
|
+
const varDecls = (targetExport.declarations ?? []).filter(ts.isVariableDeclaration);
|
|
137
|
+
return varDecls.flatMap((varDecl) => {
|
|
138
|
+
if (!varDecl.initializer)
|
|
139
|
+
return [];
|
|
140
|
+
return this.findSubgraphs(varDecl.initializer, [name]);
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
getAugmentedSourceFile = (sourcePath, name, options) => {
|
|
144
|
+
function sanitize(input) {
|
|
145
|
+
return input.replace(/[^a-zA-Z0-9]/g, "_");
|
|
146
|
+
}
|
|
147
|
+
const vars = this.getSubgraphsVariables(name);
|
|
148
|
+
const ext = path.extname(sourcePath);
|
|
149
|
+
const suffix = sourcePath.slice(0, -ext.length);
|
|
150
|
+
let typeExports = [
|
|
151
|
+
{
|
|
152
|
+
typeName: sanitize(`__langgraph__${name}_${suffix}`),
|
|
153
|
+
valueName: name,
|
|
154
|
+
graphName: name,
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
const seenTypeName = new Set();
|
|
158
|
+
for (const { subgraph, node, namespace } of vars) {
|
|
159
|
+
if (seenTypeName.has(subgraph.name))
|
|
160
|
+
continue;
|
|
161
|
+
seenTypeName.add(subgraph.name);
|
|
162
|
+
typeExports.push({
|
|
163
|
+
typeName: sanitize(`__langgraph__${namespace.join("_")}_${node}_${suffix}`),
|
|
164
|
+
valueName: subgraph.name,
|
|
165
|
+
graphName: [...namespace, node].join("|"),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
typeExports = typeExports.map(({ typeName, ...rest }) => ({
|
|
169
|
+
...rest,
|
|
170
|
+
typeName: sanitize(typeName),
|
|
171
|
+
}));
|
|
172
|
+
const sourceFilePath = `__langgraph__source_${sanitize(suffix)}${ext}`;
|
|
173
|
+
const sourceContents = [
|
|
174
|
+
this.getText(this.sourceFile),
|
|
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");
|
|
181
|
+
const inferContents = [
|
|
182
|
+
typeExports.map((type) => `import type { ${type.typeName} } from "./${sourceFileImportPath}"`),
|
|
183
|
+
this.inferFile.getText(this.inferFile),
|
|
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"]>;
|
|
188
|
+
|
|
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
|
+
];
|
|
195
|
+
return {
|
|
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
|
+
},
|
|
204
|
+
exports: typeExports,
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
findSubgraphIdentifiers = (acc, node) => {
|
|
208
|
+
if (ts.isIdentifier(node)) {
|
|
209
|
+
const smb = this.checker.getSymbolAtLocation(node);
|
|
210
|
+
if (smb?.valueDeclaration &&
|
|
211
|
+
ts.isVariableDeclaration(smb.valueDeclaration)) {
|
|
212
|
+
const target = smb.valueDeclaration;
|
|
213
|
+
const targetType = this.checker.getTypeAtLocation(target);
|
|
214
|
+
if (this.isGraphOrPregelType(targetType)) {
|
|
215
|
+
acc.push({ name: this.getText(target.name), node: target });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (smb?.declarations) {
|
|
219
|
+
const target = smb.declarations.find(ts.isImportSpecifier);
|
|
220
|
+
if (target) {
|
|
221
|
+
const targetType = this.checker.getTypeAtLocation(target);
|
|
222
|
+
if (this.isGraphOrPregelType(targetType)) {
|
|
223
|
+
acc.push({ name: this.getText(target.name), node: target });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return acc;
|
|
229
|
+
};
|
|
230
|
+
isGraphOrPregelType = (type) => {
|
|
231
|
+
return (this.checker.isTypeAssignableTo(type, this.anyPregelType) ||
|
|
232
|
+
this.checker.isTypeAssignableTo(type, this.anyGraphType));
|
|
233
|
+
};
|
|
234
|
+
getText(node) {
|
|
235
|
+
return node.getText(this.sourceFile);
|
|
236
|
+
}
|
|
237
|
+
reduceChildren(node, fn, initial) {
|
|
238
|
+
let acc = initial;
|
|
239
|
+
function it(node) {
|
|
240
|
+
acc = fn(acc, node);
|
|
241
|
+
// @ts-expect-error
|
|
242
|
+
ts.forEachChild(node, it.bind(this));
|
|
243
|
+
}
|
|
244
|
+
ts.forEachChild(node, it.bind(this));
|
|
245
|
+
return acc;
|
|
246
|
+
}
|
|
247
|
+
static extractSchemas(target, options) {
|
|
248
|
+
if (!target.length)
|
|
249
|
+
throw new Error("No graphs found");
|
|
250
|
+
function getCommonPath(a, b) {
|
|
251
|
+
const aSeg = path.normalize(a).split(path.sep);
|
|
252
|
+
const bSeg = path.normalize(b).split(path.sep);
|
|
253
|
+
const maxIter = Math.min(aSeg.length, bSeg.length);
|
|
254
|
+
const result = [];
|
|
255
|
+
for (let i = 0; i < maxIter; ++i) {
|
|
256
|
+
if (aSeg[i] !== bSeg[i])
|
|
257
|
+
break;
|
|
258
|
+
result.push(aSeg[i]);
|
|
259
|
+
}
|
|
260
|
+
return result.join(path.sep);
|
|
261
|
+
}
|
|
262
|
+
const isTestTarget = (check) => {
|
|
263
|
+
return check.every((x) => typeof x.sourceFile === "string");
|
|
264
|
+
};
|
|
265
|
+
const projectDirname = isTestTarget(target)
|
|
266
|
+
? target.reduce((acc, item) => {
|
|
267
|
+
if (!acc)
|
|
268
|
+
return path.dirname(item.sourceFile);
|
|
269
|
+
return getCommonPath(acc, path.dirname(item.sourceFile));
|
|
270
|
+
}, "")
|
|
271
|
+
: __dirname;
|
|
272
|
+
// This API is not well made for Windows, ensure that the paths are UNIX slashes
|
|
273
|
+
const fsMap = new Map();
|
|
274
|
+
const system = vfs.createFSBackedSystem(fsMap, projectDirname, ts);
|
|
275
|
+
// TODO: investigate if we should create a PR in @typescript/vfs
|
|
276
|
+
const oldReadFile = system.readFile.bind(system);
|
|
277
|
+
system.readFile = (fileName) => oldReadFile(fileName) ?? "// Non-existent file";
|
|
278
|
+
const vfsPath = (inputPath) => {
|
|
279
|
+
if (process.platform === "win32")
|
|
280
|
+
return inputPath.replace(/\\/g, "/");
|
|
281
|
+
return inputPath;
|
|
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 = {
|
|
295
|
+
...parsedTsconfig.options,
|
|
296
|
+
...compilerOptions,
|
|
297
|
+
...(options?.tsConfigOptions ?? {}),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const vfsHost = vfs.createVirtualCompilerHost(system, compilerOptions, ts);
|
|
301
|
+
const host = vfsHost.compilerHost;
|
|
302
|
+
const targetPaths = [];
|
|
303
|
+
for (const item of target) {
|
|
304
|
+
if (typeof item.sourceFile === "string") {
|
|
305
|
+
targetPaths.push({ ...item, sourceFile: item.sourceFile });
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
for (const { path: sourcePath, contents, main } of item.sourceFile ??
|
|
309
|
+
[]) {
|
|
310
|
+
fsMap.set(vfsPath(path.resolve(projectDirname, sourcePath)), contents);
|
|
311
|
+
if (main) {
|
|
312
|
+
targetPaths.push({
|
|
313
|
+
...item,
|
|
314
|
+
sourceFile: path.resolve(projectDirname, sourcePath),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const moduleCache = ts.createModuleResolutionCache(projectDirname, (x) => x);
|
|
321
|
+
host.resolveModuleNameLiterals = (entries, containingFile, redirectedReference, options) => entries.flatMap((entry) => {
|
|
322
|
+
const specifier = entry.text;
|
|
323
|
+
// Force module resolution to use @langchain/langgraph from the local project
|
|
324
|
+
// rather than from API/CLI.
|
|
325
|
+
let targetFile = containingFile;
|
|
326
|
+
if (OVERRIDE_RESOLVE.some((regex) => regex.test(specifier))) {
|
|
327
|
+
// check if we're not already importing from node_modules
|
|
328
|
+
if (!containingFile.split(path.sep).includes("node_modules")) {
|
|
329
|
+
// Doesn't matter if the file exists, only used to nudge `ts.resolveModuleName`
|
|
330
|
+
targetFile = path.resolve(projectDirname, "__langgraph__resolve.mts");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return [
|
|
334
|
+
ts.resolveModuleName(specifier, targetFile, options, host, moduleCache, redirectedReference),
|
|
335
|
+
];
|
|
336
|
+
});
|
|
337
|
+
const research = ts.createProgram({
|
|
338
|
+
rootNames: [INFER_TEMPLATE_PATH, ...targetPaths.map((i) => i.sourceFile)],
|
|
339
|
+
options: compilerOptions,
|
|
340
|
+
host,
|
|
341
|
+
});
|
|
342
|
+
const researchTargets = [];
|
|
343
|
+
for (const targetPath of targetPaths) {
|
|
344
|
+
const extractor = new SubgraphExtractor(research, research.getSourceFile(targetPath.sourceFile), research.getSourceFile(INFER_TEMPLATE_PATH), options);
|
|
345
|
+
const graphDirname = path.dirname(targetPath.sourceFile);
|
|
346
|
+
const { sourceFile, inferFile, exports } = extractor.getAugmentedSourceFile(path.relative(projectDirname, targetPath.sourceFile), targetPath.exportSymbol, {
|
|
347
|
+
allowImportingTsExtensions: compilerOptions.allowImportingTsExtensions ?? false,
|
|
348
|
+
});
|
|
349
|
+
for (const { fileName, contents } of [sourceFile, inferFile]) {
|
|
350
|
+
system.writeFile(vfsPath(path.resolve(graphDirname, fileName)), contents);
|
|
351
|
+
}
|
|
352
|
+
researchTargets.push({
|
|
353
|
+
rootName: path.resolve(graphDirname, inferFile.fileName),
|
|
354
|
+
exports,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
const extract = ts.createProgram({
|
|
358
|
+
rootNames: researchTargets.map((i) => i.rootName),
|
|
359
|
+
options: compilerOptions,
|
|
360
|
+
host,
|
|
361
|
+
});
|
|
362
|
+
// Print out any diagnostics file that were detected before emitting
|
|
363
|
+
// This may explain why sometimes the schema is invalid.
|
|
364
|
+
const allDiagnostics = ts.getPreEmitDiagnostics(extract);
|
|
365
|
+
for (const diagnostic of allDiagnostics) {
|
|
366
|
+
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n") + "\n";
|
|
367
|
+
if (diagnostic.file) {
|
|
368
|
+
const fileName = diagnostic.file.fileName;
|
|
369
|
+
const { line, character } = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
|
|
370
|
+
const fileLoc = `(${line + 1},${character + 1})`;
|
|
371
|
+
message = `${fileName} ${fileLoc}: ${message}`;
|
|
372
|
+
}
|
|
373
|
+
console.log(message);
|
|
374
|
+
}
|
|
375
|
+
const schemaGenerator = buildGenerator(extract);
|
|
376
|
+
const trySymbol = (symbol) => {
|
|
377
|
+
let schema = undefined;
|
|
378
|
+
try {
|
|
379
|
+
schema = schemaGenerator?.getSchemaForSymbol(symbol) ?? undefined;
|
|
380
|
+
}
|
|
381
|
+
catch (e) {
|
|
382
|
+
console.warn(`Failed to obtain symbol "${symbol}":`, e?.message);
|
|
383
|
+
}
|
|
384
|
+
if (schema == null)
|
|
385
|
+
return undefined;
|
|
386
|
+
const definitions = schema.definitions;
|
|
387
|
+
if (definitions == null)
|
|
388
|
+
return schema;
|
|
389
|
+
const toReplace = Object.keys(definitions).flatMap((key) => {
|
|
390
|
+
const replacedKey = key.includes("import(")
|
|
391
|
+
? key.replace(/import\(.+@langchain[\\/]core.+\)\./, "")
|
|
392
|
+
: key;
|
|
393
|
+
if (key !== replacedKey && definitions[replacedKey] == null) {
|
|
394
|
+
return [
|
|
395
|
+
{
|
|
396
|
+
source: key,
|
|
397
|
+
target: replacedKey,
|
|
398
|
+
sourceRef: `#/definitions/${key}`,
|
|
399
|
+
targetRef: `#/definitions/${replacedKey}`,
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
}
|
|
403
|
+
return [];
|
|
404
|
+
});
|
|
405
|
+
for (const { source, target } of toReplace) {
|
|
406
|
+
definitions[target] = definitions[source];
|
|
407
|
+
delete definitions[source];
|
|
408
|
+
}
|
|
409
|
+
const refMap = toReplace.reduce((acc, item) => {
|
|
410
|
+
acc[item.sourceRef] = item.targetRef;
|
|
411
|
+
return acc;
|
|
412
|
+
}, {});
|
|
413
|
+
return JSON.parse(JSON.stringify(schema, (_, value) => {
|
|
414
|
+
if (typeof value === "string" && refMap[value])
|
|
415
|
+
return refMap[value];
|
|
416
|
+
return value;
|
|
417
|
+
}));
|
|
418
|
+
};
|
|
419
|
+
return researchTargets.map(({ exports }) => Object.fromEntries(exports.map(({ typeName, graphName }) => [
|
|
420
|
+
graphName,
|
|
421
|
+
{
|
|
422
|
+
state: trySymbol(`${typeName}__update`),
|
|
423
|
+
input: trySymbol(`${typeName}__input`),
|
|
424
|
+
output: trySymbol(`${typeName}__output`),
|
|
425
|
+
config: trySymbol(`${typeName}__config`),
|
|
426
|
+
},
|
|
427
|
+
])));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { tsImport } from "tsx/esm/api";
|
|
2
|
+
import { parentPort } from "node:worker_threads";
|
|
3
|
+
parentPort?.on("message", async (payload) => {
|
|
4
|
+
const { SubgraphExtractor } = await tsImport("./parser.mjs", import.meta.url);
|
|
5
|
+
const result = SubgraphExtractor.extractSchemas(payload, { strict: false });
|
|
6
|
+
parentPort?.postMessage(result);
|
|
7
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { JSONSchema7 } from "json-schema";
|
|
3
|
+
type Args = {
|
|
4
|
+
ref: boolean;
|
|
5
|
+
aliasRef: boolean;
|
|
6
|
+
topRef: boolean;
|
|
7
|
+
titles: boolean;
|
|
8
|
+
defaultProps: boolean;
|
|
9
|
+
noExtraProps: boolean;
|
|
10
|
+
propOrder: boolean;
|
|
11
|
+
typeOfKeyword: boolean;
|
|
12
|
+
required: boolean;
|
|
13
|
+
strictNullChecks: boolean;
|
|
14
|
+
esModuleInterop: boolean;
|
|
15
|
+
experimentalDecorators: boolean;
|
|
16
|
+
out: string;
|
|
17
|
+
validationKeywords: string[];
|
|
18
|
+
include: string[];
|
|
19
|
+
excludePrivate: boolean;
|
|
20
|
+
uniqueNames: boolean;
|
|
21
|
+
rejectDateType: boolean;
|
|
22
|
+
id: string;
|
|
23
|
+
defaultNumberType: "number" | "integer";
|
|
24
|
+
constAsEnum: boolean;
|
|
25
|
+
};
|
|
26
|
+
type PartialArgs = Partial<Args>;
|
|
27
|
+
type RedefinedFields = "items" | "additionalItems" | "contains" | "properties" | "patternProperties" | "additionalProperties" | "dependencies" | "propertyNames" | "if" | "then" | "else" | "allOf" | "anyOf" | "oneOf" | "not" | "definitions";
|
|
28
|
+
type DefinitionOrBoolean = Definition | boolean;
|
|
29
|
+
interface Definition extends Omit<JSONSchema7, RedefinedFields> {
|
|
30
|
+
propertyOrder?: string[];
|
|
31
|
+
defaultProperties?: string[];
|
|
32
|
+
typeof?: "function";
|
|
33
|
+
items?: DefinitionOrBoolean | DefinitionOrBoolean[];
|
|
34
|
+
additionalItems?: DefinitionOrBoolean;
|
|
35
|
+
contains?: JSONSchema7;
|
|
36
|
+
properties?: {
|
|
37
|
+
[key: string]: DefinitionOrBoolean;
|
|
38
|
+
};
|
|
39
|
+
patternProperties?: {
|
|
40
|
+
[key: string]: DefinitionOrBoolean;
|
|
41
|
+
};
|
|
42
|
+
additionalProperties?: DefinitionOrBoolean;
|
|
43
|
+
dependencies?: {
|
|
44
|
+
[key: string]: DefinitionOrBoolean | string[];
|
|
45
|
+
};
|
|
46
|
+
propertyNames?: DefinitionOrBoolean;
|
|
47
|
+
if?: DefinitionOrBoolean;
|
|
48
|
+
then?: DefinitionOrBoolean;
|
|
49
|
+
else?: DefinitionOrBoolean;
|
|
50
|
+
allOf?: DefinitionOrBoolean[];
|
|
51
|
+
anyOf?: DefinitionOrBoolean[];
|
|
52
|
+
oneOf?: DefinitionOrBoolean[];
|
|
53
|
+
not?: DefinitionOrBoolean;
|
|
54
|
+
definitions?: {
|
|
55
|
+
[key: string]: DefinitionOrBoolean;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
type SymbolRef = {
|
|
59
|
+
name: string;
|
|
60
|
+
typeName: string;
|
|
61
|
+
fullyQualifiedName: string;
|
|
62
|
+
symbol: ts.Symbol;
|
|
63
|
+
};
|
|
64
|
+
declare class JsonSchemaGenerator {
|
|
65
|
+
private args;
|
|
66
|
+
private tc;
|
|
67
|
+
/**
|
|
68
|
+
* Holds all symbols within a custom SymbolRef object, containing useful
|
|
69
|
+
* information.
|
|
70
|
+
*/
|
|
71
|
+
private symbols;
|
|
72
|
+
/**
|
|
73
|
+
* All types for declarations of classes, interfaces, enums, and type aliases
|
|
74
|
+
* defined in all TS files.
|
|
75
|
+
*/
|
|
76
|
+
private allSymbols;
|
|
77
|
+
/**
|
|
78
|
+
* All symbols for declarations of classes, interfaces, enums, and type aliases
|
|
79
|
+
* defined in non-default-lib TS files.
|
|
80
|
+
*/
|
|
81
|
+
private userSymbols;
|
|
82
|
+
/**
|
|
83
|
+
* Maps from the names of base types to the names of the types that inherit from
|
|
84
|
+
* them.
|
|
85
|
+
*/
|
|
86
|
+
private inheritingTypes;
|
|
87
|
+
/**
|
|
88
|
+
* This map holds references to all reffed definitions, including schema
|
|
89
|
+
* overrides and generated definitions.
|
|
90
|
+
*/
|
|
91
|
+
private reffedDefinitions;
|
|
92
|
+
/**
|
|
93
|
+
* This map only holds explicit schema overrides. This helps differentiate between
|
|
94
|
+
* user defined schema overrides and generated definitions.
|
|
95
|
+
*/
|
|
96
|
+
private schemaOverrides;
|
|
97
|
+
/**
|
|
98
|
+
* This is a set of all the user-defined validation keywords.
|
|
99
|
+
*/
|
|
100
|
+
private userValidationKeywords;
|
|
101
|
+
/**
|
|
102
|
+
* If true, this makes constants be defined as enums with a single value. This is useful
|
|
103
|
+
* for cases where constant values are not supported, such as OpenAPI.
|
|
104
|
+
*/
|
|
105
|
+
private constAsEnum;
|
|
106
|
+
/**
|
|
107
|
+
* Types are assigned names which are looked up by their IDs. This is the
|
|
108
|
+
* map from type IDs to type names.
|
|
109
|
+
*/
|
|
110
|
+
private typeNamesById;
|
|
111
|
+
/**
|
|
112
|
+
* Whenever a type is assigned its name, its entry in this dictionary is set,
|
|
113
|
+
* so that we don't give the same name to two separate types.
|
|
114
|
+
*/
|
|
115
|
+
private typeIdsByName;
|
|
116
|
+
constructor(symbols: SymbolRef[], allSymbols: {
|
|
117
|
+
[name: string]: ts.Type;
|
|
118
|
+
}, userSymbols: {
|
|
119
|
+
[name: string]: ts.Symbol;
|
|
120
|
+
}, inheritingTypes: {
|
|
121
|
+
[baseName: string]: string[];
|
|
122
|
+
}, tc: ts.TypeChecker, args?: Args);
|
|
123
|
+
get ReffedDefinitions(): {
|
|
124
|
+
[key: string]: Definition;
|
|
125
|
+
};
|
|
126
|
+
private isFromDefaultLib;
|
|
127
|
+
private resetSchemaSpecificProperties;
|
|
128
|
+
/**
|
|
129
|
+
* Parse the comments of a symbol into the definition and other annotations.
|
|
130
|
+
*/
|
|
131
|
+
private parseCommentsIntoDefinition;
|
|
132
|
+
private getDefinitionForRootType;
|
|
133
|
+
private getReferencedTypeSymbol;
|
|
134
|
+
private getDefinitionForProperty;
|
|
135
|
+
private getEnumDefinition;
|
|
136
|
+
private getUnionDefinition;
|
|
137
|
+
private getIntersectionDefinition;
|
|
138
|
+
private getClassDefinition;
|
|
139
|
+
/**
|
|
140
|
+
* Gets/generates a globally unique type name for the given type
|
|
141
|
+
*/
|
|
142
|
+
private getTypeName;
|
|
143
|
+
private makeTypeNameUnique;
|
|
144
|
+
private recursiveTypeRef;
|
|
145
|
+
private getTypeDefinition;
|
|
146
|
+
setSchemaOverride(symbolName: string, schema: Definition): void;
|
|
147
|
+
getSchemaForSymbol(symbolName: string, includeReffedDefinitions?: boolean, includeAllOverrides?: boolean): Definition;
|
|
148
|
+
getSchemaForSymbols(symbolNames: string[], includeReffedDefinitions?: boolean, includeAllOverrides?: boolean): Definition;
|
|
149
|
+
getSymbols(name?: string): SymbolRef[];
|
|
150
|
+
getUserSymbols(): string[];
|
|
151
|
+
getMainFileSymbols(program: ts.Program, onlyIncludeFiles?: string[]): string[];
|
|
152
|
+
}
|
|
153
|
+
export declare function buildGenerator(program: ts.Program, args?: PartialArgs): JsonSchemaGenerator | null;
|
|
154
|
+
export {};
|