@langchain/langgraph-api 0.0.34 → 0.0.36
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/dist/api/runs.mjs +36 -5
- package/dist/graph/parser/index.d.mts +7 -1
- package/dist/graph/parser/index.mjs +36 -19
- package/dist/graph/parser/parser.d.mts +18 -7
- package/dist/graph/parser/parser.mjs +65 -29
- package/dist/graph/parser/parser.worker.mjs +1 -1
- package/dist/http/middleware.mjs +11 -2
- package/dist/queue.mjs +9 -2
- package/dist/schemas.d.mts +6 -0
- package/dist/schemas.mjs +1 -0
- package/dist/server.d.mts +6 -6
- package/dist/storage/ops.d.mts +18 -6
- package/dist/storage/ops.mjs +61 -21
- package/package.json +3 -3
package/dist/api/runs.mjs
CHANGED
|
@@ -75,6 +75,7 @@ const createValidRun = async (threadId, payload, kwargs) => {
|
|
|
75
75
|
feedback_keys: feedbackKeys,
|
|
76
76
|
temporary: threadId == null && (run.on_completion ?? "delete") === "delete",
|
|
77
77
|
subgraphs: run.stream_subgraphs ?? false,
|
|
78
|
+
resumable: run.stream_resumable ?? false,
|
|
78
79
|
}, {
|
|
79
80
|
threadId,
|
|
80
81
|
userId,
|
|
@@ -139,7 +140,11 @@ api.post("/runs/stream", zValidator("json", schemas.RunCreate), async (c) => {
|
|
|
139
140
|
? getDisconnectAbortSignal(c, stream)
|
|
140
141
|
: undefined;
|
|
141
142
|
try {
|
|
142
|
-
for await (const { event, data } of Runs.Stream.join(run.run_id, undefined, {
|
|
143
|
+
for await (const { event, data } of Runs.Stream.join(run.run_id, undefined, {
|
|
144
|
+
cancelOnDisconnect,
|
|
145
|
+
lastEventId: run.kwargs.resumable ? "-1" : undefined,
|
|
146
|
+
ignore404: true,
|
|
147
|
+
}, c.var.auth)) {
|
|
143
148
|
await stream.writeSSE({ data: serialiseAsDict(data), event });
|
|
144
149
|
}
|
|
145
150
|
}
|
|
@@ -148,6 +153,25 @@ api.post("/runs/stream", zValidator("json", schemas.RunCreate), async (c) => {
|
|
|
148
153
|
}
|
|
149
154
|
});
|
|
150
155
|
});
|
|
156
|
+
// TODO: port to Python API
|
|
157
|
+
api.get("/runs/:run_id/stream", zValidator("param", z.object({ run_id: z.string().uuid() })), zValidator("query", z.object({ cancel_on_disconnect: schemas.coercedBoolean.optional() })), async (c) => {
|
|
158
|
+
const { run_id } = c.req.valid("param");
|
|
159
|
+
const query = c.req.valid("query");
|
|
160
|
+
const lastEventId = c.req.header("Last-Event-ID") || undefined;
|
|
161
|
+
return streamSSE(c, async (stream) => {
|
|
162
|
+
const cancelOnDisconnect = query.cancel_on_disconnect
|
|
163
|
+
? getDisconnectAbortSignal(c, stream)
|
|
164
|
+
: undefined;
|
|
165
|
+
try {
|
|
166
|
+
for await (const { id, event, data } of Runs.Stream.join(run_id, undefined, { cancelOnDisconnect, lastEventId, ignore404: true }, c.var.auth)) {
|
|
167
|
+
await stream.writeSSE({ id, data: serialiseAsDict(data), event });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
logError(error, { prefix: "Error streaming run" });
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
151
175
|
api.post("/runs/wait", zValidator("json", schemas.RunCreate), async (c) => {
|
|
152
176
|
// Wait Stateless Run
|
|
153
177
|
const payload = c.req.valid("json");
|
|
@@ -198,6 +222,7 @@ api.post("/threads/:thread_id/runs", zValidator("param", z.object({ thread_id: z
|
|
|
198
222
|
auth: c.var.auth,
|
|
199
223
|
headers: c.req.raw.headers,
|
|
200
224
|
});
|
|
225
|
+
c.header("Content-Location", `/threads/${thread_id}/runs/${run.run_id}`);
|
|
201
226
|
return jsonExtra(c, run);
|
|
202
227
|
});
|
|
203
228
|
api.post("/threads/:thread_id/runs/stream", zValidator("param", z.object({ thread_id: z.string().uuid() })), zValidator("json", schemas.RunCreate), async (c) => {
|
|
@@ -208,13 +233,17 @@ api.post("/threads/:thread_id/runs/stream", zValidator("param", z.object({ threa
|
|
|
208
233
|
auth: c.var.auth,
|
|
209
234
|
headers: c.req.raw.headers,
|
|
210
235
|
});
|
|
236
|
+
c.header("Content-Location", `/threads/${thread_id}/runs/${run.run_id}`);
|
|
211
237
|
return streamSSE(c, async (stream) => {
|
|
212
238
|
const cancelOnDisconnect = payload.on_disconnect === "cancel"
|
|
213
239
|
? getDisconnectAbortSignal(c, stream)
|
|
214
240
|
: undefined;
|
|
215
241
|
try {
|
|
216
|
-
for await (const { event, data } of Runs.Stream.join(run.run_id, thread_id, {
|
|
217
|
-
|
|
242
|
+
for await (const { id, event, data } of Runs.Stream.join(run.run_id, thread_id, {
|
|
243
|
+
cancelOnDisconnect,
|
|
244
|
+
lastEventId: run.kwargs.resumable ? "-1" : undefined,
|
|
245
|
+
}, c.var.auth)) {
|
|
246
|
+
await stream.writeSSE({ id, data: serialiseAsDict(data), event });
|
|
218
247
|
}
|
|
219
248
|
}
|
|
220
249
|
catch (error) {
|
|
@@ -230,6 +259,7 @@ api.post("/threads/:thread_id/runs/wait", zValidator("param", z.object({ thread_
|
|
|
230
259
|
auth: c.var.auth,
|
|
231
260
|
headers: c.req.raw.headers,
|
|
232
261
|
});
|
|
262
|
+
c.header("Content-Location", `/threads/${thread_id}/runs/${run.run_id}`);
|
|
233
263
|
return waitKeepAlive(c, Runs.join(run.run_id, thread_id, c.var.auth));
|
|
234
264
|
});
|
|
235
265
|
api.get("/threads/:thread_id/runs/:run_id", zValidator("param", z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })), async (c) => {
|
|
@@ -257,12 +287,13 @@ api.get("/threads/:thread_id/runs/:run_id/stream", zValidator("param", z.object(
|
|
|
257
287
|
// Stream Run Http
|
|
258
288
|
const { thread_id, run_id } = c.req.valid("param");
|
|
259
289
|
const { cancel_on_disconnect } = c.req.valid("query");
|
|
290
|
+
const lastEventId = c.req.header("Last-Event-ID") || undefined;
|
|
260
291
|
return streamSSE(c, async (stream) => {
|
|
261
292
|
const signal = cancel_on_disconnect
|
|
262
293
|
? getDisconnectAbortSignal(c, stream)
|
|
263
294
|
: undefined;
|
|
264
|
-
for await (const { event, data } of Runs.Stream.join(run_id, thread_id, { cancelOnDisconnect: signal }, c.var.auth)) {
|
|
265
|
-
await stream.writeSSE({ data: serialiseAsDict(data), event });
|
|
295
|
+
for await (const { id, event, data } of Runs.Stream.join(run_id, thread_id, { cancelOnDisconnect: signal, lastEventId }, c.var.auth)) {
|
|
296
|
+
await stream.writeSSE({ id, data: serialiseAsDict(data), event });
|
|
266
297
|
}
|
|
267
298
|
});
|
|
268
299
|
});
|
|
@@ -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<
|
|
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
|
-
|
|
4
|
-
if (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 =
|
|
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 =
|
|
165
|
+
const inferFilePath = `__langgraph__infer_${suffix}.mts`;
|
|
165
166
|
const inferContents = [
|
|
166
|
-
...typeExports.map(({ typeName }) => `import type { ${typeName}} from "./
|
|
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
|
-
|
|
185
|
-
|
|
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,
|
|
232
|
-
|
|
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,
|
|
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
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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(
|
|
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(
|
|
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: [
|
|
303
|
+
rootNames: [INFER_TEMPLATE_PATH, ...targetPaths.map((i) => i.sourceFile)],
|
|
275
304
|
options: compilerOptions,
|
|
276
305
|
host,
|
|
277
306
|
});
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
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:
|
|
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
|
|
5
|
+
const result = SubgraphExtractor.extractSchemas(payload, { strict: false });
|
|
6
6
|
parentPort?.postMessage(result);
|
|
7
7
|
});
|
package/dist/http/middleware.mjs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { cors as honoCors } from "hono/cors";
|
|
2
2
|
export const cors = (cors) => {
|
|
3
|
-
if (cors == null)
|
|
4
|
-
return honoCors(
|
|
3
|
+
if (cors == null) {
|
|
4
|
+
return honoCors({
|
|
5
|
+
origin: "*",
|
|
6
|
+
exposeHeaders: ["content-location"],
|
|
7
|
+
});
|
|
8
|
+
}
|
|
5
9
|
const originRegex = cors.allow_origin_regex
|
|
6
10
|
? new RegExp(cors.allow_origin_regex)
|
|
7
11
|
: undefined;
|
|
@@ -13,6 +17,11 @@ export const cors = (cors) => {
|
|
|
13
17
|
return undefined;
|
|
14
18
|
}
|
|
15
19
|
: (cors.allow_origins ?? []);
|
|
20
|
+
if (!!cors.expose_headers?.length &&
|
|
21
|
+
cors.expose_headers.some((i) => i.toLocaleLowerCase() === "content-location")) {
|
|
22
|
+
console.warn("Adding missing `Content-Location` header in `cors.expose_headers`.");
|
|
23
|
+
cors.expose_headers.push("content-location");
|
|
24
|
+
}
|
|
16
25
|
// TODO: handle `cors.allow_credentials`
|
|
17
26
|
return honoCors({
|
|
18
27
|
origin,
|
package/dist/queue.mjs
CHANGED
|
@@ -45,17 +45,24 @@ const worker = async (run, attempt, abortSignal) => {
|
|
|
45
45
|
if (attempt > MAX_RETRY_ATTEMPTS) {
|
|
46
46
|
throw new Error(`Run ${run.run_id} exceeded max attempts`);
|
|
47
47
|
}
|
|
48
|
+
const runId = run.run_id;
|
|
49
|
+
const resumable = run.kwargs?.resumable ?? false;
|
|
48
50
|
try {
|
|
49
51
|
const stream = streamState(run, attempt, {
|
|
50
52
|
signal: abortSignal,
|
|
51
53
|
...(!temporary ? { onCheckpoint, onTaskResult } : undefined),
|
|
52
54
|
});
|
|
53
55
|
for await (const { event, data } of stream) {
|
|
54
|
-
await Runs.Stream.publish(
|
|
56
|
+
await Runs.Stream.publish({ runId, resumable, event, data });
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
catch (error) {
|
|
58
|
-
await Runs.Stream.publish(
|
|
60
|
+
await Runs.Stream.publish({
|
|
61
|
+
runId,
|
|
62
|
+
resumable,
|
|
63
|
+
event: "error",
|
|
64
|
+
data: serializeError(error),
|
|
65
|
+
});
|
|
59
66
|
throw error;
|
|
60
67
|
}
|
|
61
68
|
endedAt = new Date();
|
package/dist/schemas.d.mts
CHANGED
|
@@ -687,6 +687,7 @@ export declare const RunCreate: z.ZodObject<{
|
|
|
687
687
|
multitask_strategy: z.ZodOptional<z.ZodEnum<["reject", "rollback", "interrupt", "enqueue"]>>;
|
|
688
688
|
stream_mode: z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodEnum<["values", "messages", "messages-tuple", "updates", "events", "debug", "custom"]>, "many">, z.ZodEnum<["values", "messages", "messages-tuple", "updates", "events", "debug", "custom"]>]>>;
|
|
689
689
|
stream_subgraphs: z.ZodOptional<z.ZodBoolean>;
|
|
690
|
+
stream_resumable: z.ZodOptional<z.ZodBoolean>;
|
|
690
691
|
after_seconds: z.ZodOptional<z.ZodNumber>;
|
|
691
692
|
if_not_exists: z.ZodOptional<z.ZodEnum<["reject", "create"]>>;
|
|
692
693
|
on_completion: z.ZodOptional<z.ZodEnum<["delete", "keep"]>>;
|
|
@@ -736,6 +737,7 @@ export declare const RunCreate: z.ZodObject<{
|
|
|
736
737
|
if_not_exists?: "reject" | "create" | undefined;
|
|
737
738
|
after_seconds?: number | undefined;
|
|
738
739
|
stream_subgraphs?: boolean | undefined;
|
|
740
|
+
stream_resumable?: boolean | undefined;
|
|
739
741
|
on_completion?: "delete" | "keep" | undefined;
|
|
740
742
|
}, {
|
|
741
743
|
assistant_id: string;
|
|
@@ -782,6 +784,7 @@ export declare const RunCreate: z.ZodObject<{
|
|
|
782
784
|
after_seconds?: number | undefined;
|
|
783
785
|
on_disconnect?: "cancel" | "continue" | undefined;
|
|
784
786
|
stream_subgraphs?: boolean | undefined;
|
|
787
|
+
stream_resumable?: boolean | undefined;
|
|
785
788
|
on_completion?: "delete" | "keep" | undefined;
|
|
786
789
|
}>;
|
|
787
790
|
export declare const RunBatchCreate: z.ZodArray<z.ZodObject<{
|
|
@@ -892,6 +895,7 @@ export declare const RunBatchCreate: z.ZodArray<z.ZodObject<{
|
|
|
892
895
|
multitask_strategy: z.ZodOptional<z.ZodEnum<["reject", "rollback", "interrupt", "enqueue"]>>;
|
|
893
896
|
stream_mode: z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodEnum<["values", "messages", "messages-tuple", "updates", "events", "debug", "custom"]>, "many">, z.ZodEnum<["values", "messages", "messages-tuple", "updates", "events", "debug", "custom"]>]>>;
|
|
894
897
|
stream_subgraphs: z.ZodOptional<z.ZodBoolean>;
|
|
898
|
+
stream_resumable: z.ZodOptional<z.ZodBoolean>;
|
|
895
899
|
after_seconds: z.ZodOptional<z.ZodNumber>;
|
|
896
900
|
if_not_exists: z.ZodOptional<z.ZodEnum<["reject", "create"]>>;
|
|
897
901
|
on_completion: z.ZodOptional<z.ZodEnum<["delete", "keep"]>>;
|
|
@@ -941,6 +945,7 @@ export declare const RunBatchCreate: z.ZodArray<z.ZodObject<{
|
|
|
941
945
|
if_not_exists?: "reject" | "create" | undefined;
|
|
942
946
|
after_seconds?: number | undefined;
|
|
943
947
|
stream_subgraphs?: boolean | undefined;
|
|
948
|
+
stream_resumable?: boolean | undefined;
|
|
944
949
|
on_completion?: "delete" | "keep" | undefined;
|
|
945
950
|
}, {
|
|
946
951
|
assistant_id: string;
|
|
@@ -987,6 +992,7 @@ export declare const RunBatchCreate: z.ZodArray<z.ZodObject<{
|
|
|
987
992
|
after_seconds?: number | undefined;
|
|
988
993
|
on_disconnect?: "cancel" | "continue" | undefined;
|
|
989
994
|
stream_subgraphs?: boolean | undefined;
|
|
995
|
+
stream_resumable?: boolean | undefined;
|
|
990
996
|
on_completion?: "delete" | "keep" | undefined;
|
|
991
997
|
}>, "many">;
|
|
992
998
|
export declare const SearchResult: z.ZodObject<{
|
package/dist/schemas.mjs
CHANGED
|
@@ -205,6 +205,7 @@ export const RunCreate = z
|
|
|
205
205
|
])
|
|
206
206
|
.optional(),
|
|
207
207
|
stream_subgraphs: z.boolean().optional(),
|
|
208
|
+
stream_resumable: z.boolean().optional(),
|
|
208
209
|
after_seconds: z.number().optional(),
|
|
209
210
|
if_not_exists: z.enum(["reject", "create"]).optional(),
|
|
210
211
|
on_completion: z.enum(["delete", "keep"]).optional(),
|
package/dist/server.d.mts
CHANGED
|
@@ -40,19 +40,19 @@ export declare const StartServerSchema: z.ZodObject<{
|
|
|
40
40
|
max_age: z.ZodOptional<z.ZodNumber>;
|
|
41
41
|
}, "strip", z.ZodTypeAny, {
|
|
42
42
|
allow_origin_regex?: string | undefined;
|
|
43
|
+
expose_headers?: string[] | undefined;
|
|
43
44
|
allow_origins?: string[] | undefined;
|
|
44
45
|
allow_methods?: string[] | undefined;
|
|
45
46
|
allow_headers?: string[] | undefined;
|
|
46
47
|
allow_credentials?: boolean | undefined;
|
|
47
|
-
expose_headers?: string[] | undefined;
|
|
48
48
|
max_age?: number | undefined;
|
|
49
49
|
}, {
|
|
50
50
|
allow_origin_regex?: string | undefined;
|
|
51
|
+
expose_headers?: string[] | undefined;
|
|
51
52
|
allow_origins?: string[] | undefined;
|
|
52
53
|
allow_methods?: string[] | undefined;
|
|
53
54
|
allow_headers?: string[] | undefined;
|
|
54
55
|
allow_credentials?: boolean | undefined;
|
|
55
|
-
expose_headers?: string[] | undefined;
|
|
56
56
|
max_age?: number | undefined;
|
|
57
57
|
}>>;
|
|
58
58
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -63,22 +63,22 @@ export declare const StartServerSchema: z.ZodObject<{
|
|
|
63
63
|
disable_meta: boolean;
|
|
64
64
|
cors?: {
|
|
65
65
|
allow_origin_regex?: string | undefined;
|
|
66
|
+
expose_headers?: string[] | undefined;
|
|
66
67
|
allow_origins?: string[] | undefined;
|
|
67
68
|
allow_methods?: string[] | undefined;
|
|
68
69
|
allow_headers?: string[] | undefined;
|
|
69
70
|
allow_credentials?: boolean | undefined;
|
|
70
|
-
expose_headers?: string[] | undefined;
|
|
71
71
|
max_age?: number | undefined;
|
|
72
72
|
} | undefined;
|
|
73
73
|
app?: string | undefined;
|
|
74
74
|
}, {
|
|
75
75
|
cors?: {
|
|
76
76
|
allow_origin_regex?: string | undefined;
|
|
77
|
+
expose_headers?: string[] | undefined;
|
|
77
78
|
allow_origins?: string[] | undefined;
|
|
78
79
|
allow_methods?: string[] | undefined;
|
|
79
80
|
allow_headers?: string[] | undefined;
|
|
80
81
|
allow_credentials?: boolean | undefined;
|
|
81
|
-
expose_headers?: string[] | undefined;
|
|
82
82
|
max_age?: number | undefined;
|
|
83
83
|
} | undefined;
|
|
84
84
|
app?: string | undefined;
|
|
@@ -110,11 +110,11 @@ export declare const StartServerSchema: z.ZodObject<{
|
|
|
110
110
|
disable_meta: boolean;
|
|
111
111
|
cors?: {
|
|
112
112
|
allow_origin_regex?: string | undefined;
|
|
113
|
+
expose_headers?: string[] | undefined;
|
|
113
114
|
allow_origins?: string[] | undefined;
|
|
114
115
|
allow_methods?: string[] | undefined;
|
|
115
116
|
allow_headers?: string[] | undefined;
|
|
116
117
|
allow_credentials?: boolean | undefined;
|
|
117
|
-
expose_headers?: string[] | undefined;
|
|
118
118
|
max_age?: number | undefined;
|
|
119
119
|
} | undefined;
|
|
120
120
|
app?: string | undefined;
|
|
@@ -136,11 +136,11 @@ export declare const StartServerSchema: z.ZodObject<{
|
|
|
136
136
|
http?: {
|
|
137
137
|
cors?: {
|
|
138
138
|
allow_origin_regex?: string | undefined;
|
|
139
|
+
expose_headers?: string[] | undefined;
|
|
139
140
|
allow_origins?: string[] | undefined;
|
|
140
141
|
allow_methods?: string[] | undefined;
|
|
141
142
|
allow_headers?: string[] | undefined;
|
|
142
143
|
allow_credentials?: boolean | undefined;
|
|
143
|
-
expose_headers?: string[] | undefined;
|
|
144
144
|
max_age?: number | undefined;
|
|
145
145
|
} | undefined;
|
|
146
146
|
app?: string | undefined;
|
package/dist/storage/ops.d.mts
CHANGED
|
@@ -46,6 +46,7 @@ export interface RunKwargs {
|
|
|
46
46
|
interrupt_after?: "*" | string[] | undefined;
|
|
47
47
|
config?: RunnableConfig;
|
|
48
48
|
subgraphs?: boolean;
|
|
49
|
+
resumable?: boolean;
|
|
49
50
|
temporary?: boolean;
|
|
50
51
|
webhook?: unknown;
|
|
51
52
|
feedback_keys?: string[] | undefined;
|
|
@@ -75,13 +76,19 @@ interface Message {
|
|
|
75
76
|
data: unknown;
|
|
76
77
|
}
|
|
77
78
|
declare class Queue {
|
|
78
|
-
private
|
|
79
|
+
private log;
|
|
79
80
|
private listeners;
|
|
81
|
+
private nextId;
|
|
82
|
+
private resumable;
|
|
83
|
+
constructor(options: {
|
|
84
|
+
resumable: boolean;
|
|
85
|
+
});
|
|
80
86
|
push(item: Message): void;
|
|
81
87
|
get(options: {
|
|
82
88
|
timeout: number;
|
|
89
|
+
lastEventId?: string;
|
|
83
90
|
signal?: AbortSignal;
|
|
84
|
-
}): Promise<Message>;
|
|
91
|
+
}): Promise<[id: string, message: Message]>;
|
|
85
92
|
}
|
|
86
93
|
declare class CancellationAbortController extends AbortController {
|
|
87
94
|
abort(reason: "rollback" | "interrupt"): void;
|
|
@@ -91,10 +98,8 @@ declare class StreamManagerImpl {
|
|
|
91
98
|
control: Record<string, CancellationAbortController>;
|
|
92
99
|
getQueue(runId: string, options: {
|
|
93
100
|
ifNotFound: "create";
|
|
101
|
+
resumable: boolean;
|
|
94
102
|
}): Queue;
|
|
95
|
-
getQueue(runId: string, options: {
|
|
96
|
-
ifNotFound: "ignore";
|
|
97
|
-
}): Queue | undefined;
|
|
98
103
|
getControl(runId: string): CancellationAbortController | undefined;
|
|
99
104
|
isLocked(runId: string): boolean;
|
|
100
105
|
lock(runId: string): AbortSignal;
|
|
@@ -272,11 +277,18 @@ export declare class Runs {
|
|
|
272
277
|
join(runId: string, threadId: string | undefined, options: {
|
|
273
278
|
ignore404?: boolean;
|
|
274
279
|
cancelOnDisconnect?: AbortSignal;
|
|
280
|
+
lastEventId: string | undefined;
|
|
275
281
|
}, auth: AuthContext | undefined): AsyncGenerator<{
|
|
282
|
+
id?: string;
|
|
276
283
|
event: string;
|
|
277
284
|
data: unknown;
|
|
278
285
|
}>;
|
|
279
|
-
publish(
|
|
286
|
+
publish(payload: {
|
|
287
|
+
runId: string;
|
|
288
|
+
event: string;
|
|
289
|
+
data: unknown;
|
|
290
|
+
resumable: boolean;
|
|
291
|
+
}): Promise<void>;
|
|
280
292
|
};
|
|
281
293
|
}
|
|
282
294
|
export declare class Crons {
|
package/dist/storage/ops.mjs
CHANGED
|
@@ -20,28 +20,58 @@ class TimeoutError extends Error {
|
|
|
20
20
|
class AbortError extends Error {
|
|
21
21
|
}
|
|
22
22
|
class Queue {
|
|
23
|
-
|
|
23
|
+
log = [];
|
|
24
24
|
listeners = [];
|
|
25
|
+
nextId = 0;
|
|
26
|
+
resumable = false;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.resumable = options.resumable;
|
|
29
|
+
}
|
|
25
30
|
push(item) {
|
|
26
|
-
this.
|
|
27
|
-
for (const listener of this.listeners)
|
|
28
|
-
listener();
|
|
29
|
-
|
|
31
|
+
this.log.push(item);
|
|
32
|
+
for (const listener of this.listeners)
|
|
33
|
+
listener(this.nextId);
|
|
34
|
+
this.nextId += 1;
|
|
30
35
|
}
|
|
31
36
|
async get(options) {
|
|
32
|
-
if (this.
|
|
33
|
-
|
|
37
|
+
if (this.resumable) {
|
|
38
|
+
const lastEventId = options.lastEventId;
|
|
39
|
+
// Generator stores internal state of the read head index,
|
|
40
|
+
let targetId = lastEventId != null ? +lastEventId + 1 : null;
|
|
41
|
+
if (targetId == null ||
|
|
42
|
+
isNaN(targetId) ||
|
|
43
|
+
targetId < 0 ||
|
|
44
|
+
targetId >= this.log.length) {
|
|
45
|
+
targetId = null;
|
|
46
|
+
}
|
|
47
|
+
if (targetId != null)
|
|
48
|
+
return [String(targetId), this.log[targetId]];
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
if (this.log.length) {
|
|
52
|
+
const nextId = this.nextId - this.log.length;
|
|
53
|
+
const nextItem = this.log.shift();
|
|
54
|
+
return [String(nextId), nextItem];
|
|
55
|
+
}
|
|
34
56
|
}
|
|
35
57
|
let timeout = undefined;
|
|
36
58
|
let resolver = undefined;
|
|
37
59
|
const clean = new AbortController();
|
|
60
|
+
// listen to new item
|
|
38
61
|
return await new Promise((resolve, reject) => {
|
|
39
62
|
timeout = setTimeout(() => reject(new TimeoutError()), options.timeout);
|
|
40
63
|
resolver = resolve;
|
|
41
64
|
options.signal?.addEventListener("abort", () => reject(new AbortError()), { signal: clean.signal });
|
|
42
65
|
this.listeners.push(resolver);
|
|
43
66
|
})
|
|
44
|
-
.then(() =>
|
|
67
|
+
.then((idx) => {
|
|
68
|
+
if (this.resumable) {
|
|
69
|
+
return [String(idx), this.log[idx]];
|
|
70
|
+
}
|
|
71
|
+
const nextId = this.nextId - this.log.length;
|
|
72
|
+
const nextItem = this.log.shift();
|
|
73
|
+
return [String(nextId), nextItem];
|
|
74
|
+
})
|
|
45
75
|
.finally(() => {
|
|
46
76
|
this.listeners = this.listeners.filter((l) => l !== resolver);
|
|
47
77
|
clearTimeout(timeout);
|
|
@@ -59,12 +89,7 @@ class StreamManagerImpl {
|
|
|
59
89
|
control = {};
|
|
60
90
|
getQueue(runId, options) {
|
|
61
91
|
if (this.readers[runId] == null) {
|
|
62
|
-
|
|
63
|
-
this.readers[runId] = new Queue();
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
92
|
+
this.readers[runId] = new Queue(options);
|
|
68
93
|
}
|
|
69
94
|
return this.readers[runId];
|
|
70
95
|
}
|
|
@@ -883,7 +908,7 @@ export class Runs {
|
|
|
883
908
|
});
|
|
884
909
|
}
|
|
885
910
|
static async wait(runId, threadId, auth) {
|
|
886
|
-
const runStream = Runs.Stream.join(runId, threadId, { ignore404: threadId == null }, auth);
|
|
911
|
+
const runStream = Runs.Stream.join(runId, threadId, { ignore404: threadId == null, lastEventId: undefined }, auth);
|
|
887
912
|
const lastChunk = new Promise(async (resolve, reject) => {
|
|
888
913
|
try {
|
|
889
914
|
let lastChunk = null;
|
|
@@ -1012,7 +1037,10 @@ export class Runs {
|
|
|
1012
1037
|
yield* conn.withGenerator(async function* (STORE) {
|
|
1013
1038
|
// TODO: what if we're joining an already completed run? Should we check before?
|
|
1014
1039
|
const signal = options?.cancelOnDisconnect;
|
|
1015
|
-
const queue = StreamManager.getQueue(runId, {
|
|
1040
|
+
const queue = StreamManager.getQueue(runId, {
|
|
1041
|
+
ifNotFound: "create",
|
|
1042
|
+
resumable: options.lastEventId != null,
|
|
1043
|
+
});
|
|
1016
1044
|
const [filters] = await handleAuthEvent(auth, "threads:read", {
|
|
1017
1045
|
thread_id: threadId,
|
|
1018
1046
|
});
|
|
@@ -1027,16 +1055,22 @@ export class Runs {
|
|
|
1027
1055
|
return;
|
|
1028
1056
|
}
|
|
1029
1057
|
}
|
|
1058
|
+
let lastEventId = options?.lastEventId;
|
|
1030
1059
|
while (!signal?.aborted) {
|
|
1031
1060
|
try {
|
|
1032
|
-
const message = await queue.get({
|
|
1061
|
+
const [id, message] = await queue.get({
|
|
1062
|
+
timeout: 500,
|
|
1063
|
+
signal,
|
|
1064
|
+
lastEventId,
|
|
1065
|
+
});
|
|
1066
|
+
lastEventId = id;
|
|
1033
1067
|
if (message.topic === `run:${runId}:control`) {
|
|
1034
1068
|
if (message.data === "done")
|
|
1035
1069
|
break;
|
|
1036
1070
|
}
|
|
1037
1071
|
else {
|
|
1038
1072
|
const streamTopic = message.topic.substring(`run:${runId}:stream:`.length);
|
|
1039
|
-
yield { event: streamTopic, data: message.data };
|
|
1073
|
+
yield { id, event: streamTopic, data: message.data };
|
|
1040
1074
|
}
|
|
1041
1075
|
}
|
|
1042
1076
|
catch (error) {
|
|
@@ -1058,9 +1092,15 @@ export class Runs {
|
|
|
1058
1092
|
}
|
|
1059
1093
|
});
|
|
1060
1094
|
}
|
|
1061
|
-
static async publish(
|
|
1062
|
-
const queue = StreamManager.getQueue(runId, {
|
|
1063
|
-
|
|
1095
|
+
static async publish(payload) {
|
|
1096
|
+
const queue = StreamManager.getQueue(payload.runId, {
|
|
1097
|
+
ifNotFound: "create",
|
|
1098
|
+
resumable: payload.resumable,
|
|
1099
|
+
});
|
|
1100
|
+
queue.push({
|
|
1101
|
+
topic: `run:${payload.runId}:stream:${payload.event}`,
|
|
1102
|
+
data: payload.data,
|
|
1103
|
+
});
|
|
1064
1104
|
}
|
|
1065
1105
|
};
|
|
1066
1106
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langchain/langgraph-api",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.36",
|
|
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.
|
|
56
|
+
"@langchain/langgraph-ui": "0.0.36"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@langchain/core": "^0.3.42",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"typescript": "^5.5.4",
|
|
72
|
-
"@langchain/langgraph-sdk": "^0.0.
|
|
72
|
+
"@langchain/langgraph-sdk": "^0.0.77",
|
|
73
73
|
"@types/babel__code-frame": "^7.0.6",
|
|
74
74
|
"@types/node": "^22.2.0",
|
|
75
75
|
"@types/react": "^19.0.8",
|