@langchain/langgraph-api 0.0.16 → 0.0.18

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.
@@ -15,6 +15,9 @@ api.post("/threads", zValidator("json", schemas.ThreadCreate), async (c) => {
15
15
  metadata: payload.metadata,
16
16
  if_exists: payload.if_exists ?? "raise",
17
17
  });
18
+ if (payload.supersteps?.length) {
19
+ await Threads.State.bulk({ configurable: { thread_id: thread.thread_id } }, payload.supersteps);
20
+ }
18
21
  return jsonExtra(c, thread);
19
22
  });
20
23
  api.post("/threads/search", zValidator("json", schemas.ThreadSearchRequest), async (c) => {
@@ -37,5 +37,5 @@ logger.info(`Server running at ${host}`);
37
37
  let queryParams = `?baseUrl=http://${options.host}:${options.port}`;
38
38
  if (organizationId)
39
39
  queryParams += `&organizationId=${organizationId}`;
40
- asyncExitHook(cleanup, { wait: 1000 });
40
+ asyncExitHook(cleanup, { wait: 3_000 });
41
41
  sendToParent?.({ queryParams });
@@ -0,0 +1,15 @@
1
+ import { Command, Send } from "@langchain/langgraph";
2
+ export const getLangGraphCommand = (command) => {
3
+ let goto = command.goto != null && !Array.isArray(command.goto)
4
+ ? [command.goto]
5
+ : command.goto;
6
+ return new Command({
7
+ goto: goto?.map((item) => {
8
+ if (typeof item !== "string")
9
+ return new Send(item.node, item.input);
10
+ return item;
11
+ }),
12
+ update: command.update,
13
+ resume: command.resume,
14
+ });
15
+ };
@@ -8,6 +8,7 @@ const OVERRIDE_RESOLVE = [
8
8
  new RegExp(`^@langchain\/langgraph-checkpoint(\/.+)?$`),
9
9
  ];
10
10
  let parentURL;
11
+ let langgraphPackageURL;
11
12
  export async function initialize(args) {
12
13
  parentURL = args.parentURL;
13
14
  }
@@ -15,12 +16,37 @@ export async function resolve(specifier, context, nextResolve) {
15
16
  // HACK: @tailwindcss/node internally uses an ESM loader cache, which does not play nicely with `tsx`.
16
17
  // Node.js crashes with "TypeError [ERR_INVALID_URL_SCHEME]: The URL must be of scheme file".
17
18
  // As it already is a valid URI, we can just short-circuit the resolution and avoid `tsx`.
18
- if (specifier.includes("@tailwindcss/node/dist/esm-cache.loader.mjs") &&
19
+ if (specifier.includes("@tailwindcss/node/dist/esm-cache.loader") &&
19
20
  specifier.startsWith("file://")) {
20
- return { shortCircuit: true, url: specifier, format: "module" };
21
+ return {
22
+ shortCircuit: true,
23
+ // Node 18.x will for some reason attempt to load `.mts` instead of `.mjs`
24
+ url: specifier.replace(".mts", ".mjs"),
25
+ format: "module",
26
+ };
27
+ }
28
+ if (specifier === "@langchain/langgraph-checkpoint") {
29
+ // resolve relative to @langchain/langgraph package instead
30
+ // This is done to avoid adding a direct dependency on @langchain/langgraph-checkpoint
31
+ // in project, which if not present will cause `pnpm` to not find the package.
32
+ if (!langgraphPackageURL) {
33
+ const main = await nextResolve("@langchain/langgraph", {
34
+ ...context,
35
+ parentURL,
36
+ });
37
+ langgraphPackageURL = main.url.toString();
38
+ }
39
+ return await nextResolve(specifier, {
40
+ ...context,
41
+ parentURL: langgraphPackageURL,
42
+ });
21
43
  }
22
44
  if (OVERRIDE_RESOLVE.some((regex) => regex.test(specifier))) {
23
- return await nextResolve(specifier, { ...context, parentURL });
45
+ const resolved = await nextResolve(specifier, { ...context, parentURL });
46
+ // If @langchain/langgraph is resolved first, cache it!
47
+ if (specifier === "@langchain/langgraph" && !langgraphPackageURL) {
48
+ langgraphPackageURL = resolved.url.toString();
49
+ }
24
50
  }
25
51
  return nextResolve(specifier, context);
26
52
  }
package/dist/schemas.mjs CHANGED
@@ -140,35 +140,31 @@ export const Run = z.object({
140
140
  kwargs: z.object({}).catchall(z.any()),
141
141
  multitask_strategy: z.enum(["reject", "rollback", "interrupt", "enqueue"]),
142
142
  });
143
+ export const CommandSchema = z.object({
144
+ goto: z
145
+ .union([
146
+ z.union([
147
+ z.string(),
148
+ z.object({ node: z.string(), input: z.unknown().optional() }),
149
+ ]),
150
+ z.array(z.union([
151
+ z.string(),
152
+ z.object({ node: z.string(), input: z.unknown().optional() }),
153
+ ])),
154
+ ])
155
+ .optional(),
156
+ update: z
157
+ .union([z.record(z.unknown()), z.array(z.tuple([z.string(), z.unknown()]))])
158
+ .optional(),
159
+ resume: z.unknown().optional(),
160
+ });
143
161
  export const RunCreate = z
144
162
  .object({
145
163
  assistant_id: z.union([z.string().uuid(), z.string()]),
146
164
  checkpoint_id: z.string().optional(),
147
165
  checkpoint: CheckpointSchema.optional(),
148
166
  input: z.union([z.unknown(), z.null()]).optional(),
149
- command: z
150
- .object({
151
- goto: z
152
- .union([
153
- z.union([
154
- z.string(),
155
- z.object({ node: z.string(), input: z.unknown().optional() }),
156
- ]),
157
- z.array(z.union([
158
- z.string(),
159
- z.object({ node: z.string(), input: z.unknown().optional() }),
160
- ])),
161
- ])
162
- .optional(),
163
- update: z
164
- .union([
165
- z.record(z.unknown()),
166
- z.array(z.tuple([z.string(), z.unknown()])),
167
- ])
168
- .optional(),
169
- resume: z.unknown().optional(),
170
- })
171
- .optional(),
167
+ command: CommandSchema.optional(),
172
168
  metadata: z
173
169
  .object({})
174
170
  .catchall(z.any())
@@ -301,6 +297,16 @@ export const Thread = z.object({
301
297
  });
302
298
  export const ThreadCreate = z
303
299
  .object({
300
+ supersteps: z
301
+ .array(z.object({
302
+ updates: z.array(z.object({
303
+ values: z.unknown().nullish(),
304
+ command: CommandSchema.nullish(),
305
+ as_node: z.string(),
306
+ })),
307
+ }))
308
+ .describe("The supersteps to apply to the thread.")
309
+ .optional(),
304
310
  thread_id: z
305
311
  .string()
306
312
  .uuid()
@@ -6,6 +6,7 @@ import { store } from "./store.mjs";
6
6
  import { logger } from "../logging.mjs";
7
7
  import { serializeError } from "../utils/serde.mjs";
8
8
  import { FileSystemPersistence } from "./persist.mjs";
9
+ import { getLangGraphCommand } from "../command.mjs";
9
10
  export const conn = new FileSystemPersistence(".langgraphjs_ops.json", () => ({
10
11
  runs: {},
11
12
  threads: {},
@@ -487,6 +488,41 @@ export class Threads {
487
488
  });
488
489
  return { checkpoint: nextConfig.configurable };
489
490
  }
491
+ static async bulk(config, supersteps) {
492
+ const threadId = config.configurable?.thread_id;
493
+ if (!threadId)
494
+ return [];
495
+ const thread = await Threads.get(threadId);
496
+ const graphId = thread.metadata?.graph_id;
497
+ if (graphId == null) {
498
+ throw new HTTPException(400, {
499
+ message: `Thread ${threadId} has no graph ID`,
500
+ });
501
+ }
502
+ config.configurable ??= {};
503
+ config.configurable.graph_id ??= graphId;
504
+ const graph = await getGraph(graphId, { checkpointer, store });
505
+ const updateConfig = structuredClone(config);
506
+ updateConfig.configurable ??= {};
507
+ updateConfig.configurable.checkpoint_ns ??= "";
508
+ const nextConfig = await graph.bulkUpdateState(updateConfig, supersteps.map((i) => ({
509
+ updates: i.updates.map((j) => ({
510
+ values: j.command != null ? getLangGraphCommand(j.command) : j.values,
511
+ asNode: j.as_node,
512
+ })),
513
+ })));
514
+ const state = await Threads.State.get(config, { subgraphs: false });
515
+ // update thread values
516
+ await conn.with(async (STORE) => {
517
+ for (const thread of Object.values(STORE.threads)) {
518
+ if (thread.thread_id === threadId) {
519
+ thread.values = state.values;
520
+ break;
521
+ }
522
+ }
523
+ });
524
+ return { checkpoint: nextConfig.configurable };
525
+ }
490
526
  static async list(config, options) {
491
527
  const threadId = config.configurable?.thread_id;
492
528
  if (!threadId)
@@ -513,7 +549,7 @@ export class Threads {
513
549
  }
514
550
  export class Runs {
515
551
  static async *next() {
516
- yield* conn.withGenerator(async function* (STORE) {
552
+ yield* conn.withGenerator(async function* (STORE, options) {
517
553
  const now = new Date();
518
554
  const pendingRuns = Object.values(STORE.runs)
519
555
  .filter((run) => run.status === "pending" && run.created_at < now)
@@ -533,6 +569,7 @@ export class Runs {
533
569
  continue;
534
570
  try {
535
571
  const signal = StreamManager.lock(runId);
572
+ options.schedulePersist();
536
573
  STORE.retry_counter[runId] ??= 0;
537
574
  STORE.retry_counter[runId] += 1;
538
575
  yield { run, attempt: STORE.retry_counter[runId], signal };
@@ -67,12 +67,15 @@ export class FileSystemPersistence {
67
67
  if (this.filepath == null || this.data == null) {
68
68
  throw new Error(`${this.name} not initialized`);
69
69
  }
70
+ let shouldPersist = false;
71
+ let schedulePersist = () => void (shouldPersist = true);
70
72
  try {
71
- const gen = typeof fn === "function" ? fn(this.data) : fn;
73
+ const gen = typeof fn === "function" ? fn(this.data, { schedulePersist }) : fn;
72
74
  yield* gen;
73
75
  }
74
76
  finally {
75
- this.schedulePersist();
77
+ if (shouldPersist)
78
+ this.schedulePersist();
76
79
  }
77
80
  }
78
81
  }
package/dist/stream.mjs CHANGED
@@ -1,22 +1,8 @@
1
1
  import { getGraph } from "./graph/load.mjs";
2
2
  import { Client as LangSmithClient } from "langsmith";
3
- import { Command, Send, } from "@langchain/langgraph";
4
3
  import { runnableConfigToCheckpoint, taskRunnableConfigToCheckpoint, } from "./utils/runnableConfig.mjs";
5
- import { BaseMessageChunk, isBaseMessage } from "@langchain/core/messages";
6
- const getLangGraphCommand = (command) => {
7
- let goto = command.goto != null && !Array.isArray(command.goto)
8
- ? [command.goto]
9
- : command.goto;
10
- return new Command({
11
- goto: goto?.map((item) => {
12
- if (typeof item !== "string")
13
- return new Send(item.node, item.input);
14
- return item;
15
- }),
16
- update: command.update,
17
- resume: command.resume,
18
- });
19
- };
4
+ import { isBaseMessage } from "@langchain/core/messages";
5
+ import { getLangGraphCommand } from "./command.mjs";
20
6
  const isRunnableConfig = (config) => {
21
7
  if (typeof config !== "object" || config == null)
22
8
  return false;
@@ -1,9 +1,15 @@
1
1
  import { type BuildOptions } from "esbuild";
2
- export declare function build(agentName: string, uiUserPath: string): Promise<{
2
+ export declare function build(agentName: string, args: {
3
+ cwd: string;
4
+ userPath: string;
5
+ }): Promise<{
3
6
  basename: string;
4
7
  contents: Uint8Array;
5
8
  }[]>;
6
- export declare function watch(agentName: string, uiUserPath: string, onResult: (result: {
9
+ export declare function watch(agentName: string, args: {
10
+ cwd: string;
11
+ userPath: string;
12
+ }, onResult: (result: {
7
13
  basename: string;
8
14
  contents: Uint8Array;
9
15
  }[]) => void): Promise<import("esbuild").BuildContext<BuildOptions>>;
@@ -4,19 +4,27 @@ import * as fs from "node:fs";
4
4
  import { build as runBuild, context as runContext, } from "esbuild";
5
5
  import tailwind from "esbuild-plugin-tailwindcss";
6
6
  const renderTemplate = await fs.promises.readFile(url.fileURLToPath(new URL("./render.template.mts", import.meta.url)), "utf-8");
7
- function entrypointPlugin(uiUserPath) {
8
- const projectDir = path.dirname(uiUserPath);
7
+ function entrypointPlugin(paths) {
8
+ const fullPath = path.resolve(paths.cwd, paths.userPath);
9
+ let relativeUiPath = path
10
+ .relative(paths.cwd, fullPath)
11
+ .replaceAll(path.sep, "/");
12
+ if (relativeUiPath.startsWith("../")) {
13
+ throw new Error(`UI path must be relative to the project root. Received: "${relativeUiPath}"`);
14
+ }
15
+ if (!relativeUiPath.startsWith("./"))
16
+ relativeUiPath = `./${relativeUiPath}`;
9
17
  return {
10
18
  name: "entrypoint",
11
19
  setup(build) {
12
- build.onResolve({ filter: /^entrypoint$/ }, (args) => ({
13
- path: path.resolve(projectDir, "ui.entrypoint.tsx"),
20
+ build.onResolve({ filter: /^entrypoint$/ }, () => ({
21
+ path: path.resolve(path.dirname(fullPath), "ui.entrypoint.tsx"),
14
22
  namespace: "entrypoint-ns",
15
23
  }));
16
24
  build.onLoad({ filter: /.*/, namespace: "entrypoint-ns" }, () => ({
17
- resolveDir: projectDir,
25
+ resolveDir: paths.cwd,
18
26
  contents: [
19
- `import ui from "${uiUserPath}"`,
27
+ `import ui from "${relativeUiPath}"`,
20
28
  renderTemplate,
21
29
  `export const render = createRenderer(ui)`,
22
30
  ].join("\n"),
@@ -46,10 +54,10 @@ function registerPlugin(onEnd) {
46
54
  },
47
55
  };
48
56
  }
49
- function setup(agentName, uiUserPath, onResult) {
57
+ function setup(agentName, args, onResult) {
50
58
  return {
51
59
  write: false,
52
- outdir: path.resolve(path.dirname(uiUserPath), "dist"),
60
+ outdir: path.resolve(args.cwd, "dist"),
53
61
  entryPoints: ["entrypoint"],
54
62
  bundle: true,
55
63
  platform: "browser",
@@ -61,21 +69,17 @@ function setup(agentName, uiUserPath, onResult) {
61
69
  "@langchain/langgraph-sdk",
62
70
  "@langchain/langgraph-sdk/react-ui",
63
71
  ],
64
- plugins: [
65
- tailwind(),
66
- entrypointPlugin(uiUserPath),
67
- registerPlugin(onResult),
68
- ],
72
+ plugins: [tailwind(), entrypointPlugin(args), registerPlugin(onResult)],
69
73
  globalName: `__LGUI_${agentName}`,
70
74
  };
71
75
  }
72
- export async function build(agentName, uiUserPath) {
76
+ export async function build(agentName, args) {
73
77
  let results = [];
74
- await runBuild(setup(agentName, uiUserPath, (result) => (results = result)));
78
+ await runBuild(setup(agentName, args, (result) => (results = result)));
75
79
  return results;
76
80
  }
77
- export async function watch(agentName, uiUserPath, onResult) {
78
- const ctx = await runContext(setup(agentName, uiUserPath, onResult));
81
+ export async function watch(agentName, args, onResult) {
82
+ const ctx = await runContext(setup(agentName, args, onResult));
79
83
  await ctx.watch();
80
84
  return ctx;
81
85
  }
package/dist/ui/load.mjs CHANGED
@@ -6,9 +6,8 @@ import * as path from "node:path";
6
6
  import { watch } from "./bundler.mjs";
7
7
  const GRAPH_UI = {};
8
8
  export async function registerGraphUi(defs, options) {
9
- const result = await Promise.all(Object.entries(defs).map(async ([agentName, uiUserPath]) => {
10
- const userPath = path.resolve(options.cwd, uiUserPath);
11
- const ctx = await watch(agentName, userPath, (result) => {
9
+ const result = await Promise.all(Object.entries(defs).map(async ([agentName, userPath]) => {
10
+ const ctx = await watch(agentName, { cwd: options.cwd, userPath }, (result) => {
12
11
  GRAPH_UI[agentName] = result;
13
12
  });
14
13
  return [agentName, ctx];
@@ -1,6 +1,5 @@
1
1
  import { serialiseAsDict } from "./serde.mjs";
2
2
  import { stream } from "hono/streaming";
3
- import { StreamingApi } from "hono/utils/stream";
4
3
  export function jsonExtra(c, object) {
5
4
  return new Response(serialiseAsDict(object), {
6
5
  ...c.res,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph-api",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": "^18.19.0 || >=20.16.0"
@@ -47,13 +47,13 @@
47
47
  "zod": "^3.23.8"
48
48
  },
49
49
  "peerDependencies": {
50
- "@langchain/core": "^0.3.40",
51
- "@langchain/langgraph": "^0.2.49",
52
- "@langchain/langgraph-checkpoint": "^0.0.15",
50
+ "@langchain/core": "^0.3.42",
51
+ "@langchain/langgraph": "^0.2.57",
52
+ "@langchain/langgraph-checkpoint": "^0.0.16",
53
53
  "typescript": "^5.5.4"
54
54
  },
55
55
  "devDependencies": {
56
- "@langchain/langgraph-sdk": "^0.0.33",
56
+ "@langchain/langgraph-sdk": "^0.0.60",
57
57
  "@types/babel__code-frame": "^7.0.6",
58
58
  "@types/react": "^19.0.8",
59
59
  "@types/react-dom": "^19.0.3",