@assistant-ui/react-langchain 0.0.2 → 0.0.3
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 +111 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/useStreamRuntime.d.ts +13 -0
- package/dist/useStreamRuntime.d.ts.map +1 -1
- package/dist/useStreamRuntime.js +21 -1
- package/dist/useStreamRuntime.js.map +1 -1
- package/package.json +13 -8
- package/src/index.ts +1 -0
- package/src/useLangChainState.test.tsx +92 -0
- package/src/useStreamRuntime.ts +38 -1
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
## `@assistant-ui/react-langchain`
|
|
2
|
+
|
|
3
|
+
Adapter that wraps [`useStream`](https://docs.langchain.com/oss/javascript/langgraph-sdk/react-stream) from `@langchain/react` and exposes it as an assistant-ui runtime, with hooks for reading interrupts, submitting raw state updates, and reading arbitrary LangGraph custom state keys.
|
|
4
|
+
|
|
5
|
+
> assistant-ui also ships [`@assistant-ui/react-langgraph`](https://www.npmjs.com/package/@assistant-ui/react-langgraph), which integrates with `@langchain/langgraph-sdk` directly and has a broader feature set (subgraph events, UI messages, message metadata, cancellation). The two packages are independent adapters targeting different upstream libraries — pick whichever matches your upstream choice. See the [comparison](https://www.assistant-ui.com/docs/runtimes/langchain/comparison).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Bridges LangChain's `useStream` to an assistant-ui `AssistantRuntime`
|
|
10
|
+
- Tool invocations flow through assistant-ui's `useToolInvocations`
|
|
11
|
+
- `useLangChainInterruptState` — read the current LangGraph interrupt
|
|
12
|
+
- `useLangChainSubmit` — submit raw state updates (e.g. resume commands)
|
|
13
|
+
- `useLangChainState<T>(key)` — read custom state keys like `todos` / `files` reactively
|
|
14
|
+
- Optional assistant-cloud thread persistence
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @assistant-ui/react @assistant-ui/react-langchain @langchain/react
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Basic setup
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { useStreamRuntime } from "@assistant-ui/react-langchain";
|
|
28
|
+
import { AssistantRuntimeProvider, Thread } from "@assistant-ui/react";
|
|
29
|
+
|
|
30
|
+
function App() {
|
|
31
|
+
const runtime = useStreamRuntime({
|
|
32
|
+
assistantId: "agent",
|
|
33
|
+
apiUrl: "http://localhost:2024",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
38
|
+
<Thread />
|
|
39
|
+
</AssistantRuntimeProvider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`useStreamRuntime` accepts every option `useStream` from `@langchain/react` does, plus:
|
|
45
|
+
|
|
46
|
+
- `cloud` — an `AssistantCloud` instance for persisting threads
|
|
47
|
+
- `adapters` — `{ attachments, speech, feedback }`
|
|
48
|
+
- `messagesKey` — the state key that holds messages (default `"messages"`)
|
|
49
|
+
|
|
50
|
+
### Reading custom state keys
|
|
51
|
+
|
|
52
|
+
LangGraph agents often expose structured state beyond messages (plans, todos, scratch files, generative-UI artifacts). Read them directly with `useLangChainState` — mirrors `useStream().values[key]` upstream and updates when the stream emits new state.
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { useLangChainState } from "@assistant-ui/react-langchain";
|
|
56
|
+
|
|
57
|
+
type Todo = { id: string; title: string; done: boolean };
|
|
58
|
+
|
|
59
|
+
function TodoList() {
|
|
60
|
+
const todos = useLangChainState<Todo[]>("todos", []);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<ul>
|
|
64
|
+
{todos.map((t) => (
|
|
65
|
+
<li key={t.id}>{t.title}</li>
|
|
66
|
+
))}
|
|
67
|
+
</ul>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Signatures:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
useLangChainState<T>(key: string): T | undefined;
|
|
76
|
+
useLangChainState<T>(key: string, defaultValue: T): T;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Interrupts
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import {
|
|
83
|
+
useLangChainInterruptState,
|
|
84
|
+
useLangChainSubmit,
|
|
85
|
+
} from "@assistant-ui/react-langchain";
|
|
86
|
+
import { Command } from "@langchain/langgraph-sdk";
|
|
87
|
+
|
|
88
|
+
function InterruptPrompt() {
|
|
89
|
+
const interrupt = useLangChainInterruptState();
|
|
90
|
+
const submit = useLangChainSubmit();
|
|
91
|
+
|
|
92
|
+
if (!interrupt) return null;
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div>
|
|
96
|
+
<pre>{JSON.stringify(interrupt.value, null, 2)}</pre>
|
|
97
|
+
<button onClick={() => submit(null, { command: new Command({ resume: "yes" }) })}>
|
|
98
|
+
Approve
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Message conversion
|
|
106
|
+
|
|
107
|
+
The adapter ships `convertLangChainBaseMessage` for cases where you want to reuse the internal converter directly (e.g. when building a custom `ExternalStoreAdapter`).
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { convertLangChainBaseMessage } from "@assistant-ui/react-langchain";
|
|
111
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { useStreamRuntime, useLangChainInterruptState, useLangChainSubmit, } from "./useStreamRuntime.js";
|
|
1
|
+
export { useStreamRuntime, useLangChainInterruptState, useLangChainState, useLangChainSubmit, } from "./useStreamRuntime.js";
|
|
2
2
|
export type { UseStreamRuntimeOptions } from "./useStreamRuntime.js";
|
|
3
3
|
export { convertLangChainBaseMessage } from "./convertMessages.js";
|
|
4
4
|
export type { LangChainBaseMessage, LangChainContentBlock, LangChainToolCall, } from "./types.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,kBAAkB,GACnB,8BAA2B;AAC5B,YAAY,EAAE,uBAAuB,EAAE,8BAA2B;AAElE,OAAO,EAAE,2BAA2B,EAAE,6BAA0B;AAEhE,YAAY,EACV,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,GAClB,mBAAgB"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,iBAAiB,EACjB,kBAAkB,GACnB,8BAA2B;AAC5B,YAAY,EAAE,uBAAuB,EAAE,8BAA2B;AAElE,OAAO,EAAE,2BAA2B,EAAE,6BAA0B;AAEhE,YAAY,EACV,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,GAClB,mBAAgB"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { useStreamRuntime, useLangChainInterruptState, useLangChainSubmit, } from "./useStreamRuntime.js";
|
|
1
|
+
export { useStreamRuntime, useLangChainInterruptState, useLangChainState, useLangChainSubmit, } from "./useStreamRuntime.js";
|
|
2
2
|
export { convertLangChainBaseMessage } from "./convertMessages.js";
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,kBAAkB,GACnB,8BAA2B;AAG5B,OAAO,EAAE,2BAA2B,EAAE,6BAA0B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,iBAAiB,EACjB,kBAAkB,GACnB,8BAA2B;AAG5B,OAAO,EAAE,2BAA2B,EAAE,6BAA0B"}
|
|
@@ -48,4 +48,17 @@ export declare const useLangChainInterruptState: () => {
|
|
|
48
48
|
* commands.
|
|
49
49
|
*/
|
|
50
50
|
export declare const useLangChainSubmit: () => (values: Record<string, unknown> | null | undefined, options?: Record<string, unknown>) => Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Read a custom LangGraph state key from the current thread. Mirrors
|
|
53
|
+
* `useStream().values[key]` from `@langchain/react` and updates when the
|
|
54
|
+
* stream emits new state.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```tsx
|
|
58
|
+
* const todos = useLangChainState<Todo[]>("todos");
|
|
59
|
+
* const files = useLangChainState<Record<string, string>>("files", {});
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function useLangChainState<T>(key: string): T | undefined;
|
|
63
|
+
export declare function useLangChainState<T>(key: string, defaultValue: T): T;
|
|
51
64
|
//# sourceMappingURL=useStreamRuntime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStreamRuntime.d.ts","sourceRoot":"","sources":["../src/useStreamRuntime.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAU5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"useStreamRuntime.d.ts","sourceRoot":"","sources":["../src/useStreamRuntime.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAU5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AA6BpE,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG;IACvD,KAAK,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,EACL;QACE,WAAW,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;QAC5C,MAAM,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;QAC5C,QAAQ,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;KACxC,GACD,SAAS,CAAC;IACd,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,CAAC;AA8KF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,gBAAgB,GAAI,uBAG9B,uBAAuB,kDAazB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B;YA3PhB,OAAO;aAiQ7B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,SAG3B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EAClD,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,kBAMpC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;AACjE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC"}
|
package/dist/useStreamRuntime.js
CHANGED
|
@@ -51,26 +51,33 @@ const getMessageContent = (msg) => {
|
|
|
51
51
|
return content;
|
|
52
52
|
};
|
|
53
53
|
const useStreamThreadRuntime = ({ adapters, messagesKey = "messages", ...streamOptions }) => {
|
|
54
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
54
55
|
const externalId = useAuiState((s) => s.threadListItem.externalId);
|
|
56
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
55
57
|
const stream = useStream({
|
|
56
58
|
...streamOptions,
|
|
57
59
|
threadId: externalId,
|
|
58
60
|
});
|
|
61
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
59
62
|
const [toolStatuses, setToolStatuses] = useState({});
|
|
60
63
|
const hasExecutingTools = Object.values(toolStatuses).some((s) => s?.type === "executing");
|
|
61
64
|
const effectiveIsRunning = stream.isLoading || hasExecutingTools;
|
|
65
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
62
66
|
const threadMessages = useExternalMessageConverter({
|
|
63
67
|
callback: convertLangChainBaseMessage,
|
|
64
68
|
messages: stream.messages,
|
|
65
69
|
isRunning: effectiveIsRunning,
|
|
66
70
|
});
|
|
71
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
67
72
|
const [runtimeRef] = useState(() => ({
|
|
68
73
|
get current() {
|
|
69
74
|
return runtime;
|
|
70
75
|
},
|
|
71
76
|
}));
|
|
77
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
72
78
|
const streamRef = useRef(stream);
|
|
73
79
|
streamRef.current = stream;
|
|
80
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
74
81
|
const toolInvocations = useToolInvocations({
|
|
75
82
|
state: {
|
|
76
83
|
messages: threadMessages,
|
|
@@ -94,12 +101,15 @@ const useStreamThreadRuntime = ({ adapters, messagesKey = "messages", ...streamO
|
|
|
94
101
|
},
|
|
95
102
|
setToolStatuses,
|
|
96
103
|
});
|
|
104
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
97
105
|
const extras = useMemo(() => ({
|
|
98
106
|
[symbolLangChainRuntimeExtras]: true,
|
|
99
107
|
interrupt: stream.interrupt,
|
|
100
108
|
interrupts: stream.interrupts,
|
|
101
109
|
submit: stream.submit,
|
|
102
|
-
|
|
110
|
+
values: stream.values,
|
|
111
|
+
}), [stream.interrupt, stream.interrupts, stream.submit, stream.values]);
|
|
112
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
103
113
|
const runtime = useExternalStoreRuntime({
|
|
104
114
|
isRunning: effectiveIsRunning,
|
|
105
115
|
messages: threadMessages,
|
|
@@ -163,6 +173,7 @@ export const useStreamRuntime = ({ cloud, ...options }) => {
|
|
|
163
173
|
const cloudAdapter = useCloudThreadListAdapter({ cloud });
|
|
164
174
|
return useRemoteThreadListRuntime({
|
|
165
175
|
runtimeHook: function RuntimeHook() {
|
|
176
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
166
177
|
return useStreamThreadRuntime(optionsRef.current);
|
|
167
178
|
},
|
|
168
179
|
adapter: cloudAdapter,
|
|
@@ -193,4 +204,13 @@ export const useLangChainSubmit = () => {
|
|
|
193
204
|
return submit(values, options);
|
|
194
205
|
};
|
|
195
206
|
};
|
|
207
|
+
export function useLangChainState(key, defaultValue) {
|
|
208
|
+
return useAuiState((s) => {
|
|
209
|
+
const extras = s.thread.extras;
|
|
210
|
+
if (!extras)
|
|
211
|
+
return defaultValue;
|
|
212
|
+
const value = asLangChainRuntimeExtras(extras).values[key];
|
|
213
|
+
return value !== undefined ? value : defaultValue;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
196
216
|
//# sourceMappingURL=useStreamRuntime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStreamRuntime.js","sourceRoot":"","sources":["../src/useStreamRuntime.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAOlD,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,0BAA0B,EAC1B,kBAAkB,GAEnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,EAAE,SAAS,EAAyB,MAAM,kBAAkB,CAAC;AAEpE,OAAO,EAAE,2BAA2B,EAAE,6BAA0B;AAEhE,MAAM,4BAA4B,GAAG,MAAM,CAAC,0BAA0B,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"useStreamRuntime.js","sourceRoot":"","sources":["../src/useStreamRuntime.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-langchain",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "LangChain useStream adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"langchain",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@assistant-ui/core": "^0.1.
|
|
33
|
-
"@assistant-ui/store": "^0.2.
|
|
32
|
+
"@assistant-ui/core": "^0.1.15",
|
|
33
|
+
"@assistant-ui/store": "^0.2.8",
|
|
34
34
|
"assistant-cloud": "*",
|
|
35
|
-
"assistant-stream": "^0.3.
|
|
35
|
+
"assistant-stream": "^0.3.12"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@langchain/react": "^0.3.3",
|
|
@@ -45,12 +45,15 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@langchain/core": "^1.1.
|
|
49
|
-
"@langchain/langgraph-sdk": "^1.8.
|
|
48
|
+
"@langchain/core": "^1.1.40",
|
|
49
|
+
"@langchain/langgraph-sdk": "^1.8.9",
|
|
50
50
|
"@langchain/react": "^0.3.3",
|
|
51
|
+
"@testing-library/react": "^16.3.2",
|
|
51
52
|
"@types/react": "^19.2.14",
|
|
53
|
+
"jsdom": "^29.0.2",
|
|
52
54
|
"react": "^19.2.5",
|
|
53
|
-
"
|
|
55
|
+
"vitest": "^4.1.5",
|
|
56
|
+
"@assistant-ui/x-buildutils": "0.0.6"
|
|
54
57
|
},
|
|
55
58
|
"publishConfig": {
|
|
56
59
|
"access": "public",
|
|
@@ -66,6 +69,8 @@
|
|
|
66
69
|
"url": "https://github.com/assistant-ui/assistant-ui/issues"
|
|
67
70
|
},
|
|
68
71
|
"scripts": {
|
|
69
|
-
"build": "aui-build"
|
|
72
|
+
"build": "aui-build",
|
|
73
|
+
"test": "vitest run",
|
|
74
|
+
"test:watch": "vitest"
|
|
70
75
|
}
|
|
71
76
|
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { renderHook } from "@testing-library/react";
|
|
5
|
+
|
|
6
|
+
type Selector = (state: unknown) => unknown;
|
|
7
|
+
const { mockUseAuiState } = vi.hoisted(() => ({
|
|
8
|
+
mockUseAuiState: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock(import("@assistant-ui/store"), async (importOriginal) => {
|
|
12
|
+
const actual = await importOriginal();
|
|
13
|
+
return {
|
|
14
|
+
...actual,
|
|
15
|
+
useAuiState: ((selector: Selector) =>
|
|
16
|
+
mockUseAuiState(selector)) as typeof actual.useAuiState,
|
|
17
|
+
useAui: (() => ({})) as unknown as typeof actual.useAui,
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
import { useLangChainState } from "./useStreamRuntime";
|
|
22
|
+
|
|
23
|
+
// The real guard symbol in useStreamRuntime is module-private. To stand in for
|
|
24
|
+
// runtime-produced extras, we use a Proxy whose `has` trap claims any symbol
|
|
25
|
+
// with the description "langchain-runtime-extras" is present.
|
|
26
|
+
const makeExtras = (values: Record<string, unknown>) =>
|
|
27
|
+
new Proxy(
|
|
28
|
+
{ values },
|
|
29
|
+
{
|
|
30
|
+
has: (target, key) =>
|
|
31
|
+
(typeof key === "symbol" &&
|
|
32
|
+
key.description === "langchain-runtime-extras") ||
|
|
33
|
+
Reflect.has(target, key),
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const runSelectorAgainst = (extras: unknown) => {
|
|
38
|
+
mockUseAuiState.mockImplementationOnce((selector: Selector) =>
|
|
39
|
+
selector({ thread: { extras } }),
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
describe("useLangChainState", () => {
|
|
44
|
+
it("returns undefined when extras are absent", () => {
|
|
45
|
+
runSelectorAgainst(undefined);
|
|
46
|
+
const { result } = renderHook(() => useLangChainState<number>("count"));
|
|
47
|
+
expect(result.current).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("returns defaultValue when extras are absent", () => {
|
|
51
|
+
runSelectorAgainst(undefined);
|
|
52
|
+
const { result } = renderHook(() => useLangChainState<number>("count", 42));
|
|
53
|
+
expect(result.current).toBe(42);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("reads the value for the given key from extras.values", () => {
|
|
57
|
+
runSelectorAgainst(makeExtras({ todos: [{ id: "a" }], count: 7 }));
|
|
58
|
+
const { result } = renderHook(() =>
|
|
59
|
+
useLangChainState<Array<{ id: string }>>("todos"),
|
|
60
|
+
);
|
|
61
|
+
expect(result.current).toEqual([{ id: "a" }]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("falls back to defaultValue when the key is missing from values", () => {
|
|
65
|
+
runSelectorAgainst(makeExtras({ unrelated: "x" }));
|
|
66
|
+
const { result } = renderHook(() => useLangChainState<number>("count", 99));
|
|
67
|
+
expect(result.current).toBe(99);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("returns undefined when the key is missing and no default is given", () => {
|
|
71
|
+
runSelectorAgainst(makeExtras({ unrelated: "x" }));
|
|
72
|
+
const { result } = renderHook(() => useLangChainState<number>("count"));
|
|
73
|
+
expect(result.current).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("returns the stored value (not default) when both are present", () => {
|
|
77
|
+
runSelectorAgainst(makeExtras({ count: 0 }));
|
|
78
|
+
const { result } = renderHook(() => useLangChainState<number>("count", 99));
|
|
79
|
+
// 0 is a valid value; must be preserved over the default.
|
|
80
|
+
expect(result.current).toBe(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("preserves explicit null over defaultValue", () => {
|
|
84
|
+
runSelectorAgainst(makeExtras({ flag: null }));
|
|
85
|
+
const { result } = renderHook(() =>
|
|
86
|
+
useLangChainState<string | null>("flag", "default"),
|
|
87
|
+
);
|
|
88
|
+
// LangGraph treats null as an explicit "cleared" value distinct
|
|
89
|
+
// from a missing key; the hook must not conflate them.
|
|
90
|
+
expect(result.current).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
});
|
package/src/useStreamRuntime.ts
CHANGED
|
@@ -31,6 +31,7 @@ type LangChainRuntimeExtras = {
|
|
|
31
31
|
values: Record<string, unknown> | null | undefined,
|
|
32
32
|
options?: Record<string, unknown>,
|
|
33
33
|
) => Promise<void>;
|
|
34
|
+
values: Record<string, unknown>;
|
|
34
35
|
};
|
|
35
36
|
|
|
36
37
|
const asLangChainRuntimeExtras = (extras: unknown): LangChainRuntimeExtras => {
|
|
@@ -110,15 +111,18 @@ const useStreamThreadRuntime = ({
|
|
|
110
111
|
messagesKey = "messages",
|
|
111
112
|
...streamOptions
|
|
112
113
|
}: Omit<UseStreamRuntimeOptions, "cloud">) => {
|
|
114
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
113
115
|
const externalId = useAuiState((s) => s.threadListItem.externalId) as
|
|
114
116
|
| string
|
|
115
117
|
| null;
|
|
116
118
|
|
|
119
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
117
120
|
const stream = useStream({
|
|
118
121
|
...streamOptions,
|
|
119
122
|
threadId: externalId,
|
|
120
123
|
});
|
|
121
124
|
|
|
125
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
122
126
|
const [toolStatuses, setToolStatuses] = useState<
|
|
123
127
|
Record<string, ToolExecutionStatus>
|
|
124
128
|
>({});
|
|
@@ -127,21 +131,25 @@ const useStreamThreadRuntime = ({
|
|
|
127
131
|
);
|
|
128
132
|
const effectiveIsRunning = stream.isLoading || hasExecutingTools;
|
|
129
133
|
|
|
134
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
130
135
|
const threadMessages = useExternalMessageConverter({
|
|
131
136
|
callback: convertLangChainBaseMessage,
|
|
132
137
|
messages: stream.messages as LangChainBaseMessage[],
|
|
133
138
|
isRunning: effectiveIsRunning,
|
|
134
139
|
});
|
|
135
140
|
|
|
141
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
136
142
|
const [runtimeRef] = useState(() => ({
|
|
137
143
|
get current() {
|
|
138
144
|
return runtime;
|
|
139
145
|
},
|
|
140
146
|
}));
|
|
141
147
|
|
|
148
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
142
149
|
const streamRef = useRef(stream);
|
|
143
150
|
streamRef.current = stream;
|
|
144
151
|
|
|
152
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
145
153
|
const toolInvocations = useToolInvocations({
|
|
146
154
|
state: {
|
|
147
155
|
messages: threadMessages,
|
|
@@ -169,16 +177,19 @@ const useStreamThreadRuntime = ({
|
|
|
169
177
|
setToolStatuses,
|
|
170
178
|
});
|
|
171
179
|
|
|
180
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
172
181
|
const extras = useMemo(
|
|
173
182
|
(): LangChainRuntimeExtras => ({
|
|
174
183
|
[symbolLangChainRuntimeExtras]: true,
|
|
175
184
|
interrupt: stream.interrupt,
|
|
176
185
|
interrupts: stream.interrupts,
|
|
177
186
|
submit: stream.submit,
|
|
187
|
+
values: stream.values,
|
|
178
188
|
}),
|
|
179
|
-
[stream.interrupt, stream.interrupts, stream.submit],
|
|
189
|
+
[stream.interrupt, stream.interrupts, stream.submit, stream.values],
|
|
180
190
|
);
|
|
181
191
|
|
|
192
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
182
193
|
const runtime = useExternalStoreRuntime({
|
|
183
194
|
isRunning: effectiveIsRunning,
|
|
184
195
|
messages: threadMessages,
|
|
@@ -254,6 +265,7 @@ export const useStreamRuntime = ({
|
|
|
254
265
|
const cloudAdapter = useCloudThreadListAdapter({ cloud });
|
|
255
266
|
return useRemoteThreadListRuntime({
|
|
256
267
|
runtimeHook: function RuntimeHook() {
|
|
268
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: intentional conditional/nested hook usage
|
|
257
269
|
return useStreamThreadRuntime(optionsRef.current);
|
|
258
270
|
},
|
|
259
271
|
adapter: cloudAdapter,
|
|
@@ -288,3 +300,28 @@ export const useLangChainSubmit = () => {
|
|
|
288
300
|
return submit(values, options);
|
|
289
301
|
};
|
|
290
302
|
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Read a custom LangGraph state key from the current thread. Mirrors
|
|
306
|
+
* `useStream().values[key]` from `@langchain/react` and updates when the
|
|
307
|
+
* stream emits new state.
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```tsx
|
|
311
|
+
* const todos = useLangChainState<Todo[]>("todos");
|
|
312
|
+
* const files = useLangChainState<Record<string, string>>("files", {});
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
export function useLangChainState<T>(key: string): T | undefined;
|
|
316
|
+
export function useLangChainState<T>(key: string, defaultValue: T): T;
|
|
317
|
+
export function useLangChainState<T>(
|
|
318
|
+
key: string,
|
|
319
|
+
defaultValue?: T,
|
|
320
|
+
): T | undefined {
|
|
321
|
+
return useAuiState((s) => {
|
|
322
|
+
const extras = s.thread.extras;
|
|
323
|
+
if (!extras) return defaultValue;
|
|
324
|
+
const value = asLangChainRuntimeExtras(extras).values[key] as T | undefined;
|
|
325
|
+
return value !== undefined ? value : defaultValue;
|
|
326
|
+
});
|
|
327
|
+
}
|