feat(sdk): add isThreadLoading option to useStream, handle thread error fetching (#1466)

This commit is contained in:
David Duong
2025-07-29 21:14:22 +02:00
committed by GitHub
parent f43e48ce06
commit af9ec5aa36
2 changed files with 55 additions and 14 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@langchain/langgraph-sdk": patch
---
feat(sdk): add `isThreadLoading` option to `useStream`, handle thread error fetching
+50 -14
View File
@@ -351,9 +351,14 @@ function useThreadHistory<StateType extends Record<string, unknown>>(
client: Client,
limit: boolean | number,
clearCallbackRef: RefObject<(() => void) | undefined>,
submittingRef: RefObject<boolean>
submittingRef: RefObject<boolean>,
onErrorRef: RefObject<((error: unknown) => void) | undefined>
) {
const [history, setHistory] = useState<ThreadState<StateType>[]>([]);
const [history, setHistory] = useState<ThreadState<StateType>[] | undefined>(
undefined
);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<unknown | undefined>(undefined);
const clientHash = getClientConfigHash(client);
const clientRef = useRef(client);
@@ -365,19 +370,35 @@ function useThreadHistory<StateType extends Record<string, unknown>>(
): Promise<ThreadState<StateType>[]> => {
if (threadId != null) {
const client = clientRef.current;
setIsLoading(true);
return fetchHistory<StateType>(client, threadId, {
limit,
}).then((history) => {
setHistory(history);
return history;
});
})
.then(
(history) => {
setHistory(history);
return history;
},
(error) => {
setError(error);
onErrorRef.current?.(error);
return Promise.reject(error);
}
)
.finally(() => {
setIsLoading(false);
});
}
setHistory([]);
setHistory(undefined);
setError(undefined);
setIsLoading(false);
clearCallbackRef.current?.();
return Promise.resolve([]);
},
[clearCallbackRef, limit]
[clearCallbackRef, onErrorRef, limit]
);
useEffect(() => {
@@ -387,6 +408,8 @@ function useThreadHistory<StateType extends Record<string, unknown>>(
return {
data: history,
isLoading,
error,
mutate: (mutateId?: string) => fetcher(mutateId ?? threadId),
};
}
@@ -642,6 +665,11 @@ export interface UseStream<
*/
isLoading: boolean;
/**
* Whether the thread is currently being loaded.
*/
isThreadLoading: boolean;
/**
* Stops the stream.
*/
@@ -936,6 +964,11 @@ export function useStream<
messageManagerRef.current.clear();
};
const onErrorRef = useRef<
((error: unknown, run?: RunCallbackMeta) => void) | undefined
>(undefined);
onErrorRef.current = options.onError;
const historyLimit =
typeof fetchStateHistory === "object" && fetchStateHistory != null
? fetchStateHistory.limit ?? true
@@ -946,7 +979,8 @@ export function useStream<
client,
historyLimit,
clearCallbackRef,
submittingRef
submittingRef,
onErrorRef
);
const getMessages = useMemo(() => {
@@ -956,7 +990,7 @@ export function useStream<
: [];
}, [messagesKey]);
const { rootSequence, paths } = getBranchSequence(history.data);
const { rootSequence, paths } = getBranchSequence(history.data ?? []);
const { history: flatHistory, branchByCheckpoint } = getBranchView(
rootSequence,
paths,
@@ -967,7 +1001,7 @@ export function useStream<
const historyValues =
threadHead?.values ?? options.initialValues ?? ({} as StateType);
const historyError = (() => {
const historyValueError = (() => {
const error = threadHead?.tasks?.at(-1)?.error;
if (error == null) return undefined;
try {
@@ -988,13 +1022,13 @@ export function useStream<
return getMessages(historyValues).map(
(message, idx): MessageMetadata<StateType> => {
const messageId = message.id ?? idx;
const firstSeenIdx = findLastIndex(history.data, (state) =>
const firstSeenIdx = findLastIndex(history.data ?? [], (state) =>
getMessages(state.values)
.map((m, idx) => m.id ?? idx)
.includes(messageId)
);
const firstSeen = history.data[firstSeenIdx] as
const firstSeen = history.data?.[firstSeenIdx] as
| ThreadState<StateType>
| undefined;
@@ -1300,7 +1334,7 @@ export function useStream<
}
}, [reconnectKey]);
const error = streamError ?? historyError;
const error = streamError ?? historyValueError ?? history.error;
const values = streamValues ?? historyValues;
return {
@@ -1324,6 +1358,8 @@ export function useStream<
setBranch,
history: flatHistory,
isThreadLoading: history.isLoading && history.data == null,
get experimental_branchTree() {
if (historyLimit === false) {
throw new Error(