diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index b08b6c924..09bd9604a 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/langgraph-sdk", - "version": "0.0.50", + "version": "0.0.51", "description": "Client library for interacting with the LangGraph API", "type": "module", "packageManager": "yarn@1.22.19", diff --git a/libs/sdk-js/src/react-ui/index.ts b/libs/sdk-js/src/react-ui/index.ts index 05de0860e..902adecee 100644 --- a/libs/sdk-js/src/react-ui/index.ts +++ b/libs/sdk-js/src/react-ui/index.ts @@ -2,4 +2,8 @@ import { bootstrapUiContext } from "./client.js"; bootstrapUiContext(); export { useStreamContext, LoadExternalComponent } from "./client.js"; -export type { UIMessage, RemoveUIMessage } from "./types.js"; +export { + uiMessageReducer, + type UIMessage, + type RemoveUIMessage, +} from "./types.js"; diff --git a/libs/sdk-js/src/react-ui/server/server.ts b/libs/sdk-js/src/react-ui/server/server.ts index e62281484..62263ae3c 100644 --- a/libs/sdk-js/src/react-ui/server/server.ts +++ b/libs/sdk-js/src/react-ui/server/server.ts @@ -2,6 +2,10 @@ import { v4 as uuidv4 } from "uuid"; import type { ComponentPropsWithoutRef, ElementType } from "react"; import type { RemoveUIMessage, UIMessage } from "../types.js"; +interface MessageLike { + id?: string; +} + export const typedUi = >(config: { writer?: (chunk: unknown) => void; runId?: string; @@ -10,7 +14,7 @@ export const typedUi = >(config: { runName?: string; }) => { type PropMap = { [K in keyof Decl]: ComponentPropsWithoutRef }; - let collect: (UIMessage | RemoveUIMessage)[] = []; + let items: (UIMessage | RemoveUIMessage)[] = []; const runId = (config.metadata?.run_id as string | undefined) ?? config.runId; if (!runId) throw new Error("run_id is required"); @@ -22,28 +26,37 @@ export const typedUi = >(config: { 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); + const handlePush = ( + message: { + id?: string; + name: K; + content: PropMap[K]; + additional_kwargs?: Record; }, + options?: { message?: MessageLike }, + ): UIMessage => { + const evt: UIMessage = { + type: "ui" as const, + id: message?.id ?? uuidv4(), + name: message?.name, + content: message?.content, + additional_kwargs: { + ...metadata, + ...message?.additional_kwargs, + ...(options?.message ? { message_id: options.message.id } : null), + }, + }; + items.push(evt); + config.writer?.(evt); + return evt; }; + + const handleDelete = (id: string): RemoveUIMessage => { + const evt: RemoveUIMessage = { type: "remove-ui", id }; + items.push(evt); + config.writer?.(evt); + return evt; + }; + + return { push: handlePush, delete: handleDelete, items }; }; diff --git a/libs/sdk-js/src/react-ui/types.ts b/libs/sdk-js/src/react-ui/types.ts index a0cb6279a..86f538e0c 100644 --- a/libs/sdk-js/src/react-ui/types.ts +++ b/libs/sdk-js/src/react-ui/types.ts @@ -6,6 +6,7 @@ export interface UIMessage { content: Record; additional_kwargs: { run_id: string; + message_id?: string; [key: string]: unknown; }; } diff --git a/libs/sdk-js/src/react/stream.tsx b/libs/sdk-js/src/react/stream.tsx index eb56f2284..773876f7e 100644 --- a/libs/sdk-js/src/react/stream.tsx +++ b/libs/sdk-js/src/react/stream.tsx @@ -464,6 +464,11 @@ interface UseStreamOptions< */ onCustomEvent?: ( data: CustomStreamEvent>["data"], + options: { + mutate: ( + update: Partial | ((prev: StateType) => Partial), + ) => void; + }, ) => void; /** @@ -834,7 +839,18 @@ export function useStream< } if (event === "updates") options.onUpdateEvent?.(data); - if (event === "custom") options.onCustomEvent?.(data); + if (event === "custom") + options.onCustomEvent?.(data, { + mutate: (update) => + setStreamValues((prev) => { + // should not happen + if (prev == null) return prev; + return { + ...prev, + ...(typeof update === "function" ? update(prev) : update), + }; + }), + }); if (event === "metadata") options.onMetadataEvent?.(data); if (event === "values") setStreamValues(data);