@langchain/react 0.2.0 → 0.2.1
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.
- package/README.md +22 -1
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/stream.cjs +5 -3
- package/dist/stream.cjs.map +1 -1
- package/dist/stream.d.cts.map +1 -1
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +5 -3
- package/dist/stream.js.map +1 -1
- package/dist/suspense-stream.cjs +32 -16
- package/dist/suspense-stream.cjs.map +1 -1
- package/dist/suspense-stream.d.cts +22 -3
- package/dist/suspense-stream.d.cts.map +1 -1
- package/dist/suspense-stream.d.ts +22 -3
- package/dist/suspense-stream.d.ts.map +1 -1
- package/dist/suspense-stream.js +32 -17
- package/dist/suspense-stream.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -142,7 +142,11 @@ function Chat() {
|
|
|
142
142
|
|
|
143
143
|
### Options
|
|
144
144
|
|
|
145
|
-
`useSuspenseStream` accepts the same options as `useStream` (LangGraph Platform mode)
|
|
145
|
+
`useSuspenseStream` accepts the same options as `useStream` (LangGraph Platform mode), plus:
|
|
146
|
+
|
|
147
|
+
| Option | Type | Description |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `suspenseCache` | `SuspenseCache` | Optional cache instance for Suspense history prefetching. Useful in tests to avoid cross-test cache sharing. |
|
|
146
150
|
|
|
147
151
|
### Return Values
|
|
148
152
|
|
|
@@ -212,6 +216,23 @@ import { invalidateSuspenseCache } from "@langchain/react";
|
|
|
212
216
|
</ErrorBoundary>
|
|
213
217
|
```
|
|
214
218
|
|
|
219
|
+
For test isolation, you can create and pass a dedicated cache instance:
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
import { createSuspenseCache, useSuspenseStream } from "@langchain/react";
|
|
223
|
+
|
|
224
|
+
const suspenseCache = createSuspenseCache();
|
|
225
|
+
|
|
226
|
+
function Chat() {
|
|
227
|
+
const stream = useSuspenseStream({
|
|
228
|
+
assistantId: "agent",
|
|
229
|
+
apiUrl: "http://localhost:2024",
|
|
230
|
+
suspenseCache,
|
|
231
|
+
});
|
|
232
|
+
// ...
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
215
236
|
## Type Safety
|
|
216
237
|
|
|
217
238
|
### With `createAgent`
|
package/dist/index.cjs
CHANGED
|
@@ -18,6 +18,7 @@ Object.defineProperty(exports, "calculateDepthFromNamespace", {
|
|
|
18
18
|
return _langchain_langgraph_sdk_ui.calculateDepthFromNamespace;
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
|
+
exports.createSuspenseCache = require_suspense_stream.createSuspenseCache;
|
|
21
22
|
Object.defineProperty(exports, "extractParentIdFromNamespace", {
|
|
22
23
|
enumerable: true,
|
|
23
24
|
get: function() {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SubagentStream, SubagentStreamInterface, UseStream, UseStreamCustom, UseSuspenseStream } from "./types.cjs";
|
|
2
2
|
import { ClassSubagentStreamInterface, useStream } from "./stream.cjs";
|
|
3
|
-
import { invalidateSuspenseCache, useSuspenseStream } from "./suspense-stream.cjs";
|
|
3
|
+
import { SuspenseCache, createSuspenseCache, invalidateSuspenseCache, useSuspenseStream } from "./suspense-stream.cjs";
|
|
4
4
|
import { FetchStreamTransport } from "./stream.custom.cjs";
|
|
5
5
|
import { StreamProvider, StreamProviderCustomProps, StreamProviderProps, useStreamContext } from "./context.cjs";
|
|
6
6
|
import { AIMessage, ToolMessage } from "@langchain/core/messages";
|
|
@@ -10,5 +10,5 @@ import { AgentTypeConfigLike, BaseStream, BaseSubagentState, CompiledSubAgentLik
|
|
|
10
10
|
//#region src/index.d.ts
|
|
11
11
|
type ToolCallWithResult<ToolCall = DefaultToolCall$1> = ToolCallWithResult$1<ToolCall, ToolMessage, AIMessage>;
|
|
12
12
|
//#endregion
|
|
13
|
-
export { type AgentTypeConfigLike, type BaseStream, type BaseSubagentState, type ClassSubagentStreamInterface, type CompiledSubAgentLike, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, FetchStreamTransport, type GetToolCallsType, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type IsAgentLike, type IsDeepAgentLike, type MessageMetadata, type QueueEntry, type QueueInterface, type ResolveStreamInterface, type ResolveStreamOptions, StreamProvider, type StreamProviderCustomProps, type StreamProviderProps, type SubAgentLike, SubagentManager, type SubagentStateMap, type SubagentStatus, type SubagentStream, type SubagentStreamInterface, type SubagentToolCall, type ToolCallFromTool, type ToolCallState, ToolCallWithResult, type ToolCallsFromTools, type UseAgentStream, type UseAgentStreamOptions, type UseDeepAgentStream, type UseDeepAgentStreamOptions, type UseStream, type UseStreamCustom, type UseStreamCustomOptions, type UseStreamOptions, type UseStreamThread, type UseStreamTransport, type UseSuspenseStream, calculateDepthFromNamespace, extractParentIdFromNamespace, extractToolCallIdFromNamespace, invalidateSuspenseCache, isSubagentNamespace, useStream, useStreamContext, useSuspenseStream };
|
|
13
|
+
export { type AgentTypeConfigLike, type BaseStream, type BaseSubagentState, type ClassSubagentStreamInterface, type CompiledSubAgentLike, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, FetchStreamTransport, type GetToolCallsType, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type IsAgentLike, type IsDeepAgentLike, type MessageMetadata, type QueueEntry, type QueueInterface, type ResolveStreamInterface, type ResolveStreamOptions, StreamProvider, type StreamProviderCustomProps, type StreamProviderProps, type SubAgentLike, SubagentManager, type SubagentStateMap, type SubagentStatus, type SubagentStream, type SubagentStreamInterface, type SubagentToolCall, type SuspenseCache, type ToolCallFromTool, type ToolCallState, ToolCallWithResult, type ToolCallsFromTools, type UseAgentStream, type UseAgentStreamOptions, type UseDeepAgentStream, type UseDeepAgentStreamOptions, type UseStream, type UseStreamCustom, type UseStreamCustomOptions, type UseStreamOptions, type UseStreamThread, type UseStreamTransport, type UseSuspenseStream, calculateDepthFromNamespace, createSuspenseCache, extractParentIdFromNamespace, extractToolCallIdFromNamespace, invalidateSuspenseCache, isSubagentNamespace, useStream, useStreamContext, useSuspenseStream };
|
|
14
14
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;KAkFY,kBAAA,YAA8B,iBAAA,IACxC,oBAAA,CAAoB,QAAA,EAAU,WAAA,EAAiB,SAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SubagentStream, SubagentStreamInterface, UseStream, UseStreamCustom, UseSuspenseStream } from "./types.js";
|
|
2
2
|
import { ClassSubagentStreamInterface, useStream } from "./stream.js";
|
|
3
|
-
import { invalidateSuspenseCache, useSuspenseStream } from "./suspense-stream.js";
|
|
3
|
+
import { SuspenseCache, createSuspenseCache, invalidateSuspenseCache, useSuspenseStream } from "./suspense-stream.js";
|
|
4
4
|
import { FetchStreamTransport } from "./stream.custom.js";
|
|
5
5
|
import { StreamProvider, StreamProviderCustomProps, StreamProviderProps, useStreamContext } from "./context.js";
|
|
6
6
|
import { AgentTypeConfigLike, BaseStream, BaseSubagentState, CompiledSubAgentLike, DeepAgentTypeConfigLike, DefaultSubagentStates, ExtractAgentConfig, ExtractDeepAgentConfig, ExtractSubAgentMiddleware, GetToolCallsType, InferAgentToolCalls, InferBag, InferDeepAgentSubagents, InferNodeNames, InferStateType, InferSubagentByName, InferSubagentNames, InferSubagentState, InferSubagentStates, InferToolCalls, IsAgentLike, IsDeepAgentLike, MessageMetadata, QueueEntry, QueueInterface, ResolveStreamInterface, ResolveStreamOptions, SubAgentLike, SubagentManager, SubagentStateMap, SubagentStatus, SubagentToolCall, UseAgentStream, UseAgentStreamOptions, UseDeepAgentStream, UseDeepAgentStreamOptions, UseStreamCustomOptions, UseStreamOptions, UseStreamThread, UseStreamTransport, calculateDepthFromNamespace, extractParentIdFromNamespace, extractToolCallIdFromNamespace, isSubagentNamespace } from "@langchain/langgraph-sdk/ui";
|
|
@@ -10,5 +10,5 @@ import { DefaultToolCall, DefaultToolCall as DefaultToolCall$1, ToolCallFromTool
|
|
|
10
10
|
//#region src/index.d.ts
|
|
11
11
|
type ToolCallWithResult<ToolCall = DefaultToolCall$1> = ToolCallWithResult$1<ToolCall, ToolMessage, AIMessage>;
|
|
12
12
|
//#endregion
|
|
13
|
-
export { type AgentTypeConfigLike, type BaseStream, type BaseSubagentState, type ClassSubagentStreamInterface, type CompiledSubAgentLike, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, FetchStreamTransport, type GetToolCallsType, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type IsAgentLike, type IsDeepAgentLike, type MessageMetadata, type QueueEntry, type QueueInterface, type ResolveStreamInterface, type ResolveStreamOptions, StreamProvider, type StreamProviderCustomProps, type StreamProviderProps, type SubAgentLike, SubagentManager, type SubagentStateMap, type SubagentStatus, type SubagentStream, type SubagentStreamInterface, type SubagentToolCall, type ToolCallFromTool, type ToolCallState, ToolCallWithResult, type ToolCallsFromTools, type UseAgentStream, type UseAgentStreamOptions, type UseDeepAgentStream, type UseDeepAgentStreamOptions, type UseStream, type UseStreamCustom, type UseStreamCustomOptions, type UseStreamOptions, type UseStreamThread, type UseStreamTransport, type UseSuspenseStream, calculateDepthFromNamespace, extractParentIdFromNamespace, extractToolCallIdFromNamespace, invalidateSuspenseCache, isSubagentNamespace, useStream, useStreamContext, useSuspenseStream };
|
|
13
|
+
export { type AgentTypeConfigLike, type BaseStream, type BaseSubagentState, type ClassSubagentStreamInterface, type CompiledSubAgentLike, type DeepAgentTypeConfigLike, type DefaultSubagentStates, type DefaultToolCall, type ExtractAgentConfig, type ExtractDeepAgentConfig, type ExtractSubAgentMiddleware, FetchStreamTransport, type GetToolCallsType, type InferAgentToolCalls, type InferBag, type InferDeepAgentSubagents, type InferNodeNames, type InferStateType, type InferSubagentByName, type InferSubagentNames, type InferSubagentState, type InferSubagentStates, type InferToolCalls, type IsAgentLike, type IsDeepAgentLike, type MessageMetadata, type QueueEntry, type QueueInterface, type ResolveStreamInterface, type ResolveStreamOptions, StreamProvider, type StreamProviderCustomProps, type StreamProviderProps, type SubAgentLike, SubagentManager, type SubagentStateMap, type SubagentStatus, type SubagentStream, type SubagentStreamInterface, type SubagentToolCall, type SuspenseCache, type ToolCallFromTool, type ToolCallState, ToolCallWithResult, type ToolCallsFromTools, type UseAgentStream, type UseAgentStreamOptions, type UseDeepAgentStream, type UseDeepAgentStreamOptions, type UseStream, type UseStreamCustom, type UseStreamCustomOptions, type UseStreamOptions, type UseStreamThread, type UseStreamTransport, type UseSuspenseStream, calculateDepthFromNamespace, createSuspenseCache, extractParentIdFromNamespace, extractToolCallIdFromNamespace, invalidateSuspenseCache, isSubagentNamespace, useStream, useStreamContext, useSuspenseStream };
|
|
14
14
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;KAkFY,kBAAA,YAA8B,iBAAA,IACxC,oBAAA,CAAoB,QAAA,EAAU,WAAA,EAAiB,SAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FetchStreamTransport } from "./stream.custom.js";
|
|
2
2
|
import { useStream } from "./stream.js";
|
|
3
|
-
import { invalidateSuspenseCache, useSuspenseStream } from "./suspense-stream.js";
|
|
3
|
+
import { createSuspenseCache, invalidateSuspenseCache, useSuspenseStream } from "./suspense-stream.js";
|
|
4
4
|
import { StreamProvider, useStreamContext } from "./context.js";
|
|
5
5
|
import { SubagentManager, calculateDepthFromNamespace, extractParentIdFromNamespace, extractToolCallIdFromNamespace, isSubagentNamespace } from "@langchain/langgraph-sdk/ui";
|
|
6
|
-
export { FetchStreamTransport, StreamProvider, SubagentManager, calculateDepthFromNamespace, extractParentIdFromNamespace, extractToolCallIdFromNamespace, invalidateSuspenseCache, isSubagentNamespace, useStream, useStreamContext, useSuspenseStream };
|
|
6
|
+
export { FetchStreamTransport, StreamProvider, SubagentManager, calculateDepthFromNamespace, createSuspenseCache, extractParentIdFromNamespace, extractToolCallIdFromNamespace, invalidateSuspenseCache, isSubagentNamespace, useStream, useStreamContext, useSuspenseStream };
|
package/dist/stream.cjs
CHANGED
|
@@ -5,10 +5,12 @@ let react = require("react");
|
|
|
5
5
|
function isCustomOptions(options) {
|
|
6
6
|
return "transport" in options;
|
|
7
7
|
}
|
|
8
|
+
function selectStreamImplementation(options) {
|
|
9
|
+
return isCustomOptions(options) ? require_stream_custom.useStreamCustom : require_stream_lgp.useStreamLGP;
|
|
10
|
+
}
|
|
8
11
|
function useStream(options) {
|
|
9
|
-
const [
|
|
10
|
-
|
|
11
|
-
return require_stream_lgp.useStreamLGP(options);
|
|
12
|
+
const [useSelectedStream] = (0, react.useState)(() => selectStreamImplementation(options));
|
|
13
|
+
return useSelectedStream(options);
|
|
12
14
|
}
|
|
13
15
|
//#endregion
|
|
14
16
|
exports.useStream = useStream;
|
package/dist/stream.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.cjs","names":["useStreamCustom","useStreamLGP"],"sources":["../src/stream.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type {\n BaseMessage,\n ToolMessage as CoreToolMessage,\n AIMessage as CoreAIMessage,\n} from \"@langchain/core/messages\";\nimport type {\n BagTemplate,\n ToolCallWithResult,\n DefaultToolCall,\n} from \"@langchain/langgraph-sdk\";\nimport type {\n UseStreamOptions,\n AcceptBaseMessages,\n ResolveStreamInterface,\n ResolveStreamOptions,\n InferBag,\n InferStateType,\n MessageMetadata,\n SubagentStreamInterface,\n HistoryWithBaseMessages,\n} from \"@langchain/langgraph-sdk/ui\";\nimport { useStreamLGP } from \"./stream.lgp.js\";\nimport { useStreamCustom } from \"./stream.custom.js\";\nimport type { UseStreamCustomOptions } from \"./types.js\";\n\nfunction isCustomOptions<\n StateType extends Record<string, unknown> = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options:\n | UseStreamOptions<StateType, Bag>\n | UseStreamCustomOptions<StateType, Bag>,\n): options is UseStreamCustomOptions<StateType, Bag> {\n return \"transport\" in options;\n}\n\ntype ClassToolCallWithResult<T> =\n T extends ToolCallWithResult<infer TC, unknown, unknown>\n ? ToolCallWithResult<TC, CoreToolMessage, CoreAIMessage>\n : T;\n\nexport type ClassSubagentStreamInterface<\n StateType = Record<string, unknown>,\n ToolCall = DefaultToolCall,\n SubagentName extends string = string,\n> = Omit<\n SubagentStreamInterface<StateType, ToolCall, SubagentName>,\n \"messages\"\n> & {\n messages: BaseMessage[];\n};\n\n/**\n * Maps a stream interface to use @langchain/core BaseMessage class instances\n * instead of plain Message objects for the `messages` property, and remaps\n * tool call types to use @langchain/core message classes.\n */\nexport type WithClassMessages<T> = Omit<\n T,\n | \"messages\"\n | \"history\"\n | \"getMessagesMetadata\"\n | \"toolCalls\"\n | \"getToolCalls\"\n | \"submit\"\n | \"subagents\"\n | \"activeSubagents\"\n | \"getSubagent\"\n | \"getSubagentsByType\"\n | \"getSubagentsByMessage\"\n> & {\n messages: BaseMessage[];\n getMessagesMetadata: (\n message: BaseMessage,\n index?: number,\n ) => MessageMetadata<Record<string, unknown>> | undefined;\n} & (\"history\" extends keyof T\n ? { history: HistoryWithBaseMessages<T[\"history\"]> }\n : unknown) &\n (\"submit\" extends keyof T\n ? {\n submit: T extends {\n submit: (values: infer V, options?: infer O) => infer Ret;\n }\n ? (\n values:\n | AcceptBaseMessages<Exclude<V, null | undefined>>\n | null\n | undefined,\n options?: O,\n ) => Ret\n : never;\n }\n : unknown) &\n (\"toolCalls\" extends keyof T\n ? {\n toolCalls: T extends { toolCalls: (infer TC)[] }\n ? ClassToolCallWithResult<TC>[]\n : never;\n }\n : unknown) &\n (\"getToolCalls\" extends keyof T\n ? {\n getToolCalls: T extends {\n getToolCalls: (message: infer _M) => (infer TC)[];\n }\n ? (message: CoreAIMessage) => ClassToolCallWithResult<TC>[]\n : never;\n }\n : unknown) &\n (\"subagents\" extends keyof T\n ? {\n subagents: T extends {\n subagents: Map<\n string,\n SubagentStreamInterface<infer S, infer TC, infer N>\n >;\n }\n ? Map<string, ClassSubagentStreamInterface<S, TC, N>>\n : never;\n activeSubagents: T extends {\n activeSubagents: SubagentStreamInterface<\n infer S,\n infer TC,\n infer N\n >[];\n }\n ? ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n getSubagent: T extends {\n getSubagent: (\n id: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N> | undefined;\n }\n ? (\n toolCallId: string,\n ) => ClassSubagentStreamInterface<S, TC, N> | undefined\n : never;\n getSubagentsByType: T extends {\n getSubagentsByType: (\n type: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N>[];\n }\n ? (type: string) => ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n getSubagentsByMessage: T extends {\n getSubagentsByMessage: (\n id: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N>[];\n }\n ? (messageId: string) => ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n }\n : unknown);\n\n/**\n * A React hook that provides seamless integration with LangGraph streaming capabilities.\n *\n * The `useStream` hook handles all the complexities of streaming, state management, and branching logic,\n * letting you focus on building great chat experiences. It provides automatic state management for\n * messages, interrupts, loading states, subagent streams, and errors.\n *\n * ## Usage with ReactAgent (recommended for createAgent users)\n *\n * When using `createAgent` from `@langchain/langgraph`, you can pass `typeof agent` as the\n * type parameter to automatically infer tool call types:\n *\n * @example\n * ```typescript\n * // In your agent file (e.g., agent.ts)\n * import { createAgent, tool } from \"langchain\";\n * import { z } from \"zod\";\n *\n * const getWeather = tool(\n * async ({ location }) => `Weather in ${location}`,\n * { name: \"get_weather\", schema: z.object({ location: z.string() }) }\n * );\n *\n * export const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [getWeather],\n * });\n *\n * // In your React component\n * import { agent } from \"./agent\";\n *\n * function Chat() {\n * // Tool calls are automatically typed from the agent's tools!\n * const stream = useStream<typeof agent>({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.toolCalls[0].call.name is typed as \"get_weather\"\n * // stream.toolCalls[0].call.args is typed as { location: string }\n * }\n * ```\n *\n * ## Usage with StateGraph (for custom LangGraph applications)\n *\n * When building custom graphs with `StateGraph`, embed your tool call types directly\n * in your state's messages property using `Message<MyToolCalls>`:\n *\n * @example\n * ```typescript\n * import { Message } from \"@langchain/langgraph-sdk\";\n *\n * // Define your tool call types as a discriminated union\n * type MyToolCalls =\n * | { name: \"search\"; args: { query: string }; id?: string }\n * | { name: \"calculate\"; args: { expression: string }; id?: string };\n *\n * // Embed tool call types in your state's messages\n * interface MyGraphState {\n * messages: Message<MyToolCalls>[];\n * context?: string;\n * }\n *\n * function Chat() {\n * const stream = useStream<MyGraphState>({\n * assistantId: \"my-graph\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.values is typed as MyGraphState\n * // stream.toolCalls[0].call.name is typed as \"search\" | \"calculate\"\n * }\n * ```\n *\n * @example\n * ```typescript\n * // With additional type configuration (interrupts, configurable)\n * interface MyGraphState {\n * messages: Message<MyToolCalls>[];\n * }\n *\n * function Chat() {\n * const stream = useStream<MyGraphState, {\n * InterruptType: { question: string };\n * ConfigurableType: { userId: string };\n * }>({\n * assistantId: \"my-graph\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.interrupt is typed as { question: string } | undefined\n * }\n * ```\n *\n * ## Usage with Deep Agents (subagent streaming, experimental)\n *\n * For agents that spawn subagents (nested graphs), use `filterSubagentMessages`\n * to keep the main message stream clean while tracking subagent activity separately:\n *\n * @example\n * ```typescript\n * import { useStream, SubagentStream } from \"@langchain/langgraph-sdk/react\";\n * import type { agent } from \"./agent\";\n *\n * function DeepAgentChat() {\n * const stream = useStream<typeof agent>({\n * assistantId: \"deepagent\",\n * apiUrl: \"http://localhost:2024\",\n * // Filter subagent messages from main stream\n * filterSubagentMessages: true,\n * });\n *\n * const handleSubmit = (content: string) => {\n * stream.submit(\n * { messages: [{ content, type: \"human\" }] },\n * { streamSubgraphs: true } // Enable subgraph streaming\n * );\n * };\n *\n * // Access subagent streams via stream.subagents (Map<string, SubagentStream>)\n * const subagentList = [...stream.subagents.values()];\n *\n * return (\n * <div>\n * {stream.messages.map((msg) => <Message key={msg.id} message={msg} />)}\n *\n * {subagentList.map((subagent) => (\n * <SubagentCard\n * key={subagent.id}\n * status={subagent.status} // \"pending\" | \"running\" | \"complete\" | \"error\"\n * messages={subagent.messages}\n * toolCalls={subagent.toolCalls}\n * />\n * ))}\n * </div>\n * );\n * }\n * ```\n *\n * @template T Either a ReactAgent type (with `~agentTypes`) or a state type (`Record<string, unknown>`)\n * @template Bag Type configuration bag containing:\n * - `ConfigurableType`: Type for the `config.configurable` property\n * - `InterruptType`: Type for interrupt values\n * - `CustomEventType`: Type for custom events\n * - `UpdateType`: Type for the submit function updates\n *\n * @see {@link https://docs.langchain.com/langgraph-platform/use-stream-react | LangGraph React Integration Guide}\n */\nexport function useStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: ResolveStreamOptions<T, InferBag<T, Bag>>,\n): WithClassMessages<ResolveStreamInterface<T, InferBag<T, Bag>>>;\n\n/**\n * A React hook that provides seamless integration with LangGraph streaming capabilities.\n *\n * The `useStream` hook handles all the complexities of streaming, state management, and branching logic,\n * letting you focus on building great chat experiences. It provides automatic state management for\n * messages, interrupts, loading states, and errors.\n *\n * @template T Either a ReactAgent type (with `~agentTypes`) or a state type (`Record<string, unknown>`)\n * @template Bag Type configuration bag containing:\n * - `ConfigurableType`: Type for the `config.configurable` property\n * - `InterruptType`: Type for interrupt values\n * - `CustomEventType`: Type for custom events\n * - `UpdateType`: Type for the submit function updates\n *\n * @see {@link https://docs.langchain.com/langgraph-platform/use-stream-react | LangGraph React Integration Guide}\n */\nexport function useStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: UseStreamCustomOptions<InferStateType<T>, InferBag<T, Bag>>,\n): WithClassMessages<ResolveStreamInterface<T, InferBag<T, Bag>>>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useStream(options: any): any {\n // Store this in useState to make sure we're not changing the implementation in re-renders\n const [isCustom] = useState(isCustomOptions(options));\n\n if (isCustom) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useStreamCustom(options);\n }\n\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useStreamLGP(options);\n}\n"],"mappings":";;;;AA0BA,SAAS,gBAIP,SAGmD;AACnD,QAAO,eAAe;;AA6SxB,SAAgB,UAAU,SAAmB;CAE3C,MAAM,CAAC,aAAA,GAAA,MAAA,UAAqB,gBAAgB,QAAQ,CAAC;AAErD,KAAI,SAEF,QAAOA,sBAAAA,gBAAgB,QAAQ;AAIjC,QAAOC,mBAAAA,aAAa,QAAQ"}
|
|
1
|
+
{"version":3,"file":"stream.cjs","names":["useStreamCustom","useStreamLGP"],"sources":["../src/stream.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type {\n BaseMessage,\n ToolMessage as CoreToolMessage,\n AIMessage as CoreAIMessage,\n} from \"@langchain/core/messages\";\nimport type {\n BagTemplate,\n ToolCallWithResult,\n DefaultToolCall,\n} from \"@langchain/langgraph-sdk\";\nimport type {\n UseStreamOptions,\n AcceptBaseMessages,\n ResolveStreamInterface,\n ResolveStreamOptions,\n InferBag,\n InferStateType,\n MessageMetadata,\n SubagentStreamInterface,\n HistoryWithBaseMessages,\n} from \"@langchain/langgraph-sdk/ui\";\nimport { useStreamLGP } from \"./stream.lgp.js\";\nimport { useStreamCustom } from \"./stream.custom.js\";\nimport type { UseStreamCustomOptions } from \"./types.js\";\n\nfunction isCustomOptions<\n StateType extends Record<string, unknown> = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options:\n | UseStreamOptions<StateType, Bag>\n | UseStreamCustomOptions<StateType, Bag>,\n): options is UseStreamCustomOptions<StateType, Bag> {\n return \"transport\" in options;\n}\n\ntype UseStreamImplementation = typeof useStreamLGP | typeof useStreamCustom;\n\ntype AnyUseStreamOptions =\n | UseStreamOptions<Record<string, unknown>, BagTemplate>\n | UseStreamCustomOptions<Record<string, unknown>, BagTemplate>;\n\nfunction selectStreamImplementation(\n options: AnyUseStreamOptions,\n): UseStreamImplementation {\n return isCustomOptions(options) ? useStreamCustom : useStreamLGP;\n}\n\ntype ClassToolCallWithResult<T> =\n T extends ToolCallWithResult<infer TC, unknown, unknown>\n ? ToolCallWithResult<TC, CoreToolMessage, CoreAIMessage>\n : T;\n\nexport type ClassSubagentStreamInterface<\n StateType = Record<string, unknown>,\n ToolCall = DefaultToolCall,\n SubagentName extends string = string,\n> = Omit<\n SubagentStreamInterface<StateType, ToolCall, SubagentName>,\n \"messages\"\n> & {\n messages: BaseMessage[];\n};\n\n/**\n * Maps a stream interface to use @langchain/core BaseMessage class instances\n * instead of plain Message objects for the `messages` property, and remaps\n * tool call types to use @langchain/core message classes.\n */\nexport type WithClassMessages<T> = Omit<\n T,\n | \"messages\"\n | \"history\"\n | \"getMessagesMetadata\"\n | \"toolCalls\"\n | \"getToolCalls\"\n | \"submit\"\n | \"subagents\"\n | \"activeSubagents\"\n | \"getSubagent\"\n | \"getSubagentsByType\"\n | \"getSubagentsByMessage\"\n> & {\n messages: BaseMessage[];\n getMessagesMetadata: (\n message: BaseMessage,\n index?: number,\n ) => MessageMetadata<Record<string, unknown>> | undefined;\n} & (\"history\" extends keyof T\n ? { history: HistoryWithBaseMessages<T[\"history\"]> }\n : unknown) &\n (\"submit\" extends keyof T\n ? {\n submit: T extends {\n submit: (values: infer V, options?: infer O) => infer Ret;\n }\n ? (\n values:\n | AcceptBaseMessages<Exclude<V, null | undefined>>\n | null\n | undefined,\n options?: O,\n ) => Ret\n : never;\n }\n : unknown) &\n (\"toolCalls\" extends keyof T\n ? {\n toolCalls: T extends { toolCalls: (infer TC)[] }\n ? ClassToolCallWithResult<TC>[]\n : never;\n }\n : unknown) &\n (\"getToolCalls\" extends keyof T\n ? {\n getToolCalls: T extends {\n getToolCalls: (message: infer _M) => (infer TC)[];\n }\n ? (message: CoreAIMessage) => ClassToolCallWithResult<TC>[]\n : never;\n }\n : unknown) &\n (\"subagents\" extends keyof T\n ? {\n subagents: T extends {\n subagents: Map<\n string,\n SubagentStreamInterface<infer S, infer TC, infer N>\n >;\n }\n ? Map<string, ClassSubagentStreamInterface<S, TC, N>>\n : never;\n activeSubagents: T extends {\n activeSubagents: SubagentStreamInterface<\n infer S,\n infer TC,\n infer N\n >[];\n }\n ? ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n getSubagent: T extends {\n getSubagent: (\n id: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N> | undefined;\n }\n ? (\n toolCallId: string,\n ) => ClassSubagentStreamInterface<S, TC, N> | undefined\n : never;\n getSubagentsByType: T extends {\n getSubagentsByType: (\n type: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N>[];\n }\n ? (type: string) => ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n getSubagentsByMessage: T extends {\n getSubagentsByMessage: (\n id: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N>[];\n }\n ? (messageId: string) => ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n }\n : unknown);\n\n/**\n * A React hook that provides seamless integration with LangGraph streaming capabilities.\n *\n * The `useStream` hook handles all the complexities of streaming, state management, and branching logic,\n * letting you focus on building great chat experiences. It provides automatic state management for\n * messages, interrupts, loading states, subagent streams, and errors.\n *\n * ## Usage with ReactAgent (recommended for createAgent users)\n *\n * When using `createAgent` from `@langchain/langgraph`, you can pass `typeof agent` as the\n * type parameter to automatically infer tool call types:\n *\n * @example\n * ```typescript\n * // In your agent file (e.g., agent.ts)\n * import { createAgent, tool } from \"langchain\";\n * import { z } from \"zod\";\n *\n * const getWeather = tool(\n * async ({ location }) => `Weather in ${location}`,\n * { name: \"get_weather\", schema: z.object({ location: z.string() }) }\n * );\n *\n * export const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [getWeather],\n * });\n *\n * // In your React component\n * import { agent } from \"./agent\";\n *\n * function Chat() {\n * // Tool calls are automatically typed from the agent's tools!\n * const stream = useStream<typeof agent>({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.toolCalls[0].call.name is typed as \"get_weather\"\n * // stream.toolCalls[0].call.args is typed as { location: string }\n * }\n * ```\n *\n * ## Usage with StateGraph (for custom LangGraph applications)\n *\n * When building custom graphs with `StateGraph`, embed your tool call types directly\n * in your state's messages property using `Message<MyToolCalls>`:\n *\n * @example\n * ```typescript\n * import { Message } from \"@langchain/langgraph-sdk\";\n *\n * // Define your tool call types as a discriminated union\n * type MyToolCalls =\n * | { name: \"search\"; args: { query: string }; id?: string }\n * | { name: \"calculate\"; args: { expression: string }; id?: string };\n *\n * // Embed tool call types in your state's messages\n * interface MyGraphState {\n * messages: Message<MyToolCalls>[];\n * context?: string;\n * }\n *\n * function Chat() {\n * const stream = useStream<MyGraphState>({\n * assistantId: \"my-graph\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.values is typed as MyGraphState\n * // stream.toolCalls[0].call.name is typed as \"search\" | \"calculate\"\n * }\n * ```\n *\n * @example\n * ```typescript\n * // With additional type configuration (interrupts, configurable)\n * interface MyGraphState {\n * messages: Message<MyToolCalls>[];\n * }\n *\n * function Chat() {\n * const stream = useStream<MyGraphState, {\n * InterruptType: { question: string };\n * ConfigurableType: { userId: string };\n * }>({\n * assistantId: \"my-graph\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.interrupt is typed as { question: string } | undefined\n * }\n * ```\n *\n * ## Usage with Deep Agents (subagent streaming, experimental)\n *\n * For agents that spawn subagents (nested graphs), use `filterSubagentMessages`\n * to keep the main message stream clean while tracking subagent activity separately:\n *\n * @example\n * ```typescript\n * import { useStream, SubagentStream } from \"@langchain/langgraph-sdk/react\";\n * import type { agent } from \"./agent\";\n *\n * function DeepAgentChat() {\n * const stream = useStream<typeof agent>({\n * assistantId: \"deepagent\",\n * apiUrl: \"http://localhost:2024\",\n * // Filter subagent messages from main stream\n * filterSubagentMessages: true,\n * });\n *\n * const handleSubmit = (content: string) => {\n * stream.submit(\n * { messages: [{ content, type: \"human\" }] },\n * { streamSubgraphs: true } // Enable subgraph streaming\n * );\n * };\n *\n * // Access subagent streams via stream.subagents (Map<string, SubagentStream>)\n * const subagentList = [...stream.subagents.values()];\n *\n * return (\n * <div>\n * {stream.messages.map((msg) => <Message key={msg.id} message={msg} />)}\n *\n * {subagentList.map((subagent) => (\n * <SubagentCard\n * key={subagent.id}\n * status={subagent.status} // \"pending\" | \"running\" | \"complete\" | \"error\"\n * messages={subagent.messages}\n * toolCalls={subagent.toolCalls}\n * />\n * ))}\n * </div>\n * );\n * }\n * ```\n *\n * @template T Either a ReactAgent type (with `~agentTypes`) or a state type (`Record<string, unknown>`)\n * @template Bag Type configuration bag containing:\n * - `ConfigurableType`: Type for the `config.configurable` property\n * - `InterruptType`: Type for interrupt values\n * - `CustomEventType`: Type for custom events\n * - `UpdateType`: Type for the submit function updates\n *\n * @see {@link https://docs.langchain.com/langgraph-platform/use-stream-react | LangGraph React Integration Guide}\n */\nexport function useStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: ResolveStreamOptions<T, InferBag<T, Bag>>,\n): WithClassMessages<ResolveStreamInterface<T, InferBag<T, Bag>>>;\n\n/**\n * A React hook that provides seamless integration with LangGraph streaming capabilities.\n *\n * The `useStream` hook handles all the complexities of streaming, state management, and branching logic,\n * letting you focus on building great chat experiences. It provides automatic state management for\n * messages, interrupts, loading states, and errors.\n *\n * @template T Either a ReactAgent type (with `~agentTypes`) or a state type (`Record<string, unknown>`)\n * @template Bag Type configuration bag containing:\n * - `ConfigurableType`: Type for the `config.configurable` property\n * - `InterruptType`: Type for interrupt values\n * - `CustomEventType`: Type for custom events\n * - `UpdateType`: Type for the submit function updates\n *\n * @see {@link https://docs.langchain.com/langgraph-platform/use-stream-react | LangGraph React Integration Guide}\n */\nexport function useStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: UseStreamCustomOptions<InferStateType<T>, InferBag<T, Bag>>,\n): WithClassMessages<ResolveStreamInterface<T, InferBag<T, Bag>>>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useStream(options: any): any {\n // Keep implementation stable for the lifetime of this hook instance.\n const [useSelectedStream] = useState(() =>\n selectStreamImplementation(options),\n );\n return useSelectedStream(options);\n}\n"],"mappings":";;;;AA0BA,SAAS,gBAIP,SAGmD;AACnD,QAAO,eAAe;;AASxB,SAAS,2BACP,SACyB;AACzB,QAAO,gBAAgB,QAAQ,GAAGA,sBAAAA,kBAAkBC,mBAAAA;;AA6StD,SAAgB,UAAU,SAAmB;CAE3C,MAAM,CAAC,sBAAA,GAAA,MAAA,gBACL,2BAA2B,QAAQ,CACpC;AACD,QAAO,kBAAkB,QAAQ"}
|
package/dist/stream.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.d.cts","names":[],"sources":["../src/stream.tsx"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"stream.d.cts","names":[],"sources":["../src/stream.tsx"],"mappings":";;;;;;KAiDK,uBAAA,MACH,CAAA,SAAU,kBAAA,+BACN,kBAAA,CAAmB,EAAA,EAAI,WAAA,EAAiB,SAAA,IACxC,CAAA;AAAA,KAEM,4BAAA,aACE,MAAA,8BACD,eAAA,0CAET,IAAA,CACF,uBAAA,CAAwB,SAAA,EAAW,QAAA,EAAU,YAAA;EAG7C,QAAA,EAAU,WAAA;AAAA;;;;;;KAQA,iBAAA,MAAuB,IAAA,CACjC,CAAA;EAaA,QAAA,EAAU,WAAA;EACV,mBAAA,GACE,OAAA,EAAS,WAAA,EACT,KAAA,cACG,eAAA,CAAgB,MAAA;AAAA,6BACM,CAAA;EACrB,OAAA,EAAS,uBAAA,CAAwB,CAAA;AAAA,uCAEf,CAAA;EAElB,MAAA,EAAQ,CAAA;IACN,MAAA,GAAS,MAAA,WAAiB,OAAA;EAAA,KAGtB,MAAA,EACI,kBAAA,CAAmB,OAAA,CAAQ,CAAA,yCAG/B,OAAA,GAAU,CAAA,KACP,GAAA;AAAA,0CAIY,CAAA;EAErB,SAAA,EAAW,CAAA;IAAY,SAAA;EAAA,IACnB,uBAAA,CAAwB,EAAA;AAAA,6CAIJ,CAAA;EAExB,YAAA,EAAc,CAAA;IACZ,YAAA,GAAe,OAAA;EAAA,KAEZ,OAAA,EAAS,SAAA,KAAkB,uBAAA,CAAwB,EAAA;AAAA,0CAInC,CAAA;EAErB,SAAA,EAAW,CAAA;IACT,SAAA,EAAW,GAAA,SAET,uBAAA;EAAA,IAGA,GAAA,SAAY,4BAAA,CAA6B,CAAA,EAAG,EAAA,EAAI,CAAA;EAEpD,eAAA,EAAiB,CAAA;IACf,eAAA,EAAiB,uBAAA;EAAA,IAMf,4BAAA,CAA6B,CAAA,EAAG,EAAA,EAAI,CAAA;EAExC,WAAA,EAAa,CAAA;IACX,WAAA,GACE,EAAA,aACG,uBAAA;EAAA,KAGD,UAAA,aACG,4BAAA,CAA6B,CAAA,EAAG,EAAA,EAAI,CAAA;EAE7C,kBAAA,EAAoB,CAAA;IAClB,kBAAA,GACE,IAAA,aACG,uBAAA;EAAA,KAEF,IAAA,aAAiB,4BAAA,CAA6B,CAAA,EAAG,EAAA,EAAI,CAAA;EAE1D,qBAAA,EAAuB,CAAA;IACrB,qBAAA,GACE,EAAA,aACG,uBAAA;EAAA,KAEF,SAAA,aAAsB,4BAAA,CAA6B,CAAA,EAAG,EAAA,EAAI,CAAA;AAAA;;;;;;;;;;AA7FvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsPgB,SAAA,KACV,MAAA,+BACQ,WAAA,GAAc,WAAA,CAAA,CAE1B,OAAA,EAAS,oBAAA,CAAqB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA,KAC5C,iBAAA,CAAkB,sBAAA,CAAuB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA;;;;;;;;;;;;;;;;;iBAkB3C,SAAA,KACV,MAAA,+BACQ,WAAA,GAAc,WAAA,CAAA,CAE1B,OAAA,EAAS,wBAAA,CAAuB,cAAA,CAAe,CAAA,GAAI,QAAA,CAAS,CAAA,EAAG,GAAA,KAC9D,iBAAA,CAAkB,sBAAA,CAAuB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA"}
|
package/dist/stream.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.d.ts","names":[],"sources":["../src/stream.tsx"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"stream.d.ts","names":[],"sources":["../src/stream.tsx"],"mappings":";;;;;;KAiDK,uBAAA,MACH,CAAA,SAAU,kBAAA,+BACN,kBAAA,CAAmB,EAAA,EAAI,WAAA,EAAiB,SAAA,IACxC,CAAA;AAAA,KAEM,4BAAA,aACE,MAAA,8BACD,eAAA,0CAET,IAAA,CACF,uBAAA,CAAwB,SAAA,EAAW,QAAA,EAAU,YAAA;EAG7C,QAAA,EAAU,WAAA;AAAA;;;;;;KAQA,iBAAA,MAAuB,IAAA,CACjC,CAAA;EAaA,QAAA,EAAU,WAAA;EACV,mBAAA,GACE,OAAA,EAAS,WAAA,EACT,KAAA,cACG,eAAA,CAAgB,MAAA;AAAA,6BACM,CAAA;EACrB,OAAA,EAAS,uBAAA,CAAwB,CAAA;AAAA,uCAEf,CAAA;EAElB,MAAA,EAAQ,CAAA;IACN,MAAA,GAAS,MAAA,WAAiB,OAAA;EAAA,KAGtB,MAAA,EACI,kBAAA,CAAmB,OAAA,CAAQ,CAAA,yCAG/B,OAAA,GAAU,CAAA,KACP,GAAA;AAAA,0CAIY,CAAA;EAErB,SAAA,EAAW,CAAA;IAAY,SAAA;EAAA,IACnB,uBAAA,CAAwB,EAAA;AAAA,6CAIJ,CAAA;EAExB,YAAA,EAAc,CAAA;IACZ,YAAA,GAAe,OAAA;EAAA,KAEZ,OAAA,EAAS,SAAA,KAAkB,uBAAA,CAAwB,EAAA;AAAA,0CAInC,CAAA;EAErB,SAAA,EAAW,CAAA;IACT,SAAA,EAAW,GAAA,SAET,uBAAA;EAAA,IAGA,GAAA,SAAY,4BAAA,CAA6B,CAAA,EAAG,EAAA,EAAI,CAAA;EAEpD,eAAA,EAAiB,CAAA;IACf,eAAA,EAAiB,uBAAA;EAAA,IAMf,4BAAA,CAA6B,CAAA,EAAG,EAAA,EAAI,CAAA;EAExC,WAAA,EAAa,CAAA;IACX,WAAA,GACE,EAAA,aACG,uBAAA;EAAA,KAGD,UAAA,aACG,4BAAA,CAA6B,CAAA,EAAG,EAAA,EAAI,CAAA;EAE7C,kBAAA,EAAoB,CAAA;IAClB,kBAAA,GACE,IAAA,aACG,uBAAA;EAAA,KAEF,IAAA,aAAiB,4BAAA,CAA6B,CAAA,EAAG,EAAA,EAAI,CAAA;EAE1D,qBAAA,EAAuB,CAAA;IACrB,qBAAA,GACE,EAAA,aACG,uBAAA;EAAA,KAEF,SAAA,aAAsB,4BAAA,CAA6B,CAAA,EAAG,EAAA,EAAI,CAAA;AAAA;;;;;;;;;;AA7FvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsPgB,SAAA,KACV,MAAA,+BACQ,WAAA,GAAc,WAAA,CAAA,CAE1B,OAAA,EAAS,oBAAA,CAAqB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA,KAC5C,iBAAA,CAAkB,sBAAA,CAAuB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA;;;;;;;;;;;;;;;;;iBAkB3C,SAAA,KACV,MAAA,+BACQ,WAAA,GAAc,WAAA,CAAA,CAE1B,OAAA,EAAS,wBAAA,CAAuB,cAAA,CAAe,CAAA,GAAI,QAAA,CAAS,CAAA,EAAG,GAAA,KAC9D,iBAAA,CAAkB,sBAAA,CAAuB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA"}
|
package/dist/stream.js
CHANGED
|
@@ -5,10 +5,12 @@ import { useState } from "react";
|
|
|
5
5
|
function isCustomOptions(options) {
|
|
6
6
|
return "transport" in options;
|
|
7
7
|
}
|
|
8
|
+
function selectStreamImplementation(options) {
|
|
9
|
+
return isCustomOptions(options) ? useStreamCustom : useStreamLGP;
|
|
10
|
+
}
|
|
8
11
|
function useStream(options) {
|
|
9
|
-
const [
|
|
10
|
-
|
|
11
|
-
return useStreamLGP(options);
|
|
12
|
+
const [useSelectedStream] = useState(() => selectStreamImplementation(options));
|
|
13
|
+
return useSelectedStream(options);
|
|
12
14
|
}
|
|
13
15
|
//#endregion
|
|
14
16
|
export { useStream };
|
package/dist/stream.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.js","names":[],"sources":["../src/stream.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type {\n BaseMessage,\n ToolMessage as CoreToolMessage,\n AIMessage as CoreAIMessage,\n} from \"@langchain/core/messages\";\nimport type {\n BagTemplate,\n ToolCallWithResult,\n DefaultToolCall,\n} from \"@langchain/langgraph-sdk\";\nimport type {\n UseStreamOptions,\n AcceptBaseMessages,\n ResolveStreamInterface,\n ResolveStreamOptions,\n InferBag,\n InferStateType,\n MessageMetadata,\n SubagentStreamInterface,\n HistoryWithBaseMessages,\n} from \"@langchain/langgraph-sdk/ui\";\nimport { useStreamLGP } from \"./stream.lgp.js\";\nimport { useStreamCustom } from \"./stream.custom.js\";\nimport type { UseStreamCustomOptions } from \"./types.js\";\n\nfunction isCustomOptions<\n StateType extends Record<string, unknown> = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options:\n | UseStreamOptions<StateType, Bag>\n | UseStreamCustomOptions<StateType, Bag>,\n): options is UseStreamCustomOptions<StateType, Bag> {\n return \"transport\" in options;\n}\n\ntype ClassToolCallWithResult<T> =\n T extends ToolCallWithResult<infer TC, unknown, unknown>\n ? ToolCallWithResult<TC, CoreToolMessage, CoreAIMessage>\n : T;\n\nexport type ClassSubagentStreamInterface<\n StateType = Record<string, unknown>,\n ToolCall = DefaultToolCall,\n SubagentName extends string = string,\n> = Omit<\n SubagentStreamInterface<StateType, ToolCall, SubagentName>,\n \"messages\"\n> & {\n messages: BaseMessage[];\n};\n\n/**\n * Maps a stream interface to use @langchain/core BaseMessage class instances\n * instead of plain Message objects for the `messages` property, and remaps\n * tool call types to use @langchain/core message classes.\n */\nexport type WithClassMessages<T> = Omit<\n T,\n | \"messages\"\n | \"history\"\n | \"getMessagesMetadata\"\n | \"toolCalls\"\n | \"getToolCalls\"\n | \"submit\"\n | \"subagents\"\n | \"activeSubagents\"\n | \"getSubagent\"\n | \"getSubagentsByType\"\n | \"getSubagentsByMessage\"\n> & {\n messages: BaseMessage[];\n getMessagesMetadata: (\n message: BaseMessage,\n index?: number,\n ) => MessageMetadata<Record<string, unknown>> | undefined;\n} & (\"history\" extends keyof T\n ? { history: HistoryWithBaseMessages<T[\"history\"]> }\n : unknown) &\n (\"submit\" extends keyof T\n ? {\n submit: T extends {\n submit: (values: infer V, options?: infer O) => infer Ret;\n }\n ? (\n values:\n | AcceptBaseMessages<Exclude<V, null | undefined>>\n | null\n | undefined,\n options?: O,\n ) => Ret\n : never;\n }\n : unknown) &\n (\"toolCalls\" extends keyof T\n ? {\n toolCalls: T extends { toolCalls: (infer TC)[] }\n ? ClassToolCallWithResult<TC>[]\n : never;\n }\n : unknown) &\n (\"getToolCalls\" extends keyof T\n ? {\n getToolCalls: T extends {\n getToolCalls: (message: infer _M) => (infer TC)[];\n }\n ? (message: CoreAIMessage) => ClassToolCallWithResult<TC>[]\n : never;\n }\n : unknown) &\n (\"subagents\" extends keyof T\n ? {\n subagents: T extends {\n subagents: Map<\n string,\n SubagentStreamInterface<infer S, infer TC, infer N>\n >;\n }\n ? Map<string, ClassSubagentStreamInterface<S, TC, N>>\n : never;\n activeSubagents: T extends {\n activeSubagents: SubagentStreamInterface<\n infer S,\n infer TC,\n infer N\n >[];\n }\n ? ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n getSubagent: T extends {\n getSubagent: (\n id: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N> | undefined;\n }\n ? (\n toolCallId: string,\n ) => ClassSubagentStreamInterface<S, TC, N> | undefined\n : never;\n getSubagentsByType: T extends {\n getSubagentsByType: (\n type: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N>[];\n }\n ? (type: string) => ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n getSubagentsByMessage: T extends {\n getSubagentsByMessage: (\n id: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N>[];\n }\n ? (messageId: string) => ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n }\n : unknown);\n\n/**\n * A React hook that provides seamless integration with LangGraph streaming capabilities.\n *\n * The `useStream` hook handles all the complexities of streaming, state management, and branching logic,\n * letting you focus on building great chat experiences. It provides automatic state management for\n * messages, interrupts, loading states, subagent streams, and errors.\n *\n * ## Usage with ReactAgent (recommended for createAgent users)\n *\n * When using `createAgent` from `@langchain/langgraph`, you can pass `typeof agent` as the\n * type parameter to automatically infer tool call types:\n *\n * @example\n * ```typescript\n * // In your agent file (e.g., agent.ts)\n * import { createAgent, tool } from \"langchain\";\n * import { z } from \"zod\";\n *\n * const getWeather = tool(\n * async ({ location }) => `Weather in ${location}`,\n * { name: \"get_weather\", schema: z.object({ location: z.string() }) }\n * );\n *\n * export const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [getWeather],\n * });\n *\n * // In your React component\n * import { agent } from \"./agent\";\n *\n * function Chat() {\n * // Tool calls are automatically typed from the agent's tools!\n * const stream = useStream<typeof agent>({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.toolCalls[0].call.name is typed as \"get_weather\"\n * // stream.toolCalls[0].call.args is typed as { location: string }\n * }\n * ```\n *\n * ## Usage with StateGraph (for custom LangGraph applications)\n *\n * When building custom graphs with `StateGraph`, embed your tool call types directly\n * in your state's messages property using `Message<MyToolCalls>`:\n *\n * @example\n * ```typescript\n * import { Message } from \"@langchain/langgraph-sdk\";\n *\n * // Define your tool call types as a discriminated union\n * type MyToolCalls =\n * | { name: \"search\"; args: { query: string }; id?: string }\n * | { name: \"calculate\"; args: { expression: string }; id?: string };\n *\n * // Embed tool call types in your state's messages\n * interface MyGraphState {\n * messages: Message<MyToolCalls>[];\n * context?: string;\n * }\n *\n * function Chat() {\n * const stream = useStream<MyGraphState>({\n * assistantId: \"my-graph\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.values is typed as MyGraphState\n * // stream.toolCalls[0].call.name is typed as \"search\" | \"calculate\"\n * }\n * ```\n *\n * @example\n * ```typescript\n * // With additional type configuration (interrupts, configurable)\n * interface MyGraphState {\n * messages: Message<MyToolCalls>[];\n * }\n *\n * function Chat() {\n * const stream = useStream<MyGraphState, {\n * InterruptType: { question: string };\n * ConfigurableType: { userId: string };\n * }>({\n * assistantId: \"my-graph\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.interrupt is typed as { question: string } | undefined\n * }\n * ```\n *\n * ## Usage with Deep Agents (subagent streaming, experimental)\n *\n * For agents that spawn subagents (nested graphs), use `filterSubagentMessages`\n * to keep the main message stream clean while tracking subagent activity separately:\n *\n * @example\n * ```typescript\n * import { useStream, SubagentStream } from \"@langchain/langgraph-sdk/react\";\n * import type { agent } from \"./agent\";\n *\n * function DeepAgentChat() {\n * const stream = useStream<typeof agent>({\n * assistantId: \"deepagent\",\n * apiUrl: \"http://localhost:2024\",\n * // Filter subagent messages from main stream\n * filterSubagentMessages: true,\n * });\n *\n * const handleSubmit = (content: string) => {\n * stream.submit(\n * { messages: [{ content, type: \"human\" }] },\n * { streamSubgraphs: true } // Enable subgraph streaming\n * );\n * };\n *\n * // Access subagent streams via stream.subagents (Map<string, SubagentStream>)\n * const subagentList = [...stream.subagents.values()];\n *\n * return (\n * <div>\n * {stream.messages.map((msg) => <Message key={msg.id} message={msg} />)}\n *\n * {subagentList.map((subagent) => (\n * <SubagentCard\n * key={subagent.id}\n * status={subagent.status} // \"pending\" | \"running\" | \"complete\" | \"error\"\n * messages={subagent.messages}\n * toolCalls={subagent.toolCalls}\n * />\n * ))}\n * </div>\n * );\n * }\n * ```\n *\n * @template T Either a ReactAgent type (with `~agentTypes`) or a state type (`Record<string, unknown>`)\n * @template Bag Type configuration bag containing:\n * - `ConfigurableType`: Type for the `config.configurable` property\n * - `InterruptType`: Type for interrupt values\n * - `CustomEventType`: Type for custom events\n * - `UpdateType`: Type for the submit function updates\n *\n * @see {@link https://docs.langchain.com/langgraph-platform/use-stream-react | LangGraph React Integration Guide}\n */\nexport function useStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: ResolveStreamOptions<T, InferBag<T, Bag>>,\n): WithClassMessages<ResolveStreamInterface<T, InferBag<T, Bag>>>;\n\n/**\n * A React hook that provides seamless integration with LangGraph streaming capabilities.\n *\n * The `useStream` hook handles all the complexities of streaming, state management, and branching logic,\n * letting you focus on building great chat experiences. It provides automatic state management for\n * messages, interrupts, loading states, and errors.\n *\n * @template T Either a ReactAgent type (with `~agentTypes`) or a state type (`Record<string, unknown>`)\n * @template Bag Type configuration bag containing:\n * - `ConfigurableType`: Type for the `config.configurable` property\n * - `InterruptType`: Type for interrupt values\n * - `CustomEventType`: Type for custom events\n * - `UpdateType`: Type for the submit function updates\n *\n * @see {@link https://docs.langchain.com/langgraph-platform/use-stream-react | LangGraph React Integration Guide}\n */\nexport function useStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: UseStreamCustomOptions<InferStateType<T>, InferBag<T, Bag>>,\n): WithClassMessages<ResolveStreamInterface<T, InferBag<T, Bag>>>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useStream(options: any): any {\n // Store this in useState to make sure we're not changing the implementation in re-renders\n const [isCustom] = useState(isCustomOptions(options));\n\n if (isCustom) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useStreamCustom(options);\n }\n\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useStreamLGP(options);\n}\n"],"mappings":";;;;AA0BA,SAAS,gBAIP,SAGmD;AACnD,QAAO,eAAe;;AA6SxB,SAAgB,UAAU,SAAmB;CAE3C,MAAM,CAAC,YAAY,SAAS,gBAAgB,QAAQ,CAAC;AAErD,KAAI,SAEF,QAAO,gBAAgB,QAAQ;AAIjC,QAAO,aAAa,QAAQ"}
|
|
1
|
+
{"version":3,"file":"stream.js","names":[],"sources":["../src/stream.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type {\n BaseMessage,\n ToolMessage as CoreToolMessage,\n AIMessage as CoreAIMessage,\n} from \"@langchain/core/messages\";\nimport type {\n BagTemplate,\n ToolCallWithResult,\n DefaultToolCall,\n} from \"@langchain/langgraph-sdk\";\nimport type {\n UseStreamOptions,\n AcceptBaseMessages,\n ResolveStreamInterface,\n ResolveStreamOptions,\n InferBag,\n InferStateType,\n MessageMetadata,\n SubagentStreamInterface,\n HistoryWithBaseMessages,\n} from \"@langchain/langgraph-sdk/ui\";\nimport { useStreamLGP } from \"./stream.lgp.js\";\nimport { useStreamCustom } from \"./stream.custom.js\";\nimport type { UseStreamCustomOptions } from \"./types.js\";\n\nfunction isCustomOptions<\n StateType extends Record<string, unknown> = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options:\n | UseStreamOptions<StateType, Bag>\n | UseStreamCustomOptions<StateType, Bag>,\n): options is UseStreamCustomOptions<StateType, Bag> {\n return \"transport\" in options;\n}\n\ntype UseStreamImplementation = typeof useStreamLGP | typeof useStreamCustom;\n\ntype AnyUseStreamOptions =\n | UseStreamOptions<Record<string, unknown>, BagTemplate>\n | UseStreamCustomOptions<Record<string, unknown>, BagTemplate>;\n\nfunction selectStreamImplementation(\n options: AnyUseStreamOptions,\n): UseStreamImplementation {\n return isCustomOptions(options) ? useStreamCustom : useStreamLGP;\n}\n\ntype ClassToolCallWithResult<T> =\n T extends ToolCallWithResult<infer TC, unknown, unknown>\n ? ToolCallWithResult<TC, CoreToolMessage, CoreAIMessage>\n : T;\n\nexport type ClassSubagentStreamInterface<\n StateType = Record<string, unknown>,\n ToolCall = DefaultToolCall,\n SubagentName extends string = string,\n> = Omit<\n SubagentStreamInterface<StateType, ToolCall, SubagentName>,\n \"messages\"\n> & {\n messages: BaseMessage[];\n};\n\n/**\n * Maps a stream interface to use @langchain/core BaseMessage class instances\n * instead of plain Message objects for the `messages` property, and remaps\n * tool call types to use @langchain/core message classes.\n */\nexport type WithClassMessages<T> = Omit<\n T,\n | \"messages\"\n | \"history\"\n | \"getMessagesMetadata\"\n | \"toolCalls\"\n | \"getToolCalls\"\n | \"submit\"\n | \"subagents\"\n | \"activeSubagents\"\n | \"getSubagent\"\n | \"getSubagentsByType\"\n | \"getSubagentsByMessage\"\n> & {\n messages: BaseMessage[];\n getMessagesMetadata: (\n message: BaseMessage,\n index?: number,\n ) => MessageMetadata<Record<string, unknown>> | undefined;\n} & (\"history\" extends keyof T\n ? { history: HistoryWithBaseMessages<T[\"history\"]> }\n : unknown) &\n (\"submit\" extends keyof T\n ? {\n submit: T extends {\n submit: (values: infer V, options?: infer O) => infer Ret;\n }\n ? (\n values:\n | AcceptBaseMessages<Exclude<V, null | undefined>>\n | null\n | undefined,\n options?: O,\n ) => Ret\n : never;\n }\n : unknown) &\n (\"toolCalls\" extends keyof T\n ? {\n toolCalls: T extends { toolCalls: (infer TC)[] }\n ? ClassToolCallWithResult<TC>[]\n : never;\n }\n : unknown) &\n (\"getToolCalls\" extends keyof T\n ? {\n getToolCalls: T extends {\n getToolCalls: (message: infer _M) => (infer TC)[];\n }\n ? (message: CoreAIMessage) => ClassToolCallWithResult<TC>[]\n : never;\n }\n : unknown) &\n (\"subagents\" extends keyof T\n ? {\n subagents: T extends {\n subagents: Map<\n string,\n SubagentStreamInterface<infer S, infer TC, infer N>\n >;\n }\n ? Map<string, ClassSubagentStreamInterface<S, TC, N>>\n : never;\n activeSubagents: T extends {\n activeSubagents: SubagentStreamInterface<\n infer S,\n infer TC,\n infer N\n >[];\n }\n ? ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n getSubagent: T extends {\n getSubagent: (\n id: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N> | undefined;\n }\n ? (\n toolCallId: string,\n ) => ClassSubagentStreamInterface<S, TC, N> | undefined\n : never;\n getSubagentsByType: T extends {\n getSubagentsByType: (\n type: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N>[];\n }\n ? (type: string) => ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n getSubagentsByMessage: T extends {\n getSubagentsByMessage: (\n id: string,\n ) => SubagentStreamInterface<infer S, infer TC, infer N>[];\n }\n ? (messageId: string) => ClassSubagentStreamInterface<S, TC, N>[]\n : never;\n }\n : unknown);\n\n/**\n * A React hook that provides seamless integration with LangGraph streaming capabilities.\n *\n * The `useStream` hook handles all the complexities of streaming, state management, and branching logic,\n * letting you focus on building great chat experiences. It provides automatic state management for\n * messages, interrupts, loading states, subagent streams, and errors.\n *\n * ## Usage with ReactAgent (recommended for createAgent users)\n *\n * When using `createAgent` from `@langchain/langgraph`, you can pass `typeof agent` as the\n * type parameter to automatically infer tool call types:\n *\n * @example\n * ```typescript\n * // In your agent file (e.g., agent.ts)\n * import { createAgent, tool } from \"langchain\";\n * import { z } from \"zod\";\n *\n * const getWeather = tool(\n * async ({ location }) => `Weather in ${location}`,\n * { name: \"get_weather\", schema: z.object({ location: z.string() }) }\n * );\n *\n * export const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [getWeather],\n * });\n *\n * // In your React component\n * import { agent } from \"./agent\";\n *\n * function Chat() {\n * // Tool calls are automatically typed from the agent's tools!\n * const stream = useStream<typeof agent>({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.toolCalls[0].call.name is typed as \"get_weather\"\n * // stream.toolCalls[0].call.args is typed as { location: string }\n * }\n * ```\n *\n * ## Usage with StateGraph (for custom LangGraph applications)\n *\n * When building custom graphs with `StateGraph`, embed your tool call types directly\n * in your state's messages property using `Message<MyToolCalls>`:\n *\n * @example\n * ```typescript\n * import { Message } from \"@langchain/langgraph-sdk\";\n *\n * // Define your tool call types as a discriminated union\n * type MyToolCalls =\n * | { name: \"search\"; args: { query: string }; id?: string }\n * | { name: \"calculate\"; args: { expression: string }; id?: string };\n *\n * // Embed tool call types in your state's messages\n * interface MyGraphState {\n * messages: Message<MyToolCalls>[];\n * context?: string;\n * }\n *\n * function Chat() {\n * const stream = useStream<MyGraphState>({\n * assistantId: \"my-graph\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.values is typed as MyGraphState\n * // stream.toolCalls[0].call.name is typed as \"search\" | \"calculate\"\n * }\n * ```\n *\n * @example\n * ```typescript\n * // With additional type configuration (interrupts, configurable)\n * interface MyGraphState {\n * messages: Message<MyToolCalls>[];\n * }\n *\n * function Chat() {\n * const stream = useStream<MyGraphState, {\n * InterruptType: { question: string };\n * ConfigurableType: { userId: string };\n * }>({\n * assistantId: \"my-graph\",\n * apiUrl: \"http://localhost:2024\",\n * });\n *\n * // stream.interrupt is typed as { question: string } | undefined\n * }\n * ```\n *\n * ## Usage with Deep Agents (subagent streaming, experimental)\n *\n * For agents that spawn subagents (nested graphs), use `filterSubagentMessages`\n * to keep the main message stream clean while tracking subagent activity separately:\n *\n * @example\n * ```typescript\n * import { useStream, SubagentStream } from \"@langchain/langgraph-sdk/react\";\n * import type { agent } from \"./agent\";\n *\n * function DeepAgentChat() {\n * const stream = useStream<typeof agent>({\n * assistantId: \"deepagent\",\n * apiUrl: \"http://localhost:2024\",\n * // Filter subagent messages from main stream\n * filterSubagentMessages: true,\n * });\n *\n * const handleSubmit = (content: string) => {\n * stream.submit(\n * { messages: [{ content, type: \"human\" }] },\n * { streamSubgraphs: true } // Enable subgraph streaming\n * );\n * };\n *\n * // Access subagent streams via stream.subagents (Map<string, SubagentStream>)\n * const subagentList = [...stream.subagents.values()];\n *\n * return (\n * <div>\n * {stream.messages.map((msg) => <Message key={msg.id} message={msg} />)}\n *\n * {subagentList.map((subagent) => (\n * <SubagentCard\n * key={subagent.id}\n * status={subagent.status} // \"pending\" | \"running\" | \"complete\" | \"error\"\n * messages={subagent.messages}\n * toolCalls={subagent.toolCalls}\n * />\n * ))}\n * </div>\n * );\n * }\n * ```\n *\n * @template T Either a ReactAgent type (with `~agentTypes`) or a state type (`Record<string, unknown>`)\n * @template Bag Type configuration bag containing:\n * - `ConfigurableType`: Type for the `config.configurable` property\n * - `InterruptType`: Type for interrupt values\n * - `CustomEventType`: Type for custom events\n * - `UpdateType`: Type for the submit function updates\n *\n * @see {@link https://docs.langchain.com/langgraph-platform/use-stream-react | LangGraph React Integration Guide}\n */\nexport function useStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: ResolveStreamOptions<T, InferBag<T, Bag>>,\n): WithClassMessages<ResolveStreamInterface<T, InferBag<T, Bag>>>;\n\n/**\n * A React hook that provides seamless integration with LangGraph streaming capabilities.\n *\n * The `useStream` hook handles all the complexities of streaming, state management, and branching logic,\n * letting you focus on building great chat experiences. It provides automatic state management for\n * messages, interrupts, loading states, and errors.\n *\n * @template T Either a ReactAgent type (with `~agentTypes`) or a state type (`Record<string, unknown>`)\n * @template Bag Type configuration bag containing:\n * - `ConfigurableType`: Type for the `config.configurable` property\n * - `InterruptType`: Type for interrupt values\n * - `CustomEventType`: Type for custom events\n * - `UpdateType`: Type for the submit function updates\n *\n * @see {@link https://docs.langchain.com/langgraph-platform/use-stream-react | LangGraph React Integration Guide}\n */\nexport function useStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: UseStreamCustomOptions<InferStateType<T>, InferBag<T, Bag>>,\n): WithClassMessages<ResolveStreamInterface<T, InferBag<T, Bag>>>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useStream(options: any): any {\n // Keep implementation stable for the lifetime of this hook instance.\n const [useSelectedStream] = useState(() =>\n selectStreamImplementation(options),\n );\n return useSelectedStream(options);\n}\n"],"mappings":";;;;AA0BA,SAAS,gBAIP,SAGmD;AACnD,QAAO,eAAe;;AASxB,SAAS,2BACP,SACyB;AACzB,QAAO,gBAAgB,QAAQ,GAAG,kBAAkB;;AA6StD,SAAgB,UAAU,SAAmB;CAE3C,MAAM,CAAC,qBAAqB,eAC1B,2BAA2B,QAAQ,CACpC;AACD,QAAO,kBAAkB,QAAQ"}
|
package/dist/suspense-stream.cjs
CHANGED
|
@@ -3,7 +3,10 @@ const require_stream_lgp = require("./stream.lgp.cjs");
|
|
|
3
3
|
let react = require("react");
|
|
4
4
|
let _langchain_langgraph_sdk_client = require("@langchain/langgraph-sdk/client");
|
|
5
5
|
//#region src/suspense-stream.tsx
|
|
6
|
-
const
|
|
6
|
+
const defaultSuspenseCache = /* @__PURE__ */ new Map();
|
|
7
|
+
function createSuspenseCache() {
|
|
8
|
+
return /* @__PURE__ */ new Map();
|
|
9
|
+
}
|
|
7
10
|
function getCacheKey(client, threadId, limit) {
|
|
8
11
|
return `suspense:${(0, _langchain_langgraph_sdk_client.getClientConfigHash)(client)}:${threadId}:${limit}`;
|
|
9
12
|
}
|
|
@@ -15,25 +18,25 @@ function fetchThreadHistory(client, threadId, options) {
|
|
|
15
18
|
const limit = typeof options?.limit === "number" ? options.limit : 10;
|
|
16
19
|
return client.threads.getHistory(threadId, { limit });
|
|
17
20
|
}
|
|
18
|
-
function getOrCreateCacheEntry(client, threadId, limit) {
|
|
21
|
+
function getOrCreateCacheEntry(cache, client, threadId, limit) {
|
|
19
22
|
const key = getCacheKey(client, threadId, limit);
|
|
20
|
-
let entry =
|
|
23
|
+
let entry = cache.get(key);
|
|
21
24
|
if (!entry) {
|
|
22
25
|
entry = {
|
|
23
26
|
status: "pending",
|
|
24
27
|
promise: fetchThreadHistory(client, threadId, { limit }).then((data) => {
|
|
25
|
-
|
|
28
|
+
cache.set(key, {
|
|
26
29
|
status: "resolved",
|
|
27
30
|
data
|
|
28
31
|
});
|
|
29
32
|
}).catch((error) => {
|
|
30
|
-
|
|
33
|
+
cache.set(key, {
|
|
31
34
|
status: "rejected",
|
|
32
35
|
error
|
|
33
36
|
});
|
|
34
37
|
})
|
|
35
38
|
};
|
|
36
|
-
|
|
39
|
+
cache.set(key, entry);
|
|
37
40
|
}
|
|
38
41
|
return entry;
|
|
39
42
|
}
|
|
@@ -58,10 +61,11 @@ function getOrCreateCacheEntry(client, threadId, limit) {
|
|
|
58
61
|
* </ErrorBoundary>
|
|
59
62
|
* ```
|
|
60
63
|
*/
|
|
61
|
-
function invalidateSuspenseCache() {
|
|
62
|
-
|
|
64
|
+
function invalidateSuspenseCache(cache = defaultSuspenseCache) {
|
|
65
|
+
cache.clear();
|
|
63
66
|
}
|
|
64
67
|
function useSuspenseStream(options) {
|
|
68
|
+
const cache = options.suspenseCache ?? defaultSuspenseCache;
|
|
65
69
|
const client = (0, react.useMemo)(() => options.client ?? new _langchain_langgraph_sdk_client.Client({
|
|
66
70
|
apiUrl: options.apiUrl,
|
|
67
71
|
apiKey: options.apiKey,
|
|
@@ -78,7 +82,7 @@ function useSuspenseStream(options) {
|
|
|
78
82
|
const historyLimit = typeof options.fetchStateHistory === "object" && options.fetchStateHistory != null ? options.fetchStateHistory.limit ?? false : options.fetchStateHistory ?? false;
|
|
79
83
|
const needsHistoryFetch = threadId != null && options.thread == null;
|
|
80
84
|
let cacheEntry;
|
|
81
|
-
if (needsHistoryFetch) cacheEntry = getOrCreateCacheEntry(client, threadId, historyLimit);
|
|
85
|
+
if (needsHistoryFetch) cacheEntry = getOrCreateCacheEntry(cache, client, threadId, historyLimit);
|
|
82
86
|
const cachedData = cacheEntry?.status === "resolved" ? cacheEntry.data : void 0;
|
|
83
87
|
const cachedDataRef = (0, react.useRef)(cachedData);
|
|
84
88
|
if (cachedData != null) cachedDataRef.current = cachedData;
|
|
@@ -89,7 +93,7 @@ function useSuspenseStream(options) {
|
|
|
89
93
|
try {
|
|
90
94
|
const data = await fetchThreadHistory(client, fetchId, { limit: historyLimit });
|
|
91
95
|
const key = getCacheKey(client, fetchId, historyLimit);
|
|
92
|
-
|
|
96
|
+
cache.set(key, {
|
|
93
97
|
status: "resolved",
|
|
94
98
|
data
|
|
95
99
|
});
|
|
@@ -100,6 +104,7 @@ function useSuspenseStream(options) {
|
|
|
100
104
|
return;
|
|
101
105
|
}
|
|
102
106
|
}, [
|
|
107
|
+
cache,
|
|
103
108
|
client,
|
|
104
109
|
threadId,
|
|
105
110
|
historyLimit
|
|
@@ -127,7 +132,7 @@ function useSuspenseStream(options) {
|
|
|
127
132
|
if (cacheEntry.status === "pending") throw cacheEntry.promise;
|
|
128
133
|
if (cacheEntry.status === "rejected") {
|
|
129
134
|
const key = getCacheKey(client, threadId, historyLimit);
|
|
130
|
-
|
|
135
|
+
cache.delete(key);
|
|
131
136
|
throw cacheEntry.error instanceof Error ? cacheEntry.error : new Error(String(cacheEntry.error));
|
|
132
137
|
}
|
|
133
138
|
}
|
|
@@ -172,15 +177,26 @@ function useSuspenseStream(options) {
|
|
|
172
177
|
submit: stream.submit,
|
|
173
178
|
switchThread: stream.switchThread,
|
|
174
179
|
joinStream: stream.joinStream,
|
|
175
|
-
branch
|
|
180
|
+
get branch() {
|
|
181
|
+
return stream.branch;
|
|
182
|
+
},
|
|
176
183
|
setBranch: stream.setBranch,
|
|
177
|
-
client
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
184
|
+
get client() {
|
|
185
|
+
return stream.client;
|
|
186
|
+
},
|
|
187
|
+
get assistantId() {
|
|
188
|
+
return stream.assistantId;
|
|
189
|
+
},
|
|
190
|
+
get queue() {
|
|
191
|
+
return stream.queue;
|
|
192
|
+
},
|
|
193
|
+
get isStreaming() {
|
|
194
|
+
return stream.isLoading;
|
|
195
|
+
}
|
|
181
196
|
};
|
|
182
197
|
}
|
|
183
198
|
//#endregion
|
|
199
|
+
exports.createSuspenseCache = createSuspenseCache;
|
|
184
200
|
exports.invalidateSuspenseCache = invalidateSuspenseCache;
|
|
185
201
|
exports.useSuspenseStream = useSuspenseStream;
|
|
186
202
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suspense-stream.cjs","names":["Client","useStreamLGP"],"sources":["../src/suspense-stream.tsx"],"sourcesContent":["/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */\n\n\"use client\";\n\nimport { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { ThreadState, BagTemplate } from \"@langchain/langgraph-sdk\";\nimport { Client, getClientConfigHash } from \"@langchain/langgraph-sdk/client\";\nimport type {\n UseStreamThread,\n ResolveStreamOptions,\n ResolveStreamInterface,\n InferBag,\n} from \"@langchain/langgraph-sdk/ui\";\nimport { useStreamLGP } from \"./stream.lgp.js\";\nimport type { WithClassMessages } from \"./stream.js\";\n\n// ---------------------------------------------------------------------------\n// Suspense cache\n// ---------------------------------------------------------------------------\n\ntype SuspenseCacheEntry<T> =\n | { status: \"pending\"; promise: Promise<void> }\n | { status: \"resolved\"; data: T }\n | { status: \"rejected\"; error: unknown };\n\nconst threadCache = new Map<string, SuspenseCacheEntry<unknown>>();\n\nfunction getCacheKey(\n client: Client,\n threadId: string,\n limit: boolean | number,\n): string {\n return `suspense:${getClientConfigHash(client)}:${threadId}:${limit}`;\n}\n\nfunction fetchThreadHistory<StateType extends Record<string, unknown>>(\n client: Client,\n threadId: string,\n options?: { limit?: boolean | number },\n): Promise<ThreadState<StateType>[]> {\n if (options?.limit === false) {\n return client.threads.getState<StateType>(threadId).then((state) => {\n if (state.checkpoint == null) return [];\n return [state];\n });\n }\n\n const limit = typeof options?.limit === \"number\" ? options.limit : 10;\n return client.threads.getHistory<StateType>(threadId, { limit });\n}\n\nfunction getOrCreateCacheEntry<StateType extends Record<string, unknown>>(\n client: Client,\n threadId: string,\n limit: boolean | number,\n): SuspenseCacheEntry<ThreadState<StateType>[]> {\n const key = getCacheKey(client, threadId, limit);\n let entry = threadCache.get(key) as\n | SuspenseCacheEntry<ThreadState<StateType>[]>\n | undefined;\n\n if (!entry) {\n // Start fetch. The promise always resolves (never rejects) so React\n // Suspense correctly waits for it and then retries the render.\n const promise = fetchThreadHistory<StateType>(client, threadId, { limit })\n .then((data) => {\n threadCache.set(key, { status: \"resolved\", data });\n })\n .catch((error: unknown) => {\n threadCache.set(key, { status: \"rejected\", error });\n });\n\n entry = { status: \"pending\", promise };\n threadCache.set(key, entry);\n }\n\n return entry;\n}\n\n/**\n * Clear the internal Suspense cache used by {@link useSuspenseStream}.\n *\n * Call this from an Error Boundary's `onReset` callback so that a retry\n * triggers a fresh thread-history fetch rather than re-throwing the\n * cached error.\n *\n * @example\n * ```tsx\n * <ErrorBoundary\n * onReset={() => invalidateSuspenseCache()}\n * fallbackRender={({ resetErrorBoundary }) => (\n * <button onClick={resetErrorBoundary}>Retry</button>\n * )}\n * >\n * <Suspense fallback={<Spinner />}>\n * <Chat />\n * </Suspense>\n * </ErrorBoundary>\n * ```\n */\nexport function invalidateSuspenseCache(): void {\n threadCache.clear();\n}\n\n// ---------------------------------------------------------------------------\n// Return-type helper\n// ---------------------------------------------------------------------------\n\ntype WithSuspense<T> = Omit<T, \"isLoading\" | \"error\" | \"isThreadLoading\"> & {\n isStreaming: boolean;\n};\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * A Suspense-compatible variant of {@link useStream} for LangGraph Platform.\n *\n * `useSuspenseStream` suspends the component while the initial thread\n * history is being fetched and throws errors to the nearest React Error\n * Boundary. During active streaming the component stays rendered and\n * `isStreaming` indicates whether tokens are arriving.\n *\n * @example\n * ```tsx\n * <ErrorBoundary fallback={<ErrorDisplay />}>\n * <Suspense fallback={<Spinner />}>\n * <Chat />\n * </Suspense>\n * </ErrorBoundary>\n *\n * function Chat() {\n * const { messages, submit, isStreaming } = useSuspenseStream({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n * return <MessageList messages={messages} />;\n * }\n * ```\n *\n * @template T - Either a ReactAgent / DeepAgent type or a state record type.\n * @template Bag - Type configuration bag (ConfigurableType, InterruptType, …).\n */\nexport function useSuspenseStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: ResolveStreamOptions<T, InferBag<T, Bag>>,\n): WithClassMessages<WithSuspense<ResolveStreamInterface<T, InferBag<T, Bag>>>>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useSuspenseStream(options: any): any {\n type StateType = Record<string, unknown>;\n\n // ---- client (needed before useStreamLGP for cache key derivation) ----\n const client = useMemo(\n () =>\n options.client ??\n new Client({\n apiUrl: options.apiUrl,\n apiKey: options.apiKey,\n callerOptions: options.callerOptions,\n defaultHeaders: options.defaultHeaders,\n }),\n [\n options.client,\n options.apiKey,\n options.apiUrl,\n options.callerOptions,\n options.defaultHeaders,\n ],\n );\n\n const { threadId } = options;\n\n const historyLimit: boolean | number =\n typeof options.fetchStateHistory === \"object\" &&\n options.fetchStateHistory != null\n ? (options.fetchStateHistory.limit ?? false)\n : (options.fetchStateHistory ?? false);\n\n // Only manage history via the suspense cache when the caller hasn't\n // supplied an external `thread` and there's a threadId to load.\n const needsHistoryFetch = threadId != null && options.thread == null;\n\n // ---- suspense cache lookup (synchronous, may create fetch) ----\n let cacheEntry: SuspenseCacheEntry<ThreadState<StateType>[]> | undefined;\n\n if (needsHistoryFetch) {\n cacheEntry = getOrCreateCacheEntry<StateType>(\n client,\n threadId,\n historyLimit,\n );\n }\n\n const cachedData =\n cacheEntry?.status === \"resolved\" ? cacheEntry.data : undefined;\n\n // ---- mutable ref so `mutate` always writes the freshest data ----\n const cachedDataRef = useRef(cachedData);\n if (cachedData != null) {\n cachedDataRef.current = cachedData;\n }\n\n // Re-render trigger after external mutate calls.\n const [, setMutateVersion] = useState(0);\n\n const mutate = useCallback(\n async (\n mutateId?: string,\n ): Promise<ThreadState<StateType>[] | null | undefined> => {\n const fetchId = mutateId ?? threadId;\n if (!fetchId) return undefined;\n try {\n const data = await fetchThreadHistory<StateType>(client, fetchId, {\n limit: historyLimit,\n });\n const key = getCacheKey(client, fetchId, historyLimit);\n threadCache.set(key, { status: \"resolved\", data });\n cachedDataRef.current = data;\n setMutateVersion((v) => v + 1);\n return data;\n } catch {\n return undefined;\n }\n },\n [client, threadId, historyLimit],\n );\n\n // ---- build thread override for useStreamLGP ----\n const thread: UseStreamThread<StateType> | undefined = useMemo(() => {\n if (!needsHistoryFetch) return options.thread;\n return {\n data: cachedDataRef.current,\n error: undefined,\n isLoading: false,\n mutate,\n };\n // `cachedData` is included so the memo recomputes when the cache\n // transitions from pending → resolved across suspend/retry cycles.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [needsHistoryFetch, options.thread, cachedData, mutate]);\n\n // ---- delegate to useStreamLGP (must always run – Rules of Hooks) ----\n const stream = useStreamLGP({\n ...options,\n client,\n thread,\n });\n\n // ---- post-hook: suspend or throw ----\n\n // Suspend while thread history is loading, but only when the stream\n // itself is idle. If an active stream is running (e.g. the thread was\n // just created during submit), suspending would discard the stream\n // state, so we skip it.\n if (needsHistoryFetch && cacheEntry && !stream.isLoading) {\n if (cacheEntry.status === \"pending\") {\n // eslint-disable-next-line @typescript-eslint/no-throw-literal\n throw cacheEntry.promise;\n }\n if (cacheEntry.status === \"rejected\") {\n // Clear cache so a subsequent retry (ErrorBoundary reset) starts\n // a fresh fetch instead of re-throwing the stale error.\n const key = getCacheKey(client, threadId!, historyLimit);\n threadCache.delete(key);\n // eslint-disable-next-line no-instanceof/no-instanceof\n throw cacheEntry.error instanceof Error\n ? cacheEntry.error\n : new Error(String(cacheEntry.error));\n }\n }\n\n // Throw non-streaming errors to the nearest Error Boundary.\n if (stream.error != null && !stream.isLoading) {\n // eslint-disable-next-line no-instanceof/no-instanceof\n throw stream.error instanceof Error\n ? stream.error\n : new Error(String(stream.error));\n }\n\n // Build return object explicitly to avoid triggering throwing getters\n // (e.g. `history` throws when `fetchStateHistory` is not set).\n return {\n get values() {\n return stream.values;\n },\n get messages() {\n return stream.messages;\n },\n get toolCalls() {\n return stream.toolCalls;\n },\n get toolProgress() {\n return stream.toolProgress;\n },\n getToolCalls: stream.getToolCalls.bind(stream),\n get interrupt() {\n return stream.interrupt;\n },\n get interrupts() {\n return stream.interrupts;\n },\n get subagents() {\n return stream.subagents;\n },\n get activeSubagents() {\n return stream.activeSubagents;\n },\n getSubagent: stream.getSubagent.bind(stream),\n getSubagentsByType: stream.getSubagentsByType.bind(stream),\n getSubagentsByMessage: stream.getSubagentsByMessage.bind(stream),\n getMessagesMetadata: stream.getMessagesMetadata.bind(stream),\n get history() {\n return stream.history;\n },\n get experimental_branchTree() {\n return stream.experimental_branchTree;\n },\n stop: stream.stop,\n submit: stream.submit,\n switchThread: stream.switchThread,\n joinStream: stream.joinStream,\n branch: stream.branch,\n setBranch: stream.setBranch,\n client: stream.client,\n assistantId: stream.assistantId,\n queue: stream.queue,\n isStreaming: stream.isLoading,\n };\n}\n"],"mappings":";;;;;AAyBA,MAAM,8BAAc,IAAI,KAA0C;AAElE,SAAS,YACP,QACA,UACA,OACQ;AACR,QAAO,aAAA,GAAA,gCAAA,qBAAgC,OAAO,CAAC,GAAG,SAAS,GAAG;;AAGhE,SAAS,mBACP,QACA,UACA,SACmC;AACnC,KAAI,SAAS,UAAU,MACrB,QAAO,OAAO,QAAQ,SAAoB,SAAS,CAAC,MAAM,UAAU;AAClE,MAAI,MAAM,cAAc,KAAM,QAAO,EAAE;AACvC,SAAO,CAAC,MAAM;GACd;CAGJ,MAAM,QAAQ,OAAO,SAAS,UAAU,WAAW,QAAQ,QAAQ;AACnE,QAAO,OAAO,QAAQ,WAAsB,UAAU,EAAE,OAAO,CAAC;;AAGlE,SAAS,sBACP,QACA,UACA,OAC8C;CAC9C,MAAM,MAAM,YAAY,QAAQ,UAAU,MAAM;CAChD,IAAI,QAAQ,YAAY,IAAI,IAAI;AAIhC,KAAI,CAAC,OAAO;AAWV,UAAQ;GAAE,QAAQ;GAAW,SARb,mBAA8B,QAAQ,UAAU,EAAE,OAAO,CAAC,CACvE,MAAM,SAAS;AACd,gBAAY,IAAI,KAAK;KAAE,QAAQ;KAAY;KAAM,CAAC;KAClD,CACD,OAAO,UAAmB;AACzB,gBAAY,IAAI,KAAK;KAAE,QAAQ;KAAY;KAAO,CAAC;KACnD;GAEkC;AACtC,cAAY,IAAI,KAAK,MAAM;;AAG7B,QAAO;;;;;;;;;;;;;;;;;;;;;;;AAwBT,SAAgB,0BAAgC;AAC9C,aAAY,OAAO;;AAmDrB,SAAgB,kBAAkB,SAAmB;CAInD,MAAM,UAAA,GAAA,MAAA,eAEF,QAAQ,UACR,IAAIA,gCAAAA,OAAO;EACT,QAAQ,QAAQ;EAChB,QAAQ,QAAQ;EAChB,eAAe,QAAQ;EACvB,gBAAgB,QAAQ;EACzB,CAAC,EACJ;EACE,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACT,CACF;CAED,MAAM,EAAE,aAAa;CAErB,MAAM,eACJ,OAAO,QAAQ,sBAAsB,YACrC,QAAQ,qBAAqB,OACxB,QAAQ,kBAAkB,SAAS,QACnC,QAAQ,qBAAqB;CAIpC,MAAM,oBAAoB,YAAY,QAAQ,QAAQ,UAAU;CAGhE,IAAI;AAEJ,KAAI,kBACF,cAAa,sBACX,QACA,UACA,aACD;CAGH,MAAM,aACJ,YAAY,WAAW,aAAa,WAAW,OAAO,KAAA;CAGxD,MAAM,iBAAA,GAAA,MAAA,QAAuB,WAAW;AACxC,KAAI,cAAc,KAChB,eAAc,UAAU;CAI1B,MAAM,GAAG,qBAAA,GAAA,MAAA,UAA6B,EAAE;CAExC,MAAM,UAAA,GAAA,MAAA,aACJ,OACE,aACyD;EACzD,MAAM,UAAU,YAAY;AAC5B,MAAI,CAAC,QAAS,QAAO,KAAA;AACrB,MAAI;GACF,MAAM,OAAO,MAAM,mBAA8B,QAAQ,SAAS,EAChE,OAAO,cACR,CAAC;GACF,MAAM,MAAM,YAAY,QAAQ,SAAS,aAAa;AACtD,eAAY,IAAI,KAAK;IAAE,QAAQ;IAAY;IAAM,CAAC;AAClD,iBAAc,UAAU;AACxB,qBAAkB,MAAM,IAAI,EAAE;AAC9B,UAAO;UACD;AACN;;IAGJ;EAAC;EAAQ;EAAU;EAAa,CACjC;CAGD,MAAM,UAAA,GAAA,MAAA,eAA+D;AACnE,MAAI,CAAC,kBAAmB,QAAO,QAAQ;AACvC,SAAO;GACL,MAAM,cAAc;GACpB,OAAO,KAAA;GACP,WAAW;GACX;GACD;IAIA;EAAC;EAAmB,QAAQ;EAAQ;EAAY;EAAO,CAAC;CAG3D,MAAM,SAASC,mBAAAA,aAAa;EAC1B,GAAG;EACH;EACA;EACD,CAAC;AAQF,KAAI,qBAAqB,cAAc,CAAC,OAAO,WAAW;AACxD,MAAI,WAAW,WAAW,UAExB,OAAM,WAAW;AAEnB,MAAI,WAAW,WAAW,YAAY;GAGpC,MAAM,MAAM,YAAY,QAAQ,UAAW,aAAa;AACxD,eAAY,OAAO,IAAI;AAEvB,SAAM,WAAW,iBAAiB,QAC9B,WAAW,QACX,IAAI,MAAM,OAAO,WAAW,MAAM,CAAC;;;AAK3C,KAAI,OAAO,SAAS,QAAQ,CAAC,OAAO,UAElC,OAAM,OAAO,iBAAiB,QAC1B,OAAO,QACP,IAAI,MAAM,OAAO,OAAO,MAAM,CAAC;AAKrC,QAAO;EACL,IAAI,SAAS;AACX,UAAO,OAAO;;EAEhB,IAAI,WAAW;AACb,UAAO,OAAO;;EAEhB,IAAI,YAAY;AACd,UAAO,OAAO;;EAEhB,IAAI,eAAe;AACjB,UAAO,OAAO;;EAEhB,cAAc,OAAO,aAAa,KAAK,OAAO;EAC9C,IAAI,YAAY;AACd,UAAO,OAAO;;EAEhB,IAAI,aAAa;AACf,UAAO,OAAO;;EAEhB,IAAI,YAAY;AACd,UAAO,OAAO;;EAEhB,IAAI,kBAAkB;AACpB,UAAO,OAAO;;EAEhB,aAAa,OAAO,YAAY,KAAK,OAAO;EAC5C,oBAAoB,OAAO,mBAAmB,KAAK,OAAO;EAC1D,uBAAuB,OAAO,sBAAsB,KAAK,OAAO;EAChE,qBAAqB,OAAO,oBAAoB,KAAK,OAAO;EAC5D,IAAI,UAAU;AACZ,UAAO,OAAO;;EAEhB,IAAI,0BAA0B;AAC5B,UAAO,OAAO;;EAEhB,MAAM,OAAO;EACb,QAAQ,OAAO;EACf,cAAc,OAAO;EACrB,YAAY,OAAO;EACnB,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,QAAQ,OAAO;EACf,aAAa,OAAO;EACpB,OAAO,OAAO;EACd,aAAa,OAAO;EACrB"}
|
|
1
|
+
{"version":3,"file":"suspense-stream.cjs","names":["Client","useStreamLGP"],"sources":["../src/suspense-stream.tsx"],"sourcesContent":["/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */\n\n\"use client\";\n\nimport { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { ThreadState, BagTemplate } from \"@langchain/langgraph-sdk\";\nimport { Client, getClientConfigHash } from \"@langchain/langgraph-sdk/client\";\nimport type {\n UseStreamThread,\n ResolveStreamOptions,\n ResolveStreamInterface,\n InferBag,\n} from \"@langchain/langgraph-sdk/ui\";\nimport { useStreamLGP } from \"./stream.lgp.js\";\nimport type { WithClassMessages } from \"./stream.js\";\n\n// ---------------------------------------------------------------------------\n// Suspense cache\n// ---------------------------------------------------------------------------\n\nexport type SuspenseCacheEntry<T> =\n | { status: \"pending\"; promise: Promise<void> }\n | { status: \"resolved\"; data: T }\n | { status: \"rejected\"; error: unknown };\n\nexport type SuspenseCache = Map<string, SuspenseCacheEntry<unknown>>;\n\nconst defaultSuspenseCache: SuspenseCache = new Map();\n\nexport function createSuspenseCache(): SuspenseCache {\n return new Map();\n}\n\nfunction getCacheKey(\n client: Client,\n threadId: string,\n limit: boolean | number,\n): string {\n return `suspense:${getClientConfigHash(client)}:${threadId}:${limit}`;\n}\n\nfunction fetchThreadHistory<StateType extends Record<string, unknown>>(\n client: Client,\n threadId: string,\n options?: { limit?: boolean | number },\n): Promise<ThreadState<StateType>[]> {\n if (options?.limit === false) {\n return client.threads.getState<StateType>(threadId).then((state) => {\n if (state.checkpoint == null) return [];\n return [state];\n });\n }\n\n const limit = typeof options?.limit === \"number\" ? options.limit : 10;\n return client.threads.getHistory<StateType>(threadId, { limit });\n}\n\nfunction getOrCreateCacheEntry<StateType extends Record<string, unknown>>(\n cache: SuspenseCache,\n client: Client,\n threadId: string,\n limit: boolean | number,\n): SuspenseCacheEntry<ThreadState<StateType>[]> {\n const key = getCacheKey(client, threadId, limit);\n let entry = cache.get(key) as\n | SuspenseCacheEntry<ThreadState<StateType>[]>\n | undefined;\n\n if (!entry) {\n // Start fetch. The promise always resolves (never rejects) so React\n // Suspense correctly waits for it and then retries the render.\n const promise = fetchThreadHistory<StateType>(client, threadId, { limit })\n .then((data) => {\n cache.set(key, { status: \"resolved\", data });\n })\n .catch((error: unknown) => {\n cache.set(key, { status: \"rejected\", error });\n });\n\n entry = { status: \"pending\", promise };\n cache.set(key, entry);\n }\n\n return entry;\n}\n\n/**\n * Clear the internal Suspense cache used by {@link useSuspenseStream}.\n *\n * Call this from an Error Boundary's `onReset` callback so that a retry\n * triggers a fresh thread-history fetch rather than re-throwing the\n * cached error.\n *\n * @example\n * ```tsx\n * <ErrorBoundary\n * onReset={() => invalidateSuspenseCache()}\n * fallbackRender={({ resetErrorBoundary }) => (\n * <button onClick={resetErrorBoundary}>Retry</button>\n * )}\n * >\n * <Suspense fallback={<Spinner />}>\n * <Chat />\n * </Suspense>\n * </ErrorBoundary>\n * ```\n */\nexport function invalidateSuspenseCache(\n cache: SuspenseCache = defaultSuspenseCache,\n): void {\n cache.clear();\n}\n\n// ---------------------------------------------------------------------------\n// Return-type helper\n// ---------------------------------------------------------------------------\n\ntype WithSuspense<T> = Omit<T, \"isLoading\" | \"error\" | \"isThreadLoading\"> & {\n isStreaming: boolean;\n};\n\ntype UseSuspenseStreamOptions<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n> = ResolveStreamOptions<T, InferBag<T, Bag>> & {\n /**\n * Optional cache store used by Suspense history prefetching.\n * Provide a custom cache in tests to avoid cross-test cache sharing.\n */\n suspenseCache?: SuspenseCache;\n};\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * A Suspense-compatible variant of {@link useStream} for LangGraph Platform.\n *\n * `useSuspenseStream` suspends the component while the initial thread\n * history is being fetched and throws errors to the nearest React Error\n * Boundary. During active streaming the component stays rendered and\n * `isStreaming` indicates whether tokens are arriving.\n *\n * @example\n * ```tsx\n * <ErrorBoundary fallback={<ErrorDisplay />}>\n * <Suspense fallback={<Spinner />}>\n * <Chat />\n * </Suspense>\n * </ErrorBoundary>\n *\n * function Chat() {\n * const { messages, submit, isStreaming } = useSuspenseStream({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n * return <MessageList messages={messages} />;\n * }\n * ```\n *\n * @template T - Either a ReactAgent / DeepAgent type or a state record type.\n * @template Bag - Type configuration bag (ConfigurableType, InterruptType, …).\n */\nexport function useSuspenseStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: UseSuspenseStreamOptions<T, InferBag<T, Bag>>,\n): WithClassMessages<WithSuspense<ResolveStreamInterface<T, InferBag<T, Bag>>>>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useSuspenseStream(options: any): any {\n type StateType = Record<string, unknown>;\n const cache: SuspenseCache = options.suspenseCache ?? defaultSuspenseCache;\n\n // ---- client (needed before useStreamLGP for cache key derivation) ----\n const client = useMemo(\n () =>\n options.client ??\n new Client({\n apiUrl: options.apiUrl,\n apiKey: options.apiKey,\n callerOptions: options.callerOptions,\n defaultHeaders: options.defaultHeaders,\n }),\n [\n options.client,\n options.apiKey,\n options.apiUrl,\n options.callerOptions,\n options.defaultHeaders,\n ],\n );\n\n const { threadId } = options;\n\n const historyLimit: boolean | number =\n typeof options.fetchStateHistory === \"object\" &&\n options.fetchStateHistory != null\n ? (options.fetchStateHistory.limit ?? false)\n : (options.fetchStateHistory ?? false);\n\n // Only manage history via the suspense cache when the caller hasn't\n // supplied an external `thread` and there's a threadId to load.\n const needsHistoryFetch = threadId != null && options.thread == null;\n\n // ---- suspense cache lookup (synchronous, may create fetch) ----\n let cacheEntry: SuspenseCacheEntry<ThreadState<StateType>[]> | undefined;\n\n if (needsHistoryFetch) {\n cacheEntry = getOrCreateCacheEntry<StateType>(\n cache,\n client,\n threadId,\n historyLimit,\n );\n }\n\n const cachedData =\n cacheEntry?.status === \"resolved\" ? cacheEntry.data : undefined;\n\n // ---- mutable ref so `mutate` always writes the freshest data ----\n const cachedDataRef = useRef(cachedData);\n if (cachedData != null) {\n cachedDataRef.current = cachedData;\n }\n\n // Re-render trigger after external mutate calls.\n const [, setMutateVersion] = useState(0);\n\n const mutate = useCallback(\n async (\n mutateId?: string,\n ): Promise<ThreadState<StateType>[] | null | undefined> => {\n const fetchId = mutateId ?? threadId;\n if (!fetchId) return undefined;\n try {\n const data = await fetchThreadHistory<StateType>(client, fetchId, {\n limit: historyLimit,\n });\n const key = getCacheKey(client, fetchId, historyLimit);\n cache.set(key, { status: \"resolved\", data });\n cachedDataRef.current = data;\n setMutateVersion((v) => v + 1);\n return data;\n } catch {\n return undefined;\n }\n },\n [cache, client, threadId, historyLimit],\n );\n\n // ---- build thread override for useStreamLGP ----\n const thread: UseStreamThread<StateType> | undefined = useMemo(() => {\n if (!needsHistoryFetch) return options.thread;\n return {\n data: cachedDataRef.current,\n error: undefined,\n isLoading: false,\n mutate,\n };\n // `cachedData` is included so the memo recomputes when the cache\n // transitions from pending → resolved across suspend/retry cycles.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [needsHistoryFetch, options.thread, cachedData, mutate]);\n\n // ---- delegate to useStreamLGP (must always run – Rules of Hooks) ----\n const stream = useStreamLGP({\n ...options,\n client,\n thread,\n });\n\n // ---- post-hook: suspend or throw ----\n\n // Suspend while thread history is loading, but only when the stream\n // itself is idle. If an active stream is running (e.g. the thread was\n // just created during submit), suspending would discard the stream\n // state, so we skip it.\n if (needsHistoryFetch && cacheEntry && !stream.isLoading) {\n if (cacheEntry.status === \"pending\") {\n // eslint-disable-next-line @typescript-eslint/no-throw-literal\n throw cacheEntry.promise;\n }\n if (cacheEntry.status === \"rejected\") {\n // Clear cache so a subsequent retry (ErrorBoundary reset) starts\n // a fresh fetch instead of re-throwing the stale error.\n const key = getCacheKey(client, threadId!, historyLimit);\n cache.delete(key);\n // eslint-disable-next-line no-instanceof/no-instanceof\n throw cacheEntry.error instanceof Error\n ? cacheEntry.error\n : new Error(String(cacheEntry.error));\n }\n }\n\n // Throw non-streaming errors to the nearest Error Boundary.\n if (stream.error != null && !stream.isLoading) {\n // eslint-disable-next-line no-instanceof/no-instanceof\n throw stream.error instanceof Error\n ? stream.error\n : new Error(String(stream.error));\n }\n\n // Build return object explicitly to avoid triggering throwing getters\n // (e.g. `history` throws when `fetchStateHistory` is not set).\n return {\n get values() {\n return stream.values;\n },\n get messages() {\n return stream.messages;\n },\n get toolCalls() {\n return stream.toolCalls;\n },\n get toolProgress() {\n return stream.toolProgress;\n },\n getToolCalls: stream.getToolCalls.bind(stream),\n get interrupt() {\n return stream.interrupt;\n },\n get interrupts() {\n return stream.interrupts;\n },\n get subagents() {\n return stream.subagents;\n },\n get activeSubagents() {\n return stream.activeSubagents;\n },\n getSubagent: stream.getSubagent.bind(stream),\n getSubagentsByType: stream.getSubagentsByType.bind(stream),\n getSubagentsByMessage: stream.getSubagentsByMessage.bind(stream),\n getMessagesMetadata: stream.getMessagesMetadata.bind(stream),\n get history() {\n return stream.history;\n },\n get experimental_branchTree() {\n return stream.experimental_branchTree;\n },\n stop: stream.stop,\n submit: stream.submit,\n switchThread: stream.switchThread,\n joinStream: stream.joinStream,\n get branch() {\n return stream.branch;\n },\n setBranch: stream.setBranch,\n get client() {\n return stream.client;\n },\n get assistantId() {\n return stream.assistantId;\n },\n get queue() {\n return stream.queue;\n },\n get isStreaming() {\n return stream.isLoading;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -3,6 +3,18 @@ import { BagTemplate } from "@langchain/langgraph-sdk";
|
|
|
3
3
|
import { InferBag, ResolveStreamInterface, ResolveStreamOptions } from "@langchain/langgraph-sdk/ui";
|
|
4
4
|
|
|
5
5
|
//#region src/suspense-stream.d.ts
|
|
6
|
+
type SuspenseCacheEntry<T> = {
|
|
7
|
+
status: "pending";
|
|
8
|
+
promise: Promise<void>;
|
|
9
|
+
} | {
|
|
10
|
+
status: "resolved";
|
|
11
|
+
data: T;
|
|
12
|
+
} | {
|
|
13
|
+
status: "rejected";
|
|
14
|
+
error: unknown;
|
|
15
|
+
};
|
|
16
|
+
type SuspenseCache = Map<string, SuspenseCacheEntry<unknown>>;
|
|
17
|
+
declare function createSuspenseCache(): SuspenseCache;
|
|
6
18
|
/**
|
|
7
19
|
* Clear the internal Suspense cache used by {@link useSuspenseStream}.
|
|
8
20
|
*
|
|
@@ -24,10 +36,17 @@ import { InferBag, ResolveStreamInterface, ResolveStreamOptions } from "@langcha
|
|
|
24
36
|
* </ErrorBoundary>
|
|
25
37
|
* ```
|
|
26
38
|
*/
|
|
27
|
-
declare function invalidateSuspenseCache(): void;
|
|
39
|
+
declare function invalidateSuspenseCache(cache?: SuspenseCache): void;
|
|
28
40
|
type WithSuspense<T> = Omit<T, "isLoading" | "error" | "isThreadLoading"> & {
|
|
29
41
|
isStreaming: boolean;
|
|
30
42
|
};
|
|
43
|
+
type UseSuspenseStreamOptions<T = Record<string, unknown>, Bag extends BagTemplate = BagTemplate> = ResolveStreamOptions<T, InferBag<T, Bag>> & {
|
|
44
|
+
/**
|
|
45
|
+
* Optional cache store used by Suspense history prefetching.
|
|
46
|
+
* Provide a custom cache in tests to avoid cross-test cache sharing.
|
|
47
|
+
*/
|
|
48
|
+
suspenseCache?: SuspenseCache;
|
|
49
|
+
};
|
|
31
50
|
/**
|
|
32
51
|
* A Suspense-compatible variant of {@link useStream} for LangGraph Platform.
|
|
33
52
|
*
|
|
@@ -56,7 +75,7 @@ type WithSuspense<T> = Omit<T, "isLoading" | "error" | "isThreadLoading"> & {
|
|
|
56
75
|
* @template T - Either a ReactAgent / DeepAgent type or a state record type.
|
|
57
76
|
* @template Bag - Type configuration bag (ConfigurableType, InterruptType, …).
|
|
58
77
|
*/
|
|
59
|
-
declare function useSuspenseStream<T = Record<string, unknown>, Bag extends BagTemplate = BagTemplate>(options:
|
|
78
|
+
declare function useSuspenseStream<T = Record<string, unknown>, Bag extends BagTemplate = BagTemplate>(options: UseSuspenseStreamOptions<T, InferBag<T, Bag>>): WithClassMessages<WithSuspense<ResolveStreamInterface<T, InferBag<T, Bag>>>>;
|
|
60
79
|
//#endregion
|
|
61
|
-
export { invalidateSuspenseCache, useSuspenseStream };
|
|
80
|
+
export { SuspenseCache, createSuspenseCache, invalidateSuspenseCache, useSuspenseStream };
|
|
62
81
|
//# sourceMappingURL=suspense-stream.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suspense-stream.d.cts","names":[],"sources":["../src/suspense-stream.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"suspense-stream.d.cts","names":[],"sources":["../src/suspense-stream.tsx"],"mappings":";;;;;KAoBY,kBAAA;EACN,MAAA;EAAmB,OAAA,EAAS,OAAA;AAAA;EAC5B,MAAA;EAAoB,IAAA,EAAM,CAAA;AAAA;EAC1B,MAAA;EAAoB,KAAA;AAAA;AAAA,KAEd,aAAA,GAAgB,GAAA,SAAY,kBAAA;AAAA,iBAIxB,mBAAA,CAAA,GAAuB,aAAA;;;;;;AAJvC;;;;;AAIA;;;;;AA8EA;;;;;AAIC;iBAJe,uBAAA,CACd,KAAA,GAAO,aAAA;AAAA,KASJ,YAAA,MAAkB,IAAA,CAAK,CAAA;EAC1B,WAAA;AAAA;AAAA,KAGG,wBAAA,KACC,MAAA,+BACQ,WAAA,GAAc,WAAA,IACxB,oBAAA,CAAqB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA;EAPZ;;;;EAY1B,aAAA,GAAgB,aAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmClB;;iBAAgB,iBAAA,KACV,MAAA,+BACQ,WAAA,GAAc,WAAA,CAAA,CAE1B,OAAA,EAAS,wBAAA,CAAyB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA,KAChD,iBAAA,CAAkB,YAAA,CAAa,sBAAA,CAAuB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA"}
|
|
@@ -3,6 +3,18 @@ import { InferBag, ResolveStreamInterface, ResolveStreamOptions } from "@langcha
|
|
|
3
3
|
import { BagTemplate } from "@langchain/langgraph-sdk";
|
|
4
4
|
|
|
5
5
|
//#region src/suspense-stream.d.ts
|
|
6
|
+
type SuspenseCacheEntry<T> = {
|
|
7
|
+
status: "pending";
|
|
8
|
+
promise: Promise<void>;
|
|
9
|
+
} | {
|
|
10
|
+
status: "resolved";
|
|
11
|
+
data: T;
|
|
12
|
+
} | {
|
|
13
|
+
status: "rejected";
|
|
14
|
+
error: unknown;
|
|
15
|
+
};
|
|
16
|
+
type SuspenseCache = Map<string, SuspenseCacheEntry<unknown>>;
|
|
17
|
+
declare function createSuspenseCache(): SuspenseCache;
|
|
6
18
|
/**
|
|
7
19
|
* Clear the internal Suspense cache used by {@link useSuspenseStream}.
|
|
8
20
|
*
|
|
@@ -24,10 +36,17 @@ import { BagTemplate } from "@langchain/langgraph-sdk";
|
|
|
24
36
|
* </ErrorBoundary>
|
|
25
37
|
* ```
|
|
26
38
|
*/
|
|
27
|
-
declare function invalidateSuspenseCache(): void;
|
|
39
|
+
declare function invalidateSuspenseCache(cache?: SuspenseCache): void;
|
|
28
40
|
type WithSuspense<T> = Omit<T, "isLoading" | "error" | "isThreadLoading"> & {
|
|
29
41
|
isStreaming: boolean;
|
|
30
42
|
};
|
|
43
|
+
type UseSuspenseStreamOptions<T = Record<string, unknown>, Bag extends BagTemplate = BagTemplate> = ResolveStreamOptions<T, InferBag<T, Bag>> & {
|
|
44
|
+
/**
|
|
45
|
+
* Optional cache store used by Suspense history prefetching.
|
|
46
|
+
* Provide a custom cache in tests to avoid cross-test cache sharing.
|
|
47
|
+
*/
|
|
48
|
+
suspenseCache?: SuspenseCache;
|
|
49
|
+
};
|
|
31
50
|
/**
|
|
32
51
|
* A Suspense-compatible variant of {@link useStream} for LangGraph Platform.
|
|
33
52
|
*
|
|
@@ -56,7 +75,7 @@ type WithSuspense<T> = Omit<T, "isLoading" | "error" | "isThreadLoading"> & {
|
|
|
56
75
|
* @template T - Either a ReactAgent / DeepAgent type or a state record type.
|
|
57
76
|
* @template Bag - Type configuration bag (ConfigurableType, InterruptType, …).
|
|
58
77
|
*/
|
|
59
|
-
declare function useSuspenseStream<T = Record<string, unknown>, Bag extends BagTemplate = BagTemplate>(options:
|
|
78
|
+
declare function useSuspenseStream<T = Record<string, unknown>, Bag extends BagTemplate = BagTemplate>(options: UseSuspenseStreamOptions<T, InferBag<T, Bag>>): WithClassMessages<WithSuspense<ResolveStreamInterface<T, InferBag<T, Bag>>>>;
|
|
60
79
|
//#endregion
|
|
61
|
-
export { invalidateSuspenseCache, useSuspenseStream };
|
|
80
|
+
export { SuspenseCache, createSuspenseCache, invalidateSuspenseCache, useSuspenseStream };
|
|
62
81
|
//# sourceMappingURL=suspense-stream.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suspense-stream.d.ts","names":[],"sources":["../src/suspense-stream.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"suspense-stream.d.ts","names":[],"sources":["../src/suspense-stream.tsx"],"mappings":";;;;;KAoBY,kBAAA;EACN,MAAA;EAAmB,OAAA,EAAS,OAAA;AAAA;EAC5B,MAAA;EAAoB,IAAA,EAAM,CAAA;AAAA;EAC1B,MAAA;EAAoB,KAAA;AAAA;AAAA,KAEd,aAAA,GAAgB,GAAA,SAAY,kBAAA;AAAA,iBAIxB,mBAAA,CAAA,GAAuB,aAAA;;;;;;AAJvC;;;;;AAIA;;;;;AA8EA;;;;;AAIC;iBAJe,uBAAA,CACd,KAAA,GAAO,aAAA;AAAA,KASJ,YAAA,MAAkB,IAAA,CAAK,CAAA;EAC1B,WAAA;AAAA;AAAA,KAGG,wBAAA,KACC,MAAA,+BACQ,WAAA,GAAc,WAAA,IACxB,oBAAA,CAAqB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA;EAPZ;;;;EAY1B,aAAA,GAAgB,aAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmClB;;iBAAgB,iBAAA,KACV,MAAA,+BACQ,WAAA,GAAc,WAAA,CAAA,CAE1B,OAAA,EAAS,wBAAA,CAAyB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA,KAChD,iBAAA,CAAkB,YAAA,CAAa,sBAAA,CAAuB,CAAA,EAAG,QAAA,CAAS,CAAA,EAAG,GAAA"}
|
package/dist/suspense-stream.js
CHANGED
|
@@ -3,7 +3,10 @@ import { useStreamLGP } from "./stream.lgp.js";
|
|
|
3
3
|
import { useCallback, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Client, getClientConfigHash } from "@langchain/langgraph-sdk/client";
|
|
5
5
|
//#region src/suspense-stream.tsx
|
|
6
|
-
const
|
|
6
|
+
const defaultSuspenseCache = /* @__PURE__ */ new Map();
|
|
7
|
+
function createSuspenseCache() {
|
|
8
|
+
return /* @__PURE__ */ new Map();
|
|
9
|
+
}
|
|
7
10
|
function getCacheKey(client, threadId, limit) {
|
|
8
11
|
return `suspense:${getClientConfigHash(client)}:${threadId}:${limit}`;
|
|
9
12
|
}
|
|
@@ -15,25 +18,25 @@ function fetchThreadHistory(client, threadId, options) {
|
|
|
15
18
|
const limit = typeof options?.limit === "number" ? options.limit : 10;
|
|
16
19
|
return client.threads.getHistory(threadId, { limit });
|
|
17
20
|
}
|
|
18
|
-
function getOrCreateCacheEntry(client, threadId, limit) {
|
|
21
|
+
function getOrCreateCacheEntry(cache, client, threadId, limit) {
|
|
19
22
|
const key = getCacheKey(client, threadId, limit);
|
|
20
|
-
let entry =
|
|
23
|
+
let entry = cache.get(key);
|
|
21
24
|
if (!entry) {
|
|
22
25
|
entry = {
|
|
23
26
|
status: "pending",
|
|
24
27
|
promise: fetchThreadHistory(client, threadId, { limit }).then((data) => {
|
|
25
|
-
|
|
28
|
+
cache.set(key, {
|
|
26
29
|
status: "resolved",
|
|
27
30
|
data
|
|
28
31
|
});
|
|
29
32
|
}).catch((error) => {
|
|
30
|
-
|
|
33
|
+
cache.set(key, {
|
|
31
34
|
status: "rejected",
|
|
32
35
|
error
|
|
33
36
|
});
|
|
34
37
|
})
|
|
35
38
|
};
|
|
36
|
-
|
|
39
|
+
cache.set(key, entry);
|
|
37
40
|
}
|
|
38
41
|
return entry;
|
|
39
42
|
}
|
|
@@ -58,10 +61,11 @@ function getOrCreateCacheEntry(client, threadId, limit) {
|
|
|
58
61
|
* </ErrorBoundary>
|
|
59
62
|
* ```
|
|
60
63
|
*/
|
|
61
|
-
function invalidateSuspenseCache() {
|
|
62
|
-
|
|
64
|
+
function invalidateSuspenseCache(cache = defaultSuspenseCache) {
|
|
65
|
+
cache.clear();
|
|
63
66
|
}
|
|
64
67
|
function useSuspenseStream(options) {
|
|
68
|
+
const cache = options.suspenseCache ?? defaultSuspenseCache;
|
|
65
69
|
const client = useMemo(() => options.client ?? new Client({
|
|
66
70
|
apiUrl: options.apiUrl,
|
|
67
71
|
apiKey: options.apiKey,
|
|
@@ -78,7 +82,7 @@ function useSuspenseStream(options) {
|
|
|
78
82
|
const historyLimit = typeof options.fetchStateHistory === "object" && options.fetchStateHistory != null ? options.fetchStateHistory.limit ?? false : options.fetchStateHistory ?? false;
|
|
79
83
|
const needsHistoryFetch = threadId != null && options.thread == null;
|
|
80
84
|
let cacheEntry;
|
|
81
|
-
if (needsHistoryFetch) cacheEntry = getOrCreateCacheEntry(client, threadId, historyLimit);
|
|
85
|
+
if (needsHistoryFetch) cacheEntry = getOrCreateCacheEntry(cache, client, threadId, historyLimit);
|
|
82
86
|
const cachedData = cacheEntry?.status === "resolved" ? cacheEntry.data : void 0;
|
|
83
87
|
const cachedDataRef = useRef(cachedData);
|
|
84
88
|
if (cachedData != null) cachedDataRef.current = cachedData;
|
|
@@ -89,7 +93,7 @@ function useSuspenseStream(options) {
|
|
|
89
93
|
try {
|
|
90
94
|
const data = await fetchThreadHistory(client, fetchId, { limit: historyLimit });
|
|
91
95
|
const key = getCacheKey(client, fetchId, historyLimit);
|
|
92
|
-
|
|
96
|
+
cache.set(key, {
|
|
93
97
|
status: "resolved",
|
|
94
98
|
data
|
|
95
99
|
});
|
|
@@ -100,6 +104,7 @@ function useSuspenseStream(options) {
|
|
|
100
104
|
return;
|
|
101
105
|
}
|
|
102
106
|
}, [
|
|
107
|
+
cache,
|
|
103
108
|
client,
|
|
104
109
|
threadId,
|
|
105
110
|
historyLimit
|
|
@@ -127,7 +132,7 @@ function useSuspenseStream(options) {
|
|
|
127
132
|
if (cacheEntry.status === "pending") throw cacheEntry.promise;
|
|
128
133
|
if (cacheEntry.status === "rejected") {
|
|
129
134
|
const key = getCacheKey(client, threadId, historyLimit);
|
|
130
|
-
|
|
135
|
+
cache.delete(key);
|
|
131
136
|
throw cacheEntry.error instanceof Error ? cacheEntry.error : new Error(String(cacheEntry.error));
|
|
132
137
|
}
|
|
133
138
|
}
|
|
@@ -172,15 +177,25 @@ function useSuspenseStream(options) {
|
|
|
172
177
|
submit: stream.submit,
|
|
173
178
|
switchThread: stream.switchThread,
|
|
174
179
|
joinStream: stream.joinStream,
|
|
175
|
-
branch
|
|
180
|
+
get branch() {
|
|
181
|
+
return stream.branch;
|
|
182
|
+
},
|
|
176
183
|
setBranch: stream.setBranch,
|
|
177
|
-
client
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
184
|
+
get client() {
|
|
185
|
+
return stream.client;
|
|
186
|
+
},
|
|
187
|
+
get assistantId() {
|
|
188
|
+
return stream.assistantId;
|
|
189
|
+
},
|
|
190
|
+
get queue() {
|
|
191
|
+
return stream.queue;
|
|
192
|
+
},
|
|
193
|
+
get isStreaming() {
|
|
194
|
+
return stream.isLoading;
|
|
195
|
+
}
|
|
181
196
|
};
|
|
182
197
|
}
|
|
183
198
|
//#endregion
|
|
184
|
-
export { invalidateSuspenseCache, useSuspenseStream };
|
|
199
|
+
export { createSuspenseCache, invalidateSuspenseCache, useSuspenseStream };
|
|
185
200
|
|
|
186
201
|
//# sourceMappingURL=suspense-stream.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suspense-stream.js","names":[],"sources":["../src/suspense-stream.tsx"],"sourcesContent":["/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */\n\n\"use client\";\n\nimport { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { ThreadState, BagTemplate } from \"@langchain/langgraph-sdk\";\nimport { Client, getClientConfigHash } from \"@langchain/langgraph-sdk/client\";\nimport type {\n UseStreamThread,\n ResolveStreamOptions,\n ResolveStreamInterface,\n InferBag,\n} from \"@langchain/langgraph-sdk/ui\";\nimport { useStreamLGP } from \"./stream.lgp.js\";\nimport type { WithClassMessages } from \"./stream.js\";\n\n// ---------------------------------------------------------------------------\n// Suspense cache\n// ---------------------------------------------------------------------------\n\ntype SuspenseCacheEntry<T> =\n | { status: \"pending\"; promise: Promise<void> }\n | { status: \"resolved\"; data: T }\n | { status: \"rejected\"; error: unknown };\n\nconst threadCache = new Map<string, SuspenseCacheEntry<unknown>>();\n\nfunction getCacheKey(\n client: Client,\n threadId: string,\n limit: boolean | number,\n): string {\n return `suspense:${getClientConfigHash(client)}:${threadId}:${limit}`;\n}\n\nfunction fetchThreadHistory<StateType extends Record<string, unknown>>(\n client: Client,\n threadId: string,\n options?: { limit?: boolean | number },\n): Promise<ThreadState<StateType>[]> {\n if (options?.limit === false) {\n return client.threads.getState<StateType>(threadId).then((state) => {\n if (state.checkpoint == null) return [];\n return [state];\n });\n }\n\n const limit = typeof options?.limit === \"number\" ? options.limit : 10;\n return client.threads.getHistory<StateType>(threadId, { limit });\n}\n\nfunction getOrCreateCacheEntry<StateType extends Record<string, unknown>>(\n client: Client,\n threadId: string,\n limit: boolean | number,\n): SuspenseCacheEntry<ThreadState<StateType>[]> {\n const key = getCacheKey(client, threadId, limit);\n let entry = threadCache.get(key) as\n | SuspenseCacheEntry<ThreadState<StateType>[]>\n | undefined;\n\n if (!entry) {\n // Start fetch. The promise always resolves (never rejects) so React\n // Suspense correctly waits for it and then retries the render.\n const promise = fetchThreadHistory<StateType>(client, threadId, { limit })\n .then((data) => {\n threadCache.set(key, { status: \"resolved\", data });\n })\n .catch((error: unknown) => {\n threadCache.set(key, { status: \"rejected\", error });\n });\n\n entry = { status: \"pending\", promise };\n threadCache.set(key, entry);\n }\n\n return entry;\n}\n\n/**\n * Clear the internal Suspense cache used by {@link useSuspenseStream}.\n *\n * Call this from an Error Boundary's `onReset` callback so that a retry\n * triggers a fresh thread-history fetch rather than re-throwing the\n * cached error.\n *\n * @example\n * ```tsx\n * <ErrorBoundary\n * onReset={() => invalidateSuspenseCache()}\n * fallbackRender={({ resetErrorBoundary }) => (\n * <button onClick={resetErrorBoundary}>Retry</button>\n * )}\n * >\n * <Suspense fallback={<Spinner />}>\n * <Chat />\n * </Suspense>\n * </ErrorBoundary>\n * ```\n */\nexport function invalidateSuspenseCache(): void {\n threadCache.clear();\n}\n\n// ---------------------------------------------------------------------------\n// Return-type helper\n// ---------------------------------------------------------------------------\n\ntype WithSuspense<T> = Omit<T, \"isLoading\" | \"error\" | \"isThreadLoading\"> & {\n isStreaming: boolean;\n};\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * A Suspense-compatible variant of {@link useStream} for LangGraph Platform.\n *\n * `useSuspenseStream` suspends the component while the initial thread\n * history is being fetched and throws errors to the nearest React Error\n * Boundary. During active streaming the component stays rendered and\n * `isStreaming` indicates whether tokens are arriving.\n *\n * @example\n * ```tsx\n * <ErrorBoundary fallback={<ErrorDisplay />}>\n * <Suspense fallback={<Spinner />}>\n * <Chat />\n * </Suspense>\n * </ErrorBoundary>\n *\n * function Chat() {\n * const { messages, submit, isStreaming } = useSuspenseStream({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n * return <MessageList messages={messages} />;\n * }\n * ```\n *\n * @template T - Either a ReactAgent / DeepAgent type or a state record type.\n * @template Bag - Type configuration bag (ConfigurableType, InterruptType, …).\n */\nexport function useSuspenseStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: ResolveStreamOptions<T, InferBag<T, Bag>>,\n): WithClassMessages<WithSuspense<ResolveStreamInterface<T, InferBag<T, Bag>>>>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useSuspenseStream(options: any): any {\n type StateType = Record<string, unknown>;\n\n // ---- client (needed before useStreamLGP for cache key derivation) ----\n const client = useMemo(\n () =>\n options.client ??\n new Client({\n apiUrl: options.apiUrl,\n apiKey: options.apiKey,\n callerOptions: options.callerOptions,\n defaultHeaders: options.defaultHeaders,\n }),\n [\n options.client,\n options.apiKey,\n options.apiUrl,\n options.callerOptions,\n options.defaultHeaders,\n ],\n );\n\n const { threadId } = options;\n\n const historyLimit: boolean | number =\n typeof options.fetchStateHistory === \"object\" &&\n options.fetchStateHistory != null\n ? (options.fetchStateHistory.limit ?? false)\n : (options.fetchStateHistory ?? false);\n\n // Only manage history via the suspense cache when the caller hasn't\n // supplied an external `thread` and there's a threadId to load.\n const needsHistoryFetch = threadId != null && options.thread == null;\n\n // ---- suspense cache lookup (synchronous, may create fetch) ----\n let cacheEntry: SuspenseCacheEntry<ThreadState<StateType>[]> | undefined;\n\n if (needsHistoryFetch) {\n cacheEntry = getOrCreateCacheEntry<StateType>(\n client,\n threadId,\n historyLimit,\n );\n }\n\n const cachedData =\n cacheEntry?.status === \"resolved\" ? cacheEntry.data : undefined;\n\n // ---- mutable ref so `mutate` always writes the freshest data ----\n const cachedDataRef = useRef(cachedData);\n if (cachedData != null) {\n cachedDataRef.current = cachedData;\n }\n\n // Re-render trigger after external mutate calls.\n const [, setMutateVersion] = useState(0);\n\n const mutate = useCallback(\n async (\n mutateId?: string,\n ): Promise<ThreadState<StateType>[] | null | undefined> => {\n const fetchId = mutateId ?? threadId;\n if (!fetchId) return undefined;\n try {\n const data = await fetchThreadHistory<StateType>(client, fetchId, {\n limit: historyLimit,\n });\n const key = getCacheKey(client, fetchId, historyLimit);\n threadCache.set(key, { status: \"resolved\", data });\n cachedDataRef.current = data;\n setMutateVersion((v) => v + 1);\n return data;\n } catch {\n return undefined;\n }\n },\n [client, threadId, historyLimit],\n );\n\n // ---- build thread override for useStreamLGP ----\n const thread: UseStreamThread<StateType> | undefined = useMemo(() => {\n if (!needsHistoryFetch) return options.thread;\n return {\n data: cachedDataRef.current,\n error: undefined,\n isLoading: false,\n mutate,\n };\n // `cachedData` is included so the memo recomputes when the cache\n // transitions from pending → resolved across suspend/retry cycles.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [needsHistoryFetch, options.thread, cachedData, mutate]);\n\n // ---- delegate to useStreamLGP (must always run – Rules of Hooks) ----\n const stream = useStreamLGP({\n ...options,\n client,\n thread,\n });\n\n // ---- post-hook: suspend or throw ----\n\n // Suspend while thread history is loading, but only when the stream\n // itself is idle. If an active stream is running (e.g. the thread was\n // just created during submit), suspending would discard the stream\n // state, so we skip it.\n if (needsHistoryFetch && cacheEntry && !stream.isLoading) {\n if (cacheEntry.status === \"pending\") {\n // eslint-disable-next-line @typescript-eslint/no-throw-literal\n throw cacheEntry.promise;\n }\n if (cacheEntry.status === \"rejected\") {\n // Clear cache so a subsequent retry (ErrorBoundary reset) starts\n // a fresh fetch instead of re-throwing the stale error.\n const key = getCacheKey(client, threadId!, historyLimit);\n threadCache.delete(key);\n // eslint-disable-next-line no-instanceof/no-instanceof\n throw cacheEntry.error instanceof Error\n ? cacheEntry.error\n : new Error(String(cacheEntry.error));\n }\n }\n\n // Throw non-streaming errors to the nearest Error Boundary.\n if (stream.error != null && !stream.isLoading) {\n // eslint-disable-next-line no-instanceof/no-instanceof\n throw stream.error instanceof Error\n ? stream.error\n : new Error(String(stream.error));\n }\n\n // Build return object explicitly to avoid triggering throwing getters\n // (e.g. `history` throws when `fetchStateHistory` is not set).\n return {\n get values() {\n return stream.values;\n },\n get messages() {\n return stream.messages;\n },\n get toolCalls() {\n return stream.toolCalls;\n },\n get toolProgress() {\n return stream.toolProgress;\n },\n getToolCalls: stream.getToolCalls.bind(stream),\n get interrupt() {\n return stream.interrupt;\n },\n get interrupts() {\n return stream.interrupts;\n },\n get subagents() {\n return stream.subagents;\n },\n get activeSubagents() {\n return stream.activeSubagents;\n },\n getSubagent: stream.getSubagent.bind(stream),\n getSubagentsByType: stream.getSubagentsByType.bind(stream),\n getSubagentsByMessage: stream.getSubagentsByMessage.bind(stream),\n getMessagesMetadata: stream.getMessagesMetadata.bind(stream),\n get history() {\n return stream.history;\n },\n get experimental_branchTree() {\n return stream.experimental_branchTree;\n },\n stop: stream.stop,\n submit: stream.submit,\n switchThread: stream.switchThread,\n joinStream: stream.joinStream,\n branch: stream.branch,\n setBranch: stream.setBranch,\n client: stream.client,\n assistantId: stream.assistantId,\n queue: stream.queue,\n isStreaming: stream.isLoading,\n };\n}\n"],"mappings":";;;;;AAyBA,MAAM,8BAAc,IAAI,KAA0C;AAElE,SAAS,YACP,QACA,UACA,OACQ;AACR,QAAO,YAAY,oBAAoB,OAAO,CAAC,GAAG,SAAS,GAAG;;AAGhE,SAAS,mBACP,QACA,UACA,SACmC;AACnC,KAAI,SAAS,UAAU,MACrB,QAAO,OAAO,QAAQ,SAAoB,SAAS,CAAC,MAAM,UAAU;AAClE,MAAI,MAAM,cAAc,KAAM,QAAO,EAAE;AACvC,SAAO,CAAC,MAAM;GACd;CAGJ,MAAM,QAAQ,OAAO,SAAS,UAAU,WAAW,QAAQ,QAAQ;AACnE,QAAO,OAAO,QAAQ,WAAsB,UAAU,EAAE,OAAO,CAAC;;AAGlE,SAAS,sBACP,QACA,UACA,OAC8C;CAC9C,MAAM,MAAM,YAAY,QAAQ,UAAU,MAAM;CAChD,IAAI,QAAQ,YAAY,IAAI,IAAI;AAIhC,KAAI,CAAC,OAAO;AAWV,UAAQ;GAAE,QAAQ;GAAW,SARb,mBAA8B,QAAQ,UAAU,EAAE,OAAO,CAAC,CACvE,MAAM,SAAS;AACd,gBAAY,IAAI,KAAK;KAAE,QAAQ;KAAY;KAAM,CAAC;KAClD,CACD,OAAO,UAAmB;AACzB,gBAAY,IAAI,KAAK;KAAE,QAAQ;KAAY;KAAO,CAAC;KACnD;GAEkC;AACtC,cAAY,IAAI,KAAK,MAAM;;AAG7B,QAAO;;;;;;;;;;;;;;;;;;;;;;;AAwBT,SAAgB,0BAAgC;AAC9C,aAAY,OAAO;;AAmDrB,SAAgB,kBAAkB,SAAmB;CAInD,MAAM,SAAS,cAEX,QAAQ,UACR,IAAI,OAAO;EACT,QAAQ,QAAQ;EAChB,QAAQ,QAAQ;EAChB,eAAe,QAAQ;EACvB,gBAAgB,QAAQ;EACzB,CAAC,EACJ;EACE,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACT,CACF;CAED,MAAM,EAAE,aAAa;CAErB,MAAM,eACJ,OAAO,QAAQ,sBAAsB,YACrC,QAAQ,qBAAqB,OACxB,QAAQ,kBAAkB,SAAS,QACnC,QAAQ,qBAAqB;CAIpC,MAAM,oBAAoB,YAAY,QAAQ,QAAQ,UAAU;CAGhE,IAAI;AAEJ,KAAI,kBACF,cAAa,sBACX,QACA,UACA,aACD;CAGH,MAAM,aACJ,YAAY,WAAW,aAAa,WAAW,OAAO,KAAA;CAGxD,MAAM,gBAAgB,OAAO,WAAW;AACxC,KAAI,cAAc,KAChB,eAAc,UAAU;CAI1B,MAAM,GAAG,oBAAoB,SAAS,EAAE;CAExC,MAAM,SAAS,YACb,OACE,aACyD;EACzD,MAAM,UAAU,YAAY;AAC5B,MAAI,CAAC,QAAS,QAAO,KAAA;AACrB,MAAI;GACF,MAAM,OAAO,MAAM,mBAA8B,QAAQ,SAAS,EAChE,OAAO,cACR,CAAC;GACF,MAAM,MAAM,YAAY,QAAQ,SAAS,aAAa;AACtD,eAAY,IAAI,KAAK;IAAE,QAAQ;IAAY;IAAM,CAAC;AAClD,iBAAc,UAAU;AACxB,qBAAkB,MAAM,IAAI,EAAE;AAC9B,UAAO;UACD;AACN;;IAGJ;EAAC;EAAQ;EAAU;EAAa,CACjC;CAGD,MAAM,SAAiD,cAAc;AACnE,MAAI,CAAC,kBAAmB,QAAO,QAAQ;AACvC,SAAO;GACL,MAAM,cAAc;GACpB,OAAO,KAAA;GACP,WAAW;GACX;GACD;IAIA;EAAC;EAAmB,QAAQ;EAAQ;EAAY;EAAO,CAAC;CAG3D,MAAM,SAAS,aAAa;EAC1B,GAAG;EACH;EACA;EACD,CAAC;AAQF,KAAI,qBAAqB,cAAc,CAAC,OAAO,WAAW;AACxD,MAAI,WAAW,WAAW,UAExB,OAAM,WAAW;AAEnB,MAAI,WAAW,WAAW,YAAY;GAGpC,MAAM,MAAM,YAAY,QAAQ,UAAW,aAAa;AACxD,eAAY,OAAO,IAAI;AAEvB,SAAM,WAAW,iBAAiB,QAC9B,WAAW,QACX,IAAI,MAAM,OAAO,WAAW,MAAM,CAAC;;;AAK3C,KAAI,OAAO,SAAS,QAAQ,CAAC,OAAO,UAElC,OAAM,OAAO,iBAAiB,QAC1B,OAAO,QACP,IAAI,MAAM,OAAO,OAAO,MAAM,CAAC;AAKrC,QAAO;EACL,IAAI,SAAS;AACX,UAAO,OAAO;;EAEhB,IAAI,WAAW;AACb,UAAO,OAAO;;EAEhB,IAAI,YAAY;AACd,UAAO,OAAO;;EAEhB,IAAI,eAAe;AACjB,UAAO,OAAO;;EAEhB,cAAc,OAAO,aAAa,KAAK,OAAO;EAC9C,IAAI,YAAY;AACd,UAAO,OAAO;;EAEhB,IAAI,aAAa;AACf,UAAO,OAAO;;EAEhB,IAAI,YAAY;AACd,UAAO,OAAO;;EAEhB,IAAI,kBAAkB;AACpB,UAAO,OAAO;;EAEhB,aAAa,OAAO,YAAY,KAAK,OAAO;EAC5C,oBAAoB,OAAO,mBAAmB,KAAK,OAAO;EAC1D,uBAAuB,OAAO,sBAAsB,KAAK,OAAO;EAChE,qBAAqB,OAAO,oBAAoB,KAAK,OAAO;EAC5D,IAAI,UAAU;AACZ,UAAO,OAAO;;EAEhB,IAAI,0BAA0B;AAC5B,UAAO,OAAO;;EAEhB,MAAM,OAAO;EACb,QAAQ,OAAO;EACf,cAAc,OAAO;EACrB,YAAY,OAAO;EACnB,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,QAAQ,OAAO;EACf,aAAa,OAAO;EACpB,OAAO,OAAO;EACd,aAAa,OAAO;EACrB"}
|
|
1
|
+
{"version":3,"file":"suspense-stream.js","names":[],"sources":["../src/suspense-stream.tsx"],"sourcesContent":["/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */\n\n\"use client\";\n\nimport { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { ThreadState, BagTemplate } from \"@langchain/langgraph-sdk\";\nimport { Client, getClientConfigHash } from \"@langchain/langgraph-sdk/client\";\nimport type {\n UseStreamThread,\n ResolveStreamOptions,\n ResolveStreamInterface,\n InferBag,\n} from \"@langchain/langgraph-sdk/ui\";\nimport { useStreamLGP } from \"./stream.lgp.js\";\nimport type { WithClassMessages } from \"./stream.js\";\n\n// ---------------------------------------------------------------------------\n// Suspense cache\n// ---------------------------------------------------------------------------\n\nexport type SuspenseCacheEntry<T> =\n | { status: \"pending\"; promise: Promise<void> }\n | { status: \"resolved\"; data: T }\n | { status: \"rejected\"; error: unknown };\n\nexport type SuspenseCache = Map<string, SuspenseCacheEntry<unknown>>;\n\nconst defaultSuspenseCache: SuspenseCache = new Map();\n\nexport function createSuspenseCache(): SuspenseCache {\n return new Map();\n}\n\nfunction getCacheKey(\n client: Client,\n threadId: string,\n limit: boolean | number,\n): string {\n return `suspense:${getClientConfigHash(client)}:${threadId}:${limit}`;\n}\n\nfunction fetchThreadHistory<StateType extends Record<string, unknown>>(\n client: Client,\n threadId: string,\n options?: { limit?: boolean | number },\n): Promise<ThreadState<StateType>[]> {\n if (options?.limit === false) {\n return client.threads.getState<StateType>(threadId).then((state) => {\n if (state.checkpoint == null) return [];\n return [state];\n });\n }\n\n const limit = typeof options?.limit === \"number\" ? options.limit : 10;\n return client.threads.getHistory<StateType>(threadId, { limit });\n}\n\nfunction getOrCreateCacheEntry<StateType extends Record<string, unknown>>(\n cache: SuspenseCache,\n client: Client,\n threadId: string,\n limit: boolean | number,\n): SuspenseCacheEntry<ThreadState<StateType>[]> {\n const key = getCacheKey(client, threadId, limit);\n let entry = cache.get(key) as\n | SuspenseCacheEntry<ThreadState<StateType>[]>\n | undefined;\n\n if (!entry) {\n // Start fetch. The promise always resolves (never rejects) so React\n // Suspense correctly waits for it and then retries the render.\n const promise = fetchThreadHistory<StateType>(client, threadId, { limit })\n .then((data) => {\n cache.set(key, { status: \"resolved\", data });\n })\n .catch((error: unknown) => {\n cache.set(key, { status: \"rejected\", error });\n });\n\n entry = { status: \"pending\", promise };\n cache.set(key, entry);\n }\n\n return entry;\n}\n\n/**\n * Clear the internal Suspense cache used by {@link useSuspenseStream}.\n *\n * Call this from an Error Boundary's `onReset` callback so that a retry\n * triggers a fresh thread-history fetch rather than re-throwing the\n * cached error.\n *\n * @example\n * ```tsx\n * <ErrorBoundary\n * onReset={() => invalidateSuspenseCache()}\n * fallbackRender={({ resetErrorBoundary }) => (\n * <button onClick={resetErrorBoundary}>Retry</button>\n * )}\n * >\n * <Suspense fallback={<Spinner />}>\n * <Chat />\n * </Suspense>\n * </ErrorBoundary>\n * ```\n */\nexport function invalidateSuspenseCache(\n cache: SuspenseCache = defaultSuspenseCache,\n): void {\n cache.clear();\n}\n\n// ---------------------------------------------------------------------------\n// Return-type helper\n// ---------------------------------------------------------------------------\n\ntype WithSuspense<T> = Omit<T, \"isLoading\" | \"error\" | \"isThreadLoading\"> & {\n isStreaming: boolean;\n};\n\ntype UseSuspenseStreamOptions<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n> = ResolveStreamOptions<T, InferBag<T, Bag>> & {\n /**\n * Optional cache store used by Suspense history prefetching.\n * Provide a custom cache in tests to avoid cross-test cache sharing.\n */\n suspenseCache?: SuspenseCache;\n};\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * A Suspense-compatible variant of {@link useStream} for LangGraph Platform.\n *\n * `useSuspenseStream` suspends the component while the initial thread\n * history is being fetched and throws errors to the nearest React Error\n * Boundary. During active streaming the component stays rendered and\n * `isStreaming` indicates whether tokens are arriving.\n *\n * @example\n * ```tsx\n * <ErrorBoundary fallback={<ErrorDisplay />}>\n * <Suspense fallback={<Spinner />}>\n * <Chat />\n * </Suspense>\n * </ErrorBoundary>\n *\n * function Chat() {\n * const { messages, submit, isStreaming } = useSuspenseStream({\n * assistantId: \"agent\",\n * apiUrl: \"http://localhost:2024\",\n * });\n * return <MessageList messages={messages} />;\n * }\n * ```\n *\n * @template T - Either a ReactAgent / DeepAgent type or a state record type.\n * @template Bag - Type configuration bag (ConfigurableType, InterruptType, …).\n */\nexport function useSuspenseStream<\n T = Record<string, unknown>,\n Bag extends BagTemplate = BagTemplate,\n>(\n options: UseSuspenseStreamOptions<T, InferBag<T, Bag>>,\n): WithClassMessages<WithSuspense<ResolveStreamInterface<T, InferBag<T, Bag>>>>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useSuspenseStream(options: any): any {\n type StateType = Record<string, unknown>;\n const cache: SuspenseCache = options.suspenseCache ?? defaultSuspenseCache;\n\n // ---- client (needed before useStreamLGP for cache key derivation) ----\n const client = useMemo(\n () =>\n options.client ??\n new Client({\n apiUrl: options.apiUrl,\n apiKey: options.apiKey,\n callerOptions: options.callerOptions,\n defaultHeaders: options.defaultHeaders,\n }),\n [\n options.client,\n options.apiKey,\n options.apiUrl,\n options.callerOptions,\n options.defaultHeaders,\n ],\n );\n\n const { threadId } = options;\n\n const historyLimit: boolean | number =\n typeof options.fetchStateHistory === \"object\" &&\n options.fetchStateHistory != null\n ? (options.fetchStateHistory.limit ?? false)\n : (options.fetchStateHistory ?? false);\n\n // Only manage history via the suspense cache when the caller hasn't\n // supplied an external `thread` and there's a threadId to load.\n const needsHistoryFetch = threadId != null && options.thread == null;\n\n // ---- suspense cache lookup (synchronous, may create fetch) ----\n let cacheEntry: SuspenseCacheEntry<ThreadState<StateType>[]> | undefined;\n\n if (needsHistoryFetch) {\n cacheEntry = getOrCreateCacheEntry<StateType>(\n cache,\n client,\n threadId,\n historyLimit,\n );\n }\n\n const cachedData =\n cacheEntry?.status === \"resolved\" ? cacheEntry.data : undefined;\n\n // ---- mutable ref so `mutate` always writes the freshest data ----\n const cachedDataRef = useRef(cachedData);\n if (cachedData != null) {\n cachedDataRef.current = cachedData;\n }\n\n // Re-render trigger after external mutate calls.\n const [, setMutateVersion] = useState(0);\n\n const mutate = useCallback(\n async (\n mutateId?: string,\n ): Promise<ThreadState<StateType>[] | null | undefined> => {\n const fetchId = mutateId ?? threadId;\n if (!fetchId) return undefined;\n try {\n const data = await fetchThreadHistory<StateType>(client, fetchId, {\n limit: historyLimit,\n });\n const key = getCacheKey(client, fetchId, historyLimit);\n cache.set(key, { status: \"resolved\", data });\n cachedDataRef.current = data;\n setMutateVersion((v) => v + 1);\n return data;\n } catch {\n return undefined;\n }\n },\n [cache, client, threadId, historyLimit],\n );\n\n // ---- build thread override for useStreamLGP ----\n const thread: UseStreamThread<StateType> | undefined = useMemo(() => {\n if (!needsHistoryFetch) return options.thread;\n return {\n data: cachedDataRef.current,\n error: undefined,\n isLoading: false,\n mutate,\n };\n // `cachedData` is included so the memo recomputes when the cache\n // transitions from pending → resolved across suspend/retry cycles.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [needsHistoryFetch, options.thread, cachedData, mutate]);\n\n // ---- delegate to useStreamLGP (must always run – Rules of Hooks) ----\n const stream = useStreamLGP({\n ...options,\n client,\n thread,\n });\n\n // ---- post-hook: suspend or throw ----\n\n // Suspend while thread history is loading, but only when the stream\n // itself is idle. If an active stream is running (e.g. the thread was\n // just created during submit), suspending would discard the stream\n // state, so we skip it.\n if (needsHistoryFetch && cacheEntry && !stream.isLoading) {\n if (cacheEntry.status === \"pending\") {\n // eslint-disable-next-line @typescript-eslint/no-throw-literal\n throw cacheEntry.promise;\n }\n if (cacheEntry.status === \"rejected\") {\n // Clear cache so a subsequent retry (ErrorBoundary reset) starts\n // a fresh fetch instead of re-throwing the stale error.\n const key = getCacheKey(client, threadId!, historyLimit);\n cache.delete(key);\n // eslint-disable-next-line no-instanceof/no-instanceof\n throw cacheEntry.error instanceof Error\n ? cacheEntry.error\n : new Error(String(cacheEntry.error));\n }\n }\n\n // Throw non-streaming errors to the nearest Error Boundary.\n if (stream.error != null && !stream.isLoading) {\n // eslint-disable-next-line no-instanceof/no-instanceof\n throw stream.error instanceof Error\n ? stream.error\n : new Error(String(stream.error));\n }\n\n // Build return object explicitly to avoid triggering throwing getters\n // (e.g. `history` throws when `fetchStateHistory` is not set).\n return {\n get values() {\n return stream.values;\n },\n get messages() {\n return stream.messages;\n },\n get toolCalls() {\n return stream.toolCalls;\n },\n get toolProgress() {\n return stream.toolProgress;\n },\n getToolCalls: stream.getToolCalls.bind(stream),\n get interrupt() {\n return stream.interrupt;\n },\n get interrupts() {\n return stream.interrupts;\n },\n get subagents() {\n return stream.subagents;\n },\n get activeSubagents() {\n return stream.activeSubagents;\n },\n getSubagent: stream.getSubagent.bind(stream),\n getSubagentsByType: stream.getSubagentsByType.bind(stream),\n getSubagentsByMessage: stream.getSubagentsByMessage.bind(stream),\n getMessagesMetadata: stream.getMessagesMetadata.bind(stream),\n get history() {\n return stream.history;\n },\n get experimental_branchTree() {\n return stream.experimental_branchTree;\n },\n stop: stream.stop,\n submit: stream.submit,\n switchThread: stream.switchThread,\n joinStream: stream.joinStream,\n get branch() {\n return stream.branch;\n },\n setBranch: stream.setBranch,\n get client() {\n return stream.client;\n },\n get assistantId() {\n return stream.assistantId;\n },\n get queue() {\n return stream.queue;\n },\n get isStreaming() {\n return stream.isLoading;\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langchain/react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "React integration for LangGraph & LangChain",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"directory": "libs/sdk-react"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@langchain/langgraph-sdk": "^1.
|
|
13
|
+
"@langchain/langgraph-sdk": "^1.8.0"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@hono/node-server": "^1.19.11",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"vitest-browser-react": "^2.0.5",
|
|
40
40
|
"webdriverio": "^9.25.0",
|
|
41
41
|
"zod": "^4.3.6",
|
|
42
|
-
"@langchain/langgraph": "^1.2.
|
|
42
|
+
"@langchain/langgraph": "^1.2.5",
|
|
43
43
|
"@langchain/langgraph-api": "^1.1.16",
|
|
44
44
|
"@langchain/langgraph-checkpoint": "^1.0.1"
|
|
45
45
|
},
|