@ai-sdk/rsc 2.0.44 → 2.0.46

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/package.json +3 -2
  3. package/src/ai-state.test.ts +146 -0
  4. package/src/ai-state.tsx +210 -0
  5. package/src/index.ts +20 -0
  6. package/src/provider.tsx +149 -0
  7. package/src/rsc-client.ts +8 -0
  8. package/src/rsc-server.ts +5 -0
  9. package/src/rsc-shared.mts +11 -0
  10. package/src/shared-client/context.tsx +226 -0
  11. package/src/shared-client/index.ts +11 -0
  12. package/src/stream-ui/__snapshots__/render.ui.test.tsx.snap +91 -0
  13. package/src/stream-ui/__snapshots__/stream-ui.ui.test.tsx.snap +213 -0
  14. package/src/stream-ui/index.tsx +1 -0
  15. package/src/stream-ui/stream-ui.tsx +419 -0
  16. package/src/stream-ui/stream-ui.ui.test.tsx +321 -0
  17. package/src/streamable-ui/create-streamable-ui.tsx +148 -0
  18. package/src/streamable-ui/create-streamable-ui.ui.test.tsx +354 -0
  19. package/src/streamable-ui/create-suspended-chunk.tsx +84 -0
  20. package/src/streamable-value/create-streamable-value.test.tsx +179 -0
  21. package/src/streamable-value/create-streamable-value.ts +296 -0
  22. package/src/streamable-value/is-streamable-value.ts +10 -0
  23. package/src/streamable-value/read-streamable-value.tsx +113 -0
  24. package/src/streamable-value/read-streamable-value.ui.test.tsx +165 -0
  25. package/src/streamable-value/streamable-value.ts +37 -0
  26. package/src/streamable-value/use-streamable-value.tsx +91 -0
  27. package/src/types/index.ts +1 -0
  28. package/src/types.test-d.ts +17 -0
  29. package/src/types.ts +71 -0
  30. package/src/util/constants.ts +5 -0
  31. package/src/util/create-resolvable-promise.ts +28 -0
  32. package/src/util/is-async-generator.ts +7 -0
  33. package/src/util/is-function.ts +8 -0
  34. package/src/util/is-generator.ts +5 -0
@@ -0,0 +1,226 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps */
2
+ 'use client';
3
+
4
+ import * as React from 'react';
5
+
6
+ import * as jsondiffpatch from 'jsondiffpatch';
7
+ import { isFunction } from '../util/is-function';
8
+ import type {
9
+ AIProvider,
10
+ InferActions,
11
+ InferAIState,
12
+ InferUIState,
13
+ InternalAIProviderProps,
14
+ ValueOrUpdater,
15
+ } from '../types';
16
+
17
+ const InternalUIStateProvider = React.createContext<null | any>(null);
18
+ const InternalAIStateProvider = React.createContext<undefined | any>(undefined);
19
+ const InternalActionProvider = React.createContext<null | any>(null);
20
+ const InternalSyncUIStateProvider = React.createContext<null | any>(null);
21
+
22
+ export function InternalAIProvider({
23
+ children,
24
+ initialUIState,
25
+ initialAIState,
26
+ initialAIStatePatch,
27
+ wrappedActions,
28
+ wrappedSyncUIState,
29
+ }: InternalAIProviderProps) {
30
+ if (!('use' in React)) {
31
+ throw new Error('Unsupported React version.');
32
+ }
33
+
34
+ const uiState = React.useState(initialUIState);
35
+ const setUIState = uiState[1];
36
+
37
+ const resolvedInitialAIStatePatch = initialAIStatePatch
38
+ ? (React as any).use(initialAIStatePatch)
39
+ : undefined;
40
+ initialAIState = React.useMemo(() => {
41
+ if (resolvedInitialAIStatePatch) {
42
+ return jsondiffpatch.patch(
43
+ jsondiffpatch.clone(initialAIState),
44
+ resolvedInitialAIStatePatch,
45
+ );
46
+ }
47
+ return initialAIState;
48
+ }, [initialAIState, resolvedInitialAIStatePatch]);
49
+
50
+ const aiState = React.useState(initialAIState);
51
+ const setAIState = aiState[1];
52
+ const aiStateRef = React.useRef(aiState[0]);
53
+
54
+ React.useEffect(() => {
55
+ aiStateRef.current = aiState[0];
56
+ }, [aiState[0]]);
57
+
58
+ const clientWrappedActions = React.useMemo(
59
+ () =>
60
+ Object.fromEntries(
61
+ Object.entries(wrappedActions).map(([key, action]) => [
62
+ key,
63
+ async (...args: any) => {
64
+ const aiStateSnapshot = aiStateRef.current;
65
+ const [aiStateDelta, result] = await action(
66
+ aiStateSnapshot,
67
+ ...args,
68
+ );
69
+ (async () => {
70
+ const delta = await aiStateDelta;
71
+ if (delta !== undefined) {
72
+ aiState[1](
73
+ jsondiffpatch.patch(
74
+ jsondiffpatch.clone(aiStateSnapshot),
75
+ delta,
76
+ ),
77
+ );
78
+ }
79
+ })();
80
+ return result;
81
+ },
82
+ ]),
83
+ ),
84
+ [wrappedActions],
85
+ );
86
+
87
+ const clientWrappedSyncUIStateAction = React.useMemo(() => {
88
+ if (!wrappedSyncUIState) {
89
+ return () => {};
90
+ }
91
+
92
+ return async () => {
93
+ const aiStateSnapshot = aiStateRef.current;
94
+ const [aiStateDelta, uiState] =
95
+ await wrappedSyncUIState!(aiStateSnapshot);
96
+
97
+ if (uiState !== undefined) {
98
+ setUIState(uiState);
99
+ }
100
+
101
+ const delta = await aiStateDelta;
102
+ if (delta !== undefined) {
103
+ const patchedAiState = jsondiffpatch.patch(
104
+ jsondiffpatch.clone(aiStateSnapshot),
105
+ delta,
106
+ );
107
+ setAIState(patchedAiState);
108
+ }
109
+ };
110
+ }, [wrappedSyncUIState]);
111
+
112
+ return (
113
+ <InternalAIStateProvider.Provider value={aiState}>
114
+ <InternalUIStateProvider.Provider value={uiState}>
115
+ <InternalActionProvider.Provider value={clientWrappedActions}>
116
+ <InternalSyncUIStateProvider.Provider
117
+ value={clientWrappedSyncUIStateAction}
118
+ >
119
+ {children}
120
+ </InternalSyncUIStateProvider.Provider>
121
+ </InternalActionProvider.Provider>
122
+ </InternalUIStateProvider.Provider>
123
+ </InternalAIStateProvider.Provider>
124
+ );
125
+ }
126
+
127
+ export function useUIState<AI extends AIProvider = any>() {
128
+ type T = InferUIState<AI, any>;
129
+
130
+ const state = React.useContext<
131
+ [T, (v: T | ((v_: T) => T)) => void] | null | undefined
132
+ >(InternalUIStateProvider);
133
+ if (state === null) {
134
+ throw new Error('`useUIState` must be used inside an <AI> provider.');
135
+ }
136
+ if (!Array.isArray(state)) {
137
+ throw new Error('Invalid state');
138
+ }
139
+ if (state[0] === undefined) {
140
+ throw new Error(
141
+ '`initialUIState` must be provided to `createAI` or `<AI>`',
142
+ );
143
+ }
144
+ return state;
145
+ }
146
+
147
+ // TODO: How do we avoid causing a re-render when the AI state changes but you
148
+ // are only listening to a specific key? We need useSES perhaps?
149
+ function useAIState<AI extends AIProvider = any>(): [
150
+ InferAIState<AI, any>,
151
+ (newState: ValueOrUpdater<InferAIState<AI, any>>) => void,
152
+ ];
153
+ function useAIState<AI extends AIProvider = any>(
154
+ key: keyof InferAIState<AI, any>,
155
+ ): [
156
+ InferAIState<AI, any>[typeof key],
157
+ (newState: ValueOrUpdater<InferAIState<AI, any>[typeof key]>) => void,
158
+ ];
159
+ function useAIState<AI extends AIProvider = any>(
160
+ ...args: [] | [keyof InferAIState<AI, any>]
161
+ ) {
162
+ type T = InferAIState<AI, any>;
163
+
164
+ const state = React.useContext<
165
+ [T, (newState: ValueOrUpdater<T>) => void] | null | undefined
166
+ >(InternalAIStateProvider);
167
+ if (state === null) {
168
+ throw new Error('`useAIState` must be used inside an <AI> provider.');
169
+ }
170
+ if (!Array.isArray(state)) {
171
+ throw new Error('Invalid state');
172
+ }
173
+ if (state[0] === undefined) {
174
+ throw new Error(
175
+ '`initialAIState` must be provided to `createAI` or `<AI>`',
176
+ );
177
+ }
178
+ if (args.length >= 1 && typeof state[0] !== 'object') {
179
+ throw new Error(
180
+ 'When using `useAIState` with a key, the AI state must be an object.',
181
+ );
182
+ }
183
+
184
+ const key = args[0];
185
+ const setter = React.useCallback(
186
+ typeof key === 'undefined'
187
+ ? state[1]
188
+ : (newState: ValueOrUpdater<T>) => {
189
+ if (isFunction(newState)) {
190
+ return state[1](s => {
191
+ return { ...s, [key]: newState(s[key]) };
192
+ });
193
+ } else {
194
+ return state[1]({ ...state[0], [key]: newState });
195
+ }
196
+ },
197
+ [key],
198
+ );
199
+
200
+ if (args.length === 0) {
201
+ return state;
202
+ } else {
203
+ return [state[0][args[0]], setter];
204
+ }
205
+ }
206
+
207
+ export function useActions<AI extends AIProvider = any>() {
208
+ type T = InferActions<AI, any>;
209
+
210
+ const actions = React.useContext<T>(InternalActionProvider);
211
+ return actions;
212
+ }
213
+
214
+ export function useSyncUIState() {
215
+ const syncUIState = React.useContext<() => Promise<void>>(
216
+ InternalSyncUIStateProvider,
217
+ );
218
+
219
+ if (syncUIState === null) {
220
+ throw new Error('`useSyncUIState` must be used inside an <AI> provider.');
221
+ }
222
+
223
+ return syncUIState;
224
+ }
225
+
226
+ export { useAIState };
@@ -0,0 +1,11 @@
1
+ 'use client';
2
+
3
+ export { readStreamableValue } from '../streamable-value/read-streamable-value';
4
+ export { useStreamableValue } from '../streamable-value/use-streamable-value';
5
+ export {
6
+ InternalAIProvider,
7
+ useAIState,
8
+ useActions,
9
+ useSyncUIState,
10
+ useUIState,
11
+ } from './context';
@@ -0,0 +1,91 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`should emit React Nodes with async render function 1`] = `
4
+ {
5
+ "children": {
6
+ "children": {},
7
+ "props": {
8
+ "c": undefined,
9
+ "n": {
10
+ "done": false,
11
+ "next": {
12
+ "done": true,
13
+ "value": <div>
14
+ Weather
15
+ </div>,
16
+ },
17
+ "value": <div>
18
+ Weather
19
+ </div>,
20
+ },
21
+ },
22
+ "type": "",
23
+ },
24
+ "props": {
25
+ "fallback": undefined,
26
+ },
27
+ "type": "Symbol(react.suspense)",
28
+ }
29
+ `;
30
+
31
+ exports[`should emit React Nodes with generator render function 1`] = `
32
+ {
33
+ "children": {
34
+ "children": {},
35
+ "props": {
36
+ "c": undefined,
37
+ "n": {
38
+ "done": false,
39
+ "next": {
40
+ "done": false,
41
+ "next": {
42
+ "done": true,
43
+ "value": <div>
44
+ Weather
45
+ </div>,
46
+ },
47
+ "value": <div>
48
+ Weather
49
+ </div>,
50
+ },
51
+ "value": <div>
52
+ Loading...
53
+ </div>,
54
+ },
55
+ },
56
+ "type": "",
57
+ },
58
+ "props": {
59
+ "fallback": undefined,
60
+ },
61
+ "type": "Symbol(react.suspense)",
62
+ }
63
+ `;
64
+
65
+ exports[`should emit React Nodes with sync render function 1`] = `
66
+ {
67
+ "children": {
68
+ "children": {},
69
+ "props": {
70
+ "c": undefined,
71
+ "n": {
72
+ "done": false,
73
+ "next": {
74
+ "done": true,
75
+ "value": <div>
76
+ Weather
77
+ </div>,
78
+ },
79
+ "value": <div>
80
+ Weather
81
+ </div>,
82
+ },
83
+ },
84
+ "type": "",
85
+ },
86
+ "props": {
87
+ "fallback": undefined,
88
+ },
89
+ "type": "Symbol(react.suspense)",
90
+ }
91
+ `;
@@ -0,0 +1,213 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`options.headers > should pass headers to model 1`] = `
4
+ {
5
+ "children": {
6
+ "children": {},
7
+ "props": {
8
+ "c": undefined,
9
+ "n": {
10
+ "done": false,
11
+ "next": {
12
+ "done": true,
13
+ "value": "{ "content": "headers test" }",
14
+ },
15
+ "value": "{ "content": "headers test" }",
16
+ },
17
+ },
18
+ "type": "",
19
+ },
20
+ "props": {
21
+ "fallback": undefined,
22
+ },
23
+ "type": "Symbol(react.suspense)",
24
+ }
25
+ `;
26
+
27
+ exports[`options.providerMetadata > should pass provider metadata to model 1`] = `
28
+ {
29
+ "children": {
30
+ "children": {},
31
+ "props": {
32
+ "c": undefined,
33
+ "n": {
34
+ "done": false,
35
+ "next": {
36
+ "done": true,
37
+ "value": "{ "content": "provider metadata test" }",
38
+ },
39
+ "value": "{ "content": "provider metadata test" }",
40
+ },
41
+ },
42
+ "type": "",
43
+ },
44
+ "props": {
45
+ "fallback": undefined,
46
+ },
47
+ "type": "Symbol(react.suspense)",
48
+ }
49
+ `;
50
+
51
+ exports[`result.value > should render text 1`] = `
52
+ {
53
+ "children": {
54
+ "children": {},
55
+ "props": {
56
+ "c": undefined,
57
+ "n": {
58
+ "done": false,
59
+ "next": {
60
+ "done": false,
61
+ "next": {
62
+ "done": false,
63
+ "next": {
64
+ "done": false,
65
+ "next": {
66
+ "done": false,
67
+ "next": {
68
+ "done": false,
69
+ "next": {
70
+ "done": true,
71
+ "value": "{ "content": "Hello, world!" }",
72
+ },
73
+ "value": "{ "content": "Hello, world!" }",
74
+ },
75
+ "value": "{ "content": "Hello, world!"",
76
+ },
77
+ "value": "{ "content": "Hello, world",
78
+ },
79
+ "value": "{ "content": "Hello, ",
80
+ },
81
+ "value": "{ "content": ",
82
+ },
83
+ "value": "{ ",
84
+ },
85
+ },
86
+ "type": "",
87
+ },
88
+ "props": {
89
+ "fallback": undefined,
90
+ },
91
+ "type": "Symbol(react.suspense)",
92
+ }
93
+ `;
94
+
95
+ exports[`result.value > should render text function returned ui 1`] = `
96
+ {
97
+ "children": {
98
+ "children": {},
99
+ "props": {
100
+ "c": undefined,
101
+ "n": {
102
+ "done": false,
103
+ "next": {
104
+ "done": false,
105
+ "next": {
106
+ "done": false,
107
+ "next": {
108
+ "done": false,
109
+ "next": {
110
+ "done": false,
111
+ "next": {
112
+ "done": false,
113
+ "next": {
114
+ "done": true,
115
+ "value": <h1>
116
+ { "content": "Hello, world!" }
117
+ </h1>,
118
+ },
119
+ "value": <h1>
120
+ { "content": "Hello, world!" }
121
+ </h1>,
122
+ },
123
+ "value": <h1>
124
+ { "content": "Hello, world!"
125
+ </h1>,
126
+ },
127
+ "value": <h1>
128
+ { "content": "Hello, world
129
+ </h1>,
130
+ },
131
+ "value": <h1>
132
+ { "content": "Hello,
133
+ </h1>,
134
+ },
135
+ "value": <h1>
136
+ { "content":
137
+ </h1>,
138
+ },
139
+ "value": <h1>
140
+ {
141
+ </h1>,
142
+ },
143
+ },
144
+ "type": "",
145
+ },
146
+ "props": {
147
+ "fallback": undefined,
148
+ },
149
+ "type": "Symbol(react.suspense)",
150
+ }
151
+ `;
152
+
153
+ exports[`result.value > should render tool call results 1`] = `
154
+ {
155
+ "children": {
156
+ "children": {},
157
+ "props": {
158
+ "c": undefined,
159
+ "n": {
160
+ "done": true,
161
+ "value": <div>
162
+ tool1:
163
+ value
164
+ </div>,
165
+ },
166
+ },
167
+ "type": "",
168
+ },
169
+ "props": {
170
+ "fallback": undefined,
171
+ },
172
+ "type": "Symbol(react.suspense)",
173
+ }
174
+ `;
175
+
176
+ exports[`result.value > should render tool call results with generator render function 1`] = `
177
+ {
178
+ "children": {
179
+ "children": {},
180
+ "props": {
181
+ "c": undefined,
182
+ "n": {
183
+ "done": false,
184
+ "next": {
185
+ "done": true,
186
+ "value": <div>
187
+ tool:
188
+ value
189
+ </div>,
190
+ },
191
+ "value": <div>
192
+ Loading...
193
+ </div>,
194
+ },
195
+ },
196
+ "type": "",
197
+ },
198
+ "props": {
199
+ "fallback": undefined,
200
+ },
201
+ "type": "Symbol(react.suspense)",
202
+ }
203
+ `;
204
+
205
+ exports[`result.value > should show better error messages if legacy options are passed 1`] = `[Error: Tool definition in \`streamUI\` should not have \`render\` property. Use \`generate\` instead. Found in tool: tool1]`;
206
+
207
+ exports[`rsc - streamUI() onFinish callback > should contain final React node 1`] = `
208
+ <React.Suspense>
209
+ <Unknown
210
+ n={Promise {}}
211
+ />
212
+ </React.Suspense>
213
+ `;
@@ -0,0 +1 @@
1
+ export { streamUI } from './stream-ui';