diff --git a/libs/sdk-js/src/client.ts b/libs/sdk-js/src/client.ts index c4ffcb7ed..0406ae4a1 100644 --- a/libs/sdk-js/src/client.ts +++ b/libs/sdk-js/src/client.ts @@ -68,7 +68,7 @@ export function getApiKey(apiKey?: string): string | undefined { return undefined; } -interface ClientConfig { +export interface ClientConfig { apiUrl?: string; apiKey?: string; callerOptions?: AsyncCallerParams; diff --git a/libs/sdk-js/src/react/stream.tsx b/libs/sdk-js/src/react/stream.tsx index 01a7811be..0f229da40 100644 --- a/libs/sdk-js/src/react/stream.tsx +++ b/libs/sdk-js/src/react/stream.tsx @@ -1,7 +1,7 @@ /* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */ "use client"; -import { Client, ClientConfig } from "../client.js"; +import { Client, type ClientConfig } from "../client.js"; import type { Command, DisconnectMode, @@ -188,15 +188,16 @@ export function useStream< apiUrl: ClientConfig["apiUrl"]; apiKey?: ClientConfig["apiKey"]; - callerOptions?: ClientConfig["callerOptions"]; - timeoutMs?: ClientConfig["timeoutMs"]; - defaultHeaders?: ClientConfig["defaultHeaders"]; withMessages?: string; onError?: (error: unknown) => void; onFinish?: (state: ThreadState) => void; + onUpdateEvent?: (data: UpdatesStreamEvent["data"]) => void; + onCustomEvent?: (data: CustomStreamEvent["data"]) => void; + onMetadataEvent?: (data: MetadataStreamEvent["data"]) => void; + // TODO: can we make threadId uncontrollable / controllable? threadId?: string | null; onThreadId?: (threadId: string) => void; @@ -214,26 +215,14 @@ export function useStream< | FeedbackStreamEvent; const { assistantId, threadId, withMessages, onError, onFinish } = options; - const [client] = useState( - () => - new Client({ - apiUrl: options.apiUrl, - apiKey: options.apiKey, - callerOptions: options.callerOptions, - timeoutMs: options.timeoutMs, - defaultHeaders: options.defaultHeaders, - }), + const client = useMemo( + () => new Client({ apiUrl: options.apiUrl, apiKey: options.apiKey }), + [options.apiKey, options.apiUrl], ); - if (client == null) { - throw new Error( - "LangGraph SDK not provided. Either pass a client to `useStream` or wrap your app in a `LangGraphConfig` provider and pass the client there.", - ); - } - const [branchPath, setBranchPath] = useState([]); const [isLoading, setIsLoading] = useState(false); - const [events, setEvents] = useState([]); + const [_, setEvents] = useState([]); const [streamError, setStreamError] = useState(undefined); const [streamValues, setStreamValues] = useState(null); @@ -254,6 +243,16 @@ export function useStream< [], ); + const hasUpdateListener = options.onUpdateEvent != null; + const hasCustomListener = options.onCustomEvent != null; + + const callbackStreamMode = useMemo(() => { + const modes: Exclude[] = []; + if (hasUpdateListener) modes.push("updates"); + if (hasCustomListener) modes.push("custom"); + return modes; + }, [hasUpdateListener, hasCustomListener]); + const clearCallbackRef = useRef<() => void>(null!); clearCallbackRef.current = () => { setStreamError(undefined); @@ -480,6 +479,7 @@ export function useStream< const streamMode = unique([ ...(submitOptions?.streamMode ?? []), ...trackStreamModeRef.current, + ...callbackStreamMode, ]); const checkpoint = @@ -499,8 +499,6 @@ export function useStream< onCompletion: submitOptions?.onCompletion, onDisconnect: submitOptions?.onDisconnect ?? "cancel", - // TODO: check if integration on FE would work nice - feedbackKeys: submitOptions?.feedbackKeys, signal: abortRef.current.signal, checkpoint, @@ -539,6 +537,18 @@ export function useStream< break; } + if (event === "updates") { + options.onUpdateEvent?.(data); + } + + if (event === "custom") { + options.onCustomEvent?.(data); + } + + if (event === "metadata") { + options.onMetadataEvent?.(data); + } + if (event === "values") { setStreamValues(data); } @@ -604,30 +614,6 @@ export function useStream< const error = isLoading ? streamError : historyError; const values = streamValues ?? historyValues; - const stream = { - get custom() { - trackStreamMode("custom"); - - return events - .filter((item) => item.event === "custom") - .map(({ data }) => data as CustomType); - }, - - get events() { - trackStreamMode("events"); - return events; - }, - - get updates() { - trackStreamMode("updates"); - return events - .filter( - (item): item is UpdatesStreamEvent => - item.event === "updates", - ) - .map(({ data }) => data); - }, - }; const setBranch = useCallback( (path: string) => setBranchPath(path.split(">")), @@ -647,8 +633,6 @@ export function useStream< submit, setBranch, - stream, - get messages() { trackStreamMode("messages-tuple");