From e85e157e8f8dc3d09c0a508d06bc06c07229e419 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Sun, 16 Feb 2025 17:36:35 +0100 Subject: [PATCH 01/17] chore: use prepack hook instead of prepublish --- libs/sdk-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index b5375664a..84f9b4eb9 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -7,7 +7,7 @@ "scripts": { "clean": "rm -rf dist/ dist-cjs/", "build": "yarn clean && yarn lc_build --create-entrypoints --pre --tree-shaking", - "prepublish": "yarn run build", + "prepack": "yarn run build", "format": "prettier --write src", "lint": "prettier --check src && tsc --noEmit", "test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts", From c9ffd753f515d9a1a533900fbb4178160682936b Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Sun, 16 Feb 2025 17:37:25 +0100 Subject: [PATCH 02/17] feat(sdk-js): add react-ui implementation --- libs/sdk-js/.gitignore | 12 +++ libs/sdk-js/langchain.config.js | 9 +- libs/sdk-js/package.json | 47 +++++++++- libs/sdk-js/src/react-ui/client.tsx | 132 ++++++++++++++++++++++++++++ libs/sdk-js/src/react-ui/server.ts | 49 +++++++++++ libs/sdk-js/src/react-ui/types.ts | 40 +++++++++ libs/sdk-js/yarn.lock | 48 +++++----- 7 files changed, 310 insertions(+), 27 deletions(-) create mode 100644 libs/sdk-js/src/react-ui/client.tsx create mode 100644 libs/sdk-js/src/react-ui/server.ts create mode 100644 libs/sdk-js/src/react-ui/types.ts diff --git a/libs/sdk-js/.gitignore b/libs/sdk-js/.gitignore index 8bb855c55..404cdf0b5 100644 --- a/libs/sdk-js/.gitignore +++ b/libs/sdk-js/.gitignore @@ -10,6 +10,18 @@ react.cjs react.js react.d.ts react.d.cts +react-ui/client.cjs +react-ui/client.js +react-ui/client.d.ts +react-ui/client.d.cts +react-ui/server.cjs +react-ui/server.js +react-ui/server.d.ts +react-ui/server.d.cts +react-ui/types.cjs +react-ui/types.js +react-ui/types.d.ts +react-ui/types.d.cts node_modules dist .yarn diff --git a/libs/sdk-js/langchain.config.js b/libs/sdk-js/langchain.config.js index b74696d7b..c404437f3 100644 --- a/libs/sdk-js/langchain.config.js +++ b/libs/sdk-js/langchain.config.js @@ -11,7 +11,14 @@ function abs(relativePath) { export const config = { internals: [/react/], - entrypoints: { index: "index", client: "client", react: "react/index" }, + entrypoints: { + index: "index", + client: "client", + react: "react/index", + "react-ui/client": "react-ui/client", + "react-ui/server": "react-ui/server", + "react-ui/types": "react-ui/types", + }, tsConfigPath: resolve("./tsconfig.json"), cjsSource: "./dist-cjs", cjsDestination: "./dist", diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index 84f9b4eb9..264fb30ac 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -29,7 +29,8 @@ "@types/jest": "^29.5.12", "@types/node": "^20.12.12", "@types/uuid": "^9.0.1", - "@types/react": "18.3.2", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", "concat-md": "^0.5.1", "jest": "^29.7.0", "prettier": "^3.2.5", @@ -37,7 +38,8 @@ "typedoc": "^0.27.7", "typedoc-plugin-markdown": "^4.4.2", "typescript": "^5.4.5", - "react": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" }, "peerDependencies": { "react": "^18 || ^19", @@ -79,6 +81,33 @@ "import": "./react.js", "require": "./react.cjs" }, + "./react-ui/client": { + "types": { + "import": "./react-ui/client.d.ts", + "require": "./react-ui/client.d.cts", + "default": "./react-ui/client.d.ts" + }, + "import": "./react-ui/client.js", + "require": "./react-ui/client.cjs" + }, + "./react-ui/server": { + "types": { + "import": "./react-ui/server.d.ts", + "require": "./react-ui/server.d.cts", + "default": "./react-ui/server.d.ts" + }, + "import": "./react-ui/server.js", + "require": "./react-ui/server.cjs" + }, + "./react-ui/types": { + "types": { + "import": "./react-ui/types.d.ts", + "require": "./react-ui/types.d.cts", + "default": "./react-ui/types.d.ts" + }, + "import": "./react-ui/types.js", + "require": "./react-ui/types.cjs" + }, "./package.json": "./package.json" }, "files": [ @@ -94,6 +123,18 @@ "react.cjs", "react.js", "react.d.ts", - "react.d.cts" + "react.d.cts", + "react-ui/client.cjs", + "react-ui/client.js", + "react-ui/client.d.ts", + "react-ui/client.d.cts", + "react-ui/server.cjs", + "react-ui/server.js", + "react-ui/server.d.ts", + "react-ui/server.d.cts", + "react-ui/types.cjs", + "react-ui/types.js", + "react-ui/types.d.ts", + "react-ui/types.d.cts" ] } diff --git a/libs/sdk-js/src/react-ui/client.tsx b/libs/sdk-js/src/react-ui/client.tsx new file mode 100644 index 000000000..d30ca1615 --- /dev/null +++ b/libs/sdk-js/src/react-ui/client.tsx @@ -0,0 +1,132 @@ +import type { useStream } from "../react/index.js"; +import type { UIMessage } from "./types.js"; + +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import * as JsxRuntime from "react/jsx-runtime"; + +const UseStreamContext = React.createContext>( + null!, +); + +interface ComponentTarget { + comp: React.FunctionComponent | React.ComponentClass; + target: HTMLElement; +} + +class ComponentStore { + private cache: Record = {}; + private boundCache: Record< + string, + { + subscribe: (onStoreChange: () => void) => () => void; + getSnapshot: () => ComponentTarget | undefined; + } + > = {}; + private callbacks: Record< + string, + (( + comp: React.FunctionComponent | React.ComponentClass, + el: HTMLElement, + ) => void)[] + > = {}; + + respond( + name: string, + comp: React.FunctionComponent | React.ComponentClass, + targetElement: HTMLElement, + ) { + this.cache[name] = { comp, target: targetElement }; + this.callbacks[name]?.forEach((c) => c(comp, targetElement)); + } + + getBoundStore(name: string) { + this.boundCache[name] ??= { + subscribe: (onStoreChange: () => void) => { + this.callbacks[name] ??= []; + this.callbacks[name].push(onStoreChange); + return () => { + this.callbacks[name] = this.callbacks[name].filter( + (c) => c !== onStoreChange, + ); + }; + }, + getSnapshot: () => this.cache[name], + }; + + return this.boundCache[name]; + } +} + +const COMPONENT_STORE = new ComponentStore(); +const EXT_STORE_SYMBOL = Symbol.for("LGUI_EXT_STORE"); +const REQUIRE_SYMBOL = Symbol.for("LGUI_REQUIRE"); + +export function LoadExternalComponent({ + stream, + message, + className, +}: { + stream: ReturnType; + message: UIMessage; + className?: string; +}) { + const ref = React.useRef(null); + const id = React.useId(); + const shadowRootId = `child-shadow-${id}`; + + const store = React.useMemo( + () => COMPONENT_STORE.getBoundStore(shadowRootId), + [shadowRootId], + ); + const state = React.useSyncExternalStore(store.subscribe, store.getSnapshot); + + React.useEffect(() => { + fetch("http://localhost:3123", { + headers: { "Content-Type": "application/json" }, + method: "POST", + body: JSON.stringify({ name: message.name, shadowRootId }), + }) + .then((a) => a.text()) + .then((html) => { + const dom = ref.current; + if (!dom) return; + const root = dom.shadowRoot ?? dom.attachShadow({ mode: "open" }); + const fragment = document.createRange().createContextualFragment(html); + root.appendChild(fragment); + }); + }, [message.name, shadowRootId]); + + return ( + <> +
+ + + {state?.target && + ReactDOM.createPortal( + React.createElement(state.comp, message.content), + state.target, + )} + + + ); +} + +declare global { + interface Window { + [EXT_STORE_SYMBOL]: ComponentStore; + [REQUIRE_SYMBOL]: (name: string) => unknown; + } +} + +window[EXT_STORE_SYMBOL] = COMPONENT_STORE; +window[REQUIRE_SYMBOL] = (name: string) => { + if (name === "react") return React; + if (name === "react-dom") return ReactDOM; + if (name === "react/jsx-runtime") return JsxRuntime; + if (name === "@langchain/langgraph-sdk/react") { + return { useStream: () => React.useContext(UseStreamContext) }; + } + + throw new Error(`Unknown module...: ${name}`); +}; diff --git a/libs/sdk-js/src/react-ui/server.ts b/libs/sdk-js/src/react-ui/server.ts new file mode 100644 index 000000000..b40fbb964 --- /dev/null +++ b/libs/sdk-js/src/react-ui/server.ts @@ -0,0 +1,49 @@ +import { v4 as uuidv4 } from "uuid"; +import type { ComponentPropsWithoutRef, ElementType } from "react"; +import type { RemoveUIMessage, UIMessage } from "./types.js"; + +export const typedUi = >(config: { + writer?: (chunk: unknown) => void; + runId?: string; + metadata?: Record; + tags?: string[]; + runName?: string; +}) => { + type PropMap = { [K in keyof Decl]: ComponentPropsWithoutRef }; + let collect: unknown[] = []; + + const runId = (config.metadata?.run_id as string | undefined) ?? config.runId; + if (!runId) throw new Error("run_id is required"); + + const metadata = { + ...config.metadata, + tags: config.tags, + name: config.runName, + run_id: runId, + }; + + const create = ( + name: K, + props: PropMap[K], + ): UIMessage => ({ + type: "ui" as const, + id: uuidv4(), + name, + content: props, + additional_kwargs: metadata, + }); + + const remove = (id: string): RemoveUIMessage => ({ type: "remove-ui", id }); + + return { + create, + remove, + + collect, + write: (name: K, props: PropMap[K]) => { + const evt: UIMessage = create(name, props); + collect.push(evt); + config.writer?.(evt); + }, + }; +}; diff --git a/libs/sdk-js/src/react-ui/types.ts b/libs/sdk-js/src/react-ui/types.ts new file mode 100644 index 000000000..a0cb6279a --- /dev/null +++ b/libs/sdk-js/src/react-ui/types.ts @@ -0,0 +1,40 @@ +export interface UIMessage { + type: "ui"; + + id: string; + name: string; + content: Record; + additional_kwargs: { + run_id: string; + [key: string]: unknown; + }; +} + +export interface RemoveUIMessage { + type: "remove-ui"; + id: string; +} + +export function uiMessageReducer( + state: UIMessage[], + update: UIMessage | RemoveUIMessage | (UIMessage | RemoveUIMessage)[], +) { + const events = Array.isArray(update) ? update : [update]; + let newState = state.slice(); + + for (const event of events) { + if (event.type === "remove-ui") { + newState = newState.filter((ui) => ui.id !== event.id); + continue; + } + + const index = state.findIndex((ui) => ui.id === event.id); + if (index !== -1) { + newState[index] = event; + } else { + newState.push(event); + } + } + + return newState; +} diff --git a/libs/sdk-js/yarn.lock b/libs/sdk-js/yarn.lock index 635f0c5f3..68a384289 100644 --- a/libs/sdk-js/yarn.lock +++ b/libs/sdk-js/yarn.lock @@ -1005,17 +1005,16 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== -"@types/prop-types@*": - version "15.7.14" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.14.tgz#1433419d73b2a7ebfc6918dcefd2ec0d5cd698f2" - integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ== +"@types/react-dom@^19.0.3": + version "19.0.3" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.3.tgz#0804dfd279a165d5a0ad8b53a5b9e65f338050a4" + integrity sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA== -"@types/react@18.3.2": - version "18.3.2" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.2.tgz#462ae4904973bc212fa910424d901e3d137dbfcd" - integrity sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w== +"@types/react@^19.0.8": + version "19.0.9" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.9.tgz#675255eb7d978bdaf71f9d08f6b740d41b0b7f32" + integrity sha512-FedNTYgmMwSZmD1Sru/W1gJKuiYCN/3SuBkmZkcxX+FpO5zL76B22A9YNfAKg4HQO3Neh/30AiynP6BELdU0qQ== dependencies: - "@types/prop-types" "*" csstype "^3.0.2" "@types/retry@0.12.0": @@ -2896,7 +2895,7 @@ js-tiktoken@^1.0.12: dependencies: base64-js "^1.5.1" -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: +js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== @@ -2991,13 +2990,6 @@ longest-streak@^2.0.0: resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== -loose-envify@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" @@ -3640,17 +3632,22 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +react-dom@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57" + integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ== + dependencies: + scheduler "^0.25.0" + react-is@^18.0.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -react@^18.3.1: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" - integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== - dependencies: - loose-envify "^1.1.0" +react@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd" + integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ== read-pkg-up@^7.0.1: version "7.0.1" @@ -3840,6 +3837,11 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" +scheduler@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0.tgz#336cd9768e8cceebf52d3c80e3dcf5de23e7e015" + integrity sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA== + "semver@2 || 3 || 4 || 5": version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" From b73b34ddd5671c51b7bd6e8a8d5838aa31afa24f Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Tue, 18 Feb 2025 14:56:17 +0100 Subject: [PATCH 03/17] Add apiUrl, assistantId to props --- libs/sdk-js/src/react-ui/client.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/sdk-js/src/react-ui/client.tsx b/libs/sdk-js/src/react-ui/client.tsx index d30ca1615..17799c4e0 100644 --- a/libs/sdk-js/src/react-ui/client.tsx +++ b/libs/sdk-js/src/react-ui/client.tsx @@ -63,10 +63,14 @@ const EXT_STORE_SYMBOL = Symbol.for("LGUI_EXT_STORE"); const REQUIRE_SYMBOL = Symbol.for("LGUI_REQUIRE"); export function LoadExternalComponent({ + apiUrl = "http://localhost:2024", + assistantId, stream, message, className, }: { + apiUrl?: string; + assistantId: string; stream: ReturnType; message: UIMessage; className?: string; @@ -82,7 +86,7 @@ export function LoadExternalComponent({ const state = React.useSyncExternalStore(store.subscribe, store.getSnapshot); React.useEffect(() => { - fetch("http://localhost:3123", { + fetch(`${apiUrl}/ui/${assistantId}`, { headers: { "Content-Type": "application/json" }, method: "POST", body: JSON.stringify({ name: message.name, shadowRootId }), @@ -95,7 +99,7 @@ export function LoadExternalComponent({ const fragment = document.createRange().createContextualFragment(html); root.appendChild(fragment); }); - }, [message.name, shadowRootId]); + }, [apiUrl, assistantId, message.name, shadowRootId]); return ( <> From 5a7d384e2fa78461465d0e0ca03338defc986ffd Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Mon, 3 Mar 2025 19:04:58 +0100 Subject: [PATCH 04/17] Bump to 0.0.46-experimental.0 --- libs/sdk-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index 264fb30ac..bf06d4e62 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/langgraph-sdk", - "version": "0.0.46", + "version": "0.0.46-experimental.0", "description": "Client library for interacting with the LangGraph API", "type": "module", "packageManager": "yarn@1.22.19", From f49856af0b6506f0dc9f734d33671c37a07c8640 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 5 Mar 2025 00:37:51 +0100 Subject: [PATCH 05/17] Fix types for `collect` --- libs/sdk-js/src/react-ui/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/sdk-js/src/react-ui/server.ts b/libs/sdk-js/src/react-ui/server.ts index b40fbb964..759098c66 100644 --- a/libs/sdk-js/src/react-ui/server.ts +++ b/libs/sdk-js/src/react-ui/server.ts @@ -10,7 +10,7 @@ export const typedUi = >(config: { runName?: string; }) => { type PropMap = { [K in keyof Decl]: ComponentPropsWithoutRef }; - let collect: unknown[] = []; + let collect: (UIMessage | RemoveUIMessage)[] = []; const runId = (config.metadata?.run_id as string | undefined) ?? config.runId; if (!runId) throw new Error("run_id is required"); From 7e060f88a27db8737ad9f2b7d504864af60ead02 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 5 Mar 2025 16:14:42 +0100 Subject: [PATCH 06/17] Introduce useStreamContext --- libs/sdk-js/src/react-ui/client.tsx | 50 +++++++++++++++++++++++++---- libs/sdk-js/src/react/stream.tsx | 2 +- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/libs/sdk-js/src/react-ui/client.tsx b/libs/sdk-js/src/react-ui/client.tsx index 17799c4e0..38c2e9525 100644 --- a/libs/sdk-js/src/react-ui/client.tsx +++ b/libs/sdk-js/src/react-ui/client.tsx @@ -1,13 +1,46 @@ -import type { useStream } from "../react/index.js"; +import { useStream } from "../react/index.js"; import type { UIMessage } from "./types.js"; import * as React from "react"; import * as ReactDOM from "react-dom"; import * as JsxRuntime from "react/jsx-runtime"; +import type { UseStream } from "../react/stream.js"; -const UseStreamContext = React.createContext>( - null!, -); +const UseStreamContext = React.createContext<{ + stream: ReturnType; + meta: unknown; +}>(null!); + +type BagTemplate = { + ConfigurableType?: Record; + InterruptType?: unknown; + CustomEventType?: unknown; + UpdateType?: unknown; + MetaType?: unknown; +}; + +type GetMetaType = Bag extends { MetaType: unknown } + ? Bag["MetaType"] + : unknown; + +type UseStreamContext< + StateType extends Record = Record, + Bag extends BagTemplate = BagTemplate, +> = { stream: UseStream; meta: GetMetaType }; + +export function useStreamContext< + StateType extends Record = Record, + Bag extends { + ConfigurableType?: Record; + InterruptType?: unknown; + CustomEventType?: unknown; + UpdateType?: unknown; + MetaType?: unknown; + } = BagTemplate, +>(): UseStreamContext { + const ctx = React.useContext(UseStreamContext); + return ctx as UseStreamContext; +} interface ComponentTarget { comp: React.FunctionComponent | React.ComponentClass; @@ -68,12 +101,14 @@ export function LoadExternalComponent({ stream, message, className, + meta, }: { apiUrl?: string; assistantId: string; stream: ReturnType; message: UIMessage; className?: string; + meta?: unknown; }) { const ref = React.useRef(null); const id = React.useId(); @@ -105,7 +140,7 @@ export function LoadExternalComponent({ <>
- + {state?.target && ReactDOM.createPortal( React.createElement(state.comp, message.content), @@ -128,8 +163,9 @@ window[REQUIRE_SYMBOL] = (name: string) => { if (name === "react") return React; if (name === "react-dom") return ReactDOM; if (name === "react/jsx-runtime") return JsxRuntime; - if (name === "@langchain/langgraph-sdk/react") { - return { useStream: () => React.useContext(UseStreamContext) }; + if (name === "@langchain/langgraph-sdk/react") return { useStream }; + if (name === "@langchain/langgraph-sdk/react-ui/client") { + return { useStreamContext }; } throw new Error(`Unknown module...: ${name}`); diff --git a/libs/sdk-js/src/react/stream.tsx b/libs/sdk-js/src/react/stream.tsx index abe29ca5c..eb56f2284 100644 --- a/libs/sdk-js/src/react/stream.tsx +++ b/libs/sdk-js/src/react/stream.tsx @@ -482,7 +482,7 @@ interface UseStreamOptions< onThreadId?: (threadId: string) => void; } -interface UseStream< +export interface UseStream< StateType extends Record = Record, Bag extends BagTemplate = BagTemplate, > { From 4ee863f30ddd815bfec776c9cd01feeebc1776bd Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 5 Mar 2025 16:14:58 +0100 Subject: [PATCH 07/17] Reexport as @langchain/langgraph-sdk/react-ui --- libs/sdk-js/langchain.config.js | 2 +- libs/sdk-js/src/react-ui/index.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 libs/sdk-js/src/react-ui/index.ts diff --git a/libs/sdk-js/langchain.config.js b/libs/sdk-js/langchain.config.js index c404437f3..d2853bfa5 100644 --- a/libs/sdk-js/langchain.config.js +++ b/libs/sdk-js/langchain.config.js @@ -15,7 +15,7 @@ export const config = { index: "index", client: "client", react: "react/index", - "react-ui/client": "react-ui/client", + "react-ui": "react-ui/index", "react-ui/server": "react-ui/server", "react-ui/types": "react-ui/types", }, diff --git a/libs/sdk-js/src/react-ui/index.ts b/libs/sdk-js/src/react-ui/index.ts new file mode 100644 index 000000000..f2728822f --- /dev/null +++ b/libs/sdk-js/src/react-ui/index.ts @@ -0,0 +1,2 @@ +export { useStreamContext, LoadExternalComponent } from "./client.js"; +export type { UIMessage, RemoveUIMessage } from "./types.js"; From 677fd3ce28046da617de1cdeda0d0b6e2d5e0b31 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 5 Mar 2025 16:15:34 +0100 Subject: [PATCH 08/17] Update entrypoint --- libs/sdk-js/.gitignore | 8 ++++---- libs/sdk-js/package.json | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/libs/sdk-js/.gitignore b/libs/sdk-js/.gitignore index 404cdf0b5..16d769abe 100644 --- a/libs/sdk-js/.gitignore +++ b/libs/sdk-js/.gitignore @@ -10,10 +10,10 @@ react.cjs react.js react.d.ts react.d.cts -react-ui/client.cjs -react-ui/client.js -react-ui/client.d.ts -react-ui/client.d.cts +react-ui.cjs +react-ui.js +react-ui.d.ts +react-ui.d.cts react-ui/server.cjs react-ui/server.js react-ui/server.d.ts diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index bf06d4e62..ef016ee43 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -81,14 +81,14 @@ "import": "./react.js", "require": "./react.cjs" }, - "./react-ui/client": { + "./react-ui": { "types": { - "import": "./react-ui/client.d.ts", - "require": "./react-ui/client.d.cts", - "default": "./react-ui/client.d.ts" + "import": "./react-ui.d.ts", + "require": "./react-ui.d.cts", + "default": "./react-ui.d.ts" }, - "import": "./react-ui/client.js", - "require": "./react-ui/client.cjs" + "import": "./react-ui.js", + "require": "./react-ui.cjs" }, "./react-ui/server": { "types": { @@ -124,10 +124,10 @@ "react.js", "react.d.ts", "react.d.cts", - "react-ui/client.cjs", - "react-ui/client.js", - "react-ui/client.d.ts", - "react-ui/client.d.cts", + "react-ui.cjs", + "react-ui.js", + "react-ui.d.ts", + "react-ui.d.cts", "react-ui/server.cjs", "react-ui/server.js", "react-ui/server.d.ts", From 79de3dbad3acdd830b267200db2fd49730d35272 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 5 Mar 2025 16:19:28 +0100 Subject: [PATCH 09/17] Fix require symbol --- libs/sdk-js/src/react-ui/client.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libs/sdk-js/src/react-ui/client.tsx b/libs/sdk-js/src/react-ui/client.tsx index 38c2e9525..62ba7553e 100644 --- a/libs/sdk-js/src/react-ui/client.tsx +++ b/libs/sdk-js/src/react-ui/client.tsx @@ -164,8 +164,13 @@ window[REQUIRE_SYMBOL] = (name: string) => { if (name === "react-dom") return ReactDOM; if (name === "react/jsx-runtime") return JsxRuntime; if (name === "@langchain/langgraph-sdk/react") return { useStream }; - if (name === "@langchain/langgraph-sdk/react-ui/client") { - return { useStreamContext }; + if (name === "@langchain/langgraph-sdk/react-ui") { + return { + useStreamContext, + LoadExternalComponent: () => { + throw new Error("Nesting LoadExternalComponent is not supported"); + }, + }; } throw new Error(`Unknown module...: ${name}`); From b37f894f384baff173eefc1d2c0049c9a0cff833 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 5 Mar 2025 16:21:38 +0100 Subject: [PATCH 10/17] Stabilise boostrapping UI context --- libs/sdk-js/src/react-ui/client.tsx | 40 ++++++++++++++++++----------- libs/sdk-js/src/react-ui/index.ts | 3 +++ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/libs/sdk-js/src/react-ui/client.tsx b/libs/sdk-js/src/react-ui/client.tsx index 62ba7553e..eaf2ce8f3 100644 --- a/libs/sdk-js/src/react-ui/client.tsx +++ b/libs/sdk-js/src/react-ui/client.tsx @@ -158,20 +158,30 @@ declare global { } } -window[EXT_STORE_SYMBOL] = COMPONENT_STORE; -window[REQUIRE_SYMBOL] = (name: string) => { - if (name === "react") return React; - if (name === "react-dom") return ReactDOM; - if (name === "react/jsx-runtime") return JsxRuntime; - if (name === "@langchain/langgraph-sdk/react") return { useStream }; - if (name === "@langchain/langgraph-sdk/react-ui") { - return { - useStreamContext, - LoadExternalComponent: () => { - throw new Error("Nesting LoadExternalComponent is not supported"); - }, - }; +export function bootstrapUiContext() { + if (typeof window === "undefined") { + console.warn( + "Attempting to bootstrap UI context outside of browser environment. " + + "Avoid importing from `@langchain/langgraph-sdk/react-ui` in server context.", + ); + return; } - throw new Error(`Unknown module...: ${name}`); -}; + window[EXT_STORE_SYMBOL] = COMPONENT_STORE; + window[REQUIRE_SYMBOL] = (name: string) => { + if (name === "react") return React; + if (name === "react-dom") return ReactDOM; + if (name === "react/jsx-runtime") return JsxRuntime; + if (name === "@langchain/langgraph-sdk/react") return { useStream }; + if (name === "@langchain/langgraph-sdk/react-ui") { + return { + useStreamContext, + LoadExternalComponent: () => { + throw new Error("Nesting LoadExternalComponent is not supported"); + }, + }; + } + + throw new Error(`Unknown module...: ${name}`); + }; +} diff --git a/libs/sdk-js/src/react-ui/index.ts b/libs/sdk-js/src/react-ui/index.ts index f2728822f..05de0860e 100644 --- a/libs/sdk-js/src/react-ui/index.ts +++ b/libs/sdk-js/src/react-ui/index.ts @@ -1,2 +1,5 @@ +import { bootstrapUiContext } from "./client.js"; +bootstrapUiContext(); + export { useStreamContext, LoadExternalComponent } from "./client.js"; export type { UIMessage, RemoveUIMessage } from "./types.js"; From 80331b88e0a7f253d2ffc8e7cfa2e0cb4714107c Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 5 Mar 2025 16:38:07 +0100 Subject: [PATCH 11/17] Remove a nesting level --- libs/sdk-js/src/react-ui/client.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/libs/sdk-js/src/react-ui/client.tsx b/libs/sdk-js/src/react-ui/client.tsx index eaf2ce8f3..af2e7f111 100644 --- a/libs/sdk-js/src/react-ui/client.tsx +++ b/libs/sdk-js/src/react-ui/client.tsx @@ -23,10 +23,12 @@ type GetMetaType = Bag extends { MetaType: unknown } ? Bag["MetaType"] : unknown; -type UseStreamContext< +interface UseStreamContext< StateType extends Record = Record, Bag extends BagTemplate = BagTemplate, -> = { stream: UseStream; meta: GetMetaType }; +> extends UseStream { + meta?: GetMetaType; +} export function useStreamContext< StateType extends Record = Record, @@ -39,7 +41,18 @@ export function useStreamContext< } = BagTemplate, >(): UseStreamContext { const ctx = React.useContext(UseStreamContext); - return ctx as UseStreamContext; + if (!ctx) { + throw new Error( + "useStreamContext must be used within a LoadExternalComponent", + ); + } + + return new Proxy(ctx, { + get(target, prop: keyof UseStreamContext) { + if (prop === "meta") return target.meta; + return target.stream[prop]; + }, + }) as unknown as UseStreamContext; } interface ComponentTarget { From fcf134a45245a2b8094b945928f5d9f4d9977f19 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 5 Mar 2025 16:47:48 +0100 Subject: [PATCH 12/17] Remove @langchain/langgraph-sdk/react-ui/types entrypoint --- libs/sdk-js/.gitignore | 4 ---- libs/sdk-js/langchain.config.js | 3 +-- libs/sdk-js/package.json | 15 +-------------- libs/sdk-js/src/react-ui/server/index.ts | 6 ++++++ libs/sdk-js/src/react-ui/{ => server}/server.ts | 2 +- 5 files changed, 9 insertions(+), 21 deletions(-) create mode 100644 libs/sdk-js/src/react-ui/server/index.ts rename libs/sdk-js/src/react-ui/{ => server}/server.ts (95%) diff --git a/libs/sdk-js/.gitignore b/libs/sdk-js/.gitignore index 16d769abe..128c6dc98 100644 --- a/libs/sdk-js/.gitignore +++ b/libs/sdk-js/.gitignore @@ -18,10 +18,6 @@ react-ui/server.cjs react-ui/server.js react-ui/server.d.ts react-ui/server.d.cts -react-ui/types.cjs -react-ui/types.js -react-ui/types.d.ts -react-ui/types.d.cts node_modules dist .yarn diff --git a/libs/sdk-js/langchain.config.js b/libs/sdk-js/langchain.config.js index d2853bfa5..21d796c87 100644 --- a/libs/sdk-js/langchain.config.js +++ b/libs/sdk-js/langchain.config.js @@ -16,8 +16,7 @@ export const config = { client: "client", react: "react/index", "react-ui": "react-ui/index", - "react-ui/server": "react-ui/server", - "react-ui/types": "react-ui/types", + "react-ui/server": "react-ui/server/index", }, tsConfigPath: resolve("./tsconfig.json"), cjsSource: "./dist-cjs", diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index ef016ee43..65f7cc70e 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -99,15 +99,6 @@ "import": "./react-ui/server.js", "require": "./react-ui/server.cjs" }, - "./react-ui/types": { - "types": { - "import": "./react-ui/types.d.ts", - "require": "./react-ui/types.d.cts", - "default": "./react-ui/types.d.ts" - }, - "import": "./react-ui/types.js", - "require": "./react-ui/types.cjs" - }, "./package.json": "./package.json" }, "files": [ @@ -131,10 +122,6 @@ "react-ui/server.cjs", "react-ui/server.js", "react-ui/server.d.ts", - "react-ui/server.d.cts", - "react-ui/types.cjs", - "react-ui/types.js", - "react-ui/types.d.ts", - "react-ui/types.d.cts" + "react-ui/server.d.cts" ] } diff --git a/libs/sdk-js/src/react-ui/server/index.ts b/libs/sdk-js/src/react-ui/server/index.ts new file mode 100644 index 000000000..45c2c1e78 --- /dev/null +++ b/libs/sdk-js/src/react-ui/server/index.ts @@ -0,0 +1,6 @@ +export { typedUi } from "./server.js"; +export { + uiMessageReducer, + type UIMessage, + type RemoveUIMessage, +} from "../types.js"; diff --git a/libs/sdk-js/src/react-ui/server.ts b/libs/sdk-js/src/react-ui/server/server.ts similarity index 95% rename from libs/sdk-js/src/react-ui/server.ts rename to libs/sdk-js/src/react-ui/server/server.ts index 759098c66..e62281484 100644 --- a/libs/sdk-js/src/react-ui/server.ts +++ b/libs/sdk-js/src/react-ui/server/server.ts @@ -1,6 +1,6 @@ import { v4 as uuidv4 } from "uuid"; import type { ComponentPropsWithoutRef, ElementType } from "react"; -import type { RemoveUIMessage, UIMessage } from "./types.js"; +import type { RemoveUIMessage, UIMessage } from "../types.js"; export const typedUi = >(config: { writer?: (chunk: unknown) => void; From f6781d19abc53e0a34b53482b43c5a6a2e26c43e Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 5 Mar 2025 16:48:29 +0100 Subject: [PATCH 13/17] Undo version experimental bump --- libs/sdk-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index 65f7cc70e..26cecc9fa 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/langgraph-sdk", - "version": "0.0.46-experimental.0", + "version": "0.0.46", "description": "Client library for interacting with the LangGraph API", "type": "module", "packageManager": "yarn@1.22.19", From 52627010cb2b4cc59841327dff67fbe8aea15816 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 5 Mar 2025 17:57:57 +0100 Subject: [PATCH 14/17] Cache promises --- libs/sdk-js/src/react-ui/client.tsx | 112 ++++++++++++++++++---------- 1 file changed, 74 insertions(+), 38 deletions(-) diff --git a/libs/sdk-js/src/react-ui/client.tsx b/libs/sdk-js/src/react-ui/client.tsx index af2e7f111..d2f633cdb 100644 --- a/libs/sdk-js/src/react-ui/client.tsx +++ b/libs/sdk-js/src/react-ui/client.tsx @@ -78,51 +78,88 @@ class ComponentStore { > = {}; respond( - name: string, + shadowRootId: string, comp: React.FunctionComponent | React.ComponentClass, targetElement: HTMLElement, ) { - this.cache[name] = { comp, target: targetElement }; - this.callbacks[name]?.forEach((c) => c(comp, targetElement)); + this.cache[shadowRootId] = { comp, target: targetElement }; + this.callbacks[shadowRootId]?.forEach((c) => c(comp, targetElement)); } - getBoundStore(name: string) { - this.boundCache[name] ??= { + getBoundStore(shadowRootId: string) { + this.boundCache[shadowRootId] ??= { subscribe: (onStoreChange: () => void) => { - this.callbacks[name] ??= []; - this.callbacks[name].push(onStoreChange); + this.callbacks[shadowRootId] ??= []; + this.callbacks[shadowRootId].push(onStoreChange); return () => { - this.callbacks[name] = this.callbacks[name].filter( + this.callbacks[shadowRootId] = this.callbacks[shadowRootId].filter( (c) => c !== onStoreChange, ); }; }, - getSnapshot: () => this.cache[name], + getSnapshot: () => this.cache[shadowRootId], }; - return this.boundCache[name]; + return this.boundCache[shadowRootId]; } } const COMPONENT_STORE = new ComponentStore(); +const COMPONENT_PROMISE_CACHE: Record | undefined> = {}; + const EXT_STORE_SYMBOL = Symbol.for("LGUI_EXT_STORE"); const REQUIRE_SYMBOL = Symbol.for("LGUI_REQUIRE"); +interface LoadExternalComponentProps + extends Pick, "style" | "className"> { + /** API URL of the LangGraph Platform */ + apiUrl?: string; + + /** ID of the assistant */ + assistantId: string; + + /** Stream of the assistant */ + stream: ReturnType; + + /** UI message to be rendered */ + message: UIMessage; + + /** Additional context to be passed to the child component */ + meta?: unknown; + + /** Fallback to be rendered when the component is loading */ + fallback?: React.ReactNode; +} + +function fetchComponent( + apiUrl: string, + assistantId: string, + agentName: string, +): Promise { + const cacheKey = `${apiUrl}-${assistantId}-${agentName}`; + if (COMPONENT_PROMISE_CACHE[cacheKey] != null) { + return COMPONENT_PROMISE_CACHE[cacheKey] as Promise; + } + + const request: Promise = fetch(`${apiUrl}/ui/${assistantId}`, { + headers: { Accept: "text/html", "Content-Type": "application/json" }, + method: "POST", + body: JSON.stringify({ name: agentName }), + }).then((a) => a.text()); + + COMPONENT_PROMISE_CACHE[cacheKey] = request; + return request; +} + export function LoadExternalComponent({ apiUrl = "http://localhost:2024", assistantId, stream, message, - className, meta, -}: { - apiUrl?: string; - assistantId: string; - stream: ReturnType; - message: UIMessage; - className?: string; - meta?: unknown; -}) { + fallback, + ...props +}: LoadExternalComponentProps) { const ref = React.useRef(null); const id = React.useId(); const shadowRootId = `child-shadow-${id}`; @@ -134,31 +171,30 @@ export function LoadExternalComponent({ const state = React.useSyncExternalStore(store.subscribe, store.getSnapshot); React.useEffect(() => { - fetch(`${apiUrl}/ui/${assistantId}`, { - headers: { "Content-Type": "application/json" }, - method: "POST", - body: JSON.stringify({ name: message.name, shadowRootId }), - }) - .then((a) => a.text()) - .then((html) => { - const dom = ref.current; - if (!dom) return; - const root = dom.shadowRoot ?? dom.attachShadow({ mode: "open" }); - const fragment = document.createRange().createContextualFragment(html); - root.appendChild(fragment); - }); + fetchComponent(apiUrl, assistantId, message.name).then((html) => { + const dom = ref.current; + if (!dom) return; + const root = dom.shadowRoot ?? dom.attachShadow({ mode: "open" }); + const fragment = document + .createRange() + .createContextualFragment( + html.replace("{{shadowRootId}}", shadowRootId), + ); + root.appendChild(fragment); + }); }, [apiUrl, assistantId, message.name, shadowRootId]); return ( <> -
+
- {state?.target && - ReactDOM.createPortal( - React.createElement(state.comp, message.content), - state.target, - )} + {state?.target != null + ? ReactDOM.createPortal( + React.createElement(state.comp, message.content), + state.target, + ) + : fallback} ); From 36bbe059df6c44567992309d62f96f65ee6c5f8b Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 5 Mar 2025 21:21:13 +0100 Subject: [PATCH 15/17] Bump to 0.0.47-experimental.0 --- libs/sdk-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index 26cecc9fa..c686388cc 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/langgraph-sdk", - "version": "0.0.46", + "version": "0.0.47-experimental.0", "description": "Client library for interacting with the LangGraph API", "type": "module", "packageManager": "yarn@1.22.19", From 40062c40df3004f9cbc84584b8c3a4de4a350c40 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Thu, 6 Mar 2025 00:39:21 +0100 Subject: [PATCH 16/17] Add fallback for components defined at client level --- libs/sdk-js/src/react-ui/client.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/libs/sdk-js/src/react-ui/client.tsx b/libs/sdk-js/src/react-ui/client.tsx index d2f633cdb..f30f442d5 100644 --- a/libs/sdk-js/src/react-ui/client.tsx +++ b/libs/sdk-js/src/react-ui/client.tsx @@ -129,6 +129,12 @@ interface LoadExternalComponentProps /** Fallback to be rendered when the component is loading */ fallback?: React.ReactNode; + + /** + * Map of components that can be rendered directly without fetching the UI code + * from the server. + */ + components?: Record; } function fetchComponent( @@ -158,6 +164,7 @@ export function LoadExternalComponent({ message, meta, fallback, + components, ...props }: LoadExternalComponentProps) { const ref = React.useRef(null); @@ -170,7 +177,11 @@ export function LoadExternalComponent({ ); const state = React.useSyncExternalStore(store.subscribe, store.getSnapshot); + const clientComponent = components?.[message.name]; + const hasClientComponent = clientComponent != null; + React.useEffect(() => { + if (hasClientComponent) return; fetchComponent(apiUrl, assistantId, message.name).then((html) => { const dom = ref.current; if (!dom) return; @@ -182,7 +193,11 @@ export function LoadExternalComponent({ ); root.appendChild(fragment); }); - }, [apiUrl, assistantId, message.name, shadowRootId]); + }, [apiUrl, assistantId, message.name, shadowRootId, hasClientComponent]); + + if (hasClientComponent) { + return React.createElement(clientComponent, message.content); + } return ( <> From 9d0186b5bfb3614151fe8dfd426dfe5504cf751b Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Thu, 6 Mar 2025 17:03:47 +0100 Subject: [PATCH 17/17] Bump to 0.0.47 --- libs/sdk-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index c686388cc..0c84c0676 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/langgraph-sdk", - "version": "0.0.47-experimental.0", + "version": "0.0.47", "description": "Client library for interacting with the LangGraph API", "type": "module", "packageManager": "yarn@1.22.19",