@assistant-ui/core 0.2.18 → 0.2.19

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 (228) hide show
  1. package/dist/index.d.ts +4 -2
  2. package/dist/index.js +3 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/internal.d.ts +2 -1
  5. package/dist/internal.js +2 -1
  6. package/dist/model-context/interactable-composer-metadata.d.ts +105 -0
  7. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -0
  8. package/dist/model-context/interactable-composer-metadata.js +259 -0
  9. package/dist/model-context/interactable-composer-metadata.js.map +1 -0
  10. package/dist/model-context/registry.d.ts.map +1 -1
  11. package/dist/model-context/registry.js +1 -0
  12. package/dist/model-context/registry.js.map +1 -1
  13. package/dist/model-context/types.d.ts +6 -0
  14. package/dist/model-context/types.d.ts.map +1 -1
  15. package/dist/model-context/types.js +4 -0
  16. package/dist/model-context/types.js.map +1 -1
  17. package/dist/react/AssistantProvider.d.ts +1 -1
  18. package/dist/react/AssistantProvider.js +1 -1
  19. package/dist/react/AssistantRuntimeProvider.d.ts +1 -1
  20. package/dist/react/RuntimeAdapter.js +1 -1
  21. package/dist/react/client/Interactables.d.ts +8 -2
  22. package/dist/react/client/Interactables.d.ts.map +1 -1
  23. package/dist/react/client/Interactables.js +190 -94
  24. package/dist/react/client/Interactables.js.map +1 -1
  25. package/dist/react/client/Tools.js +1 -1
  26. package/dist/react/client/interactable-model-context.d.ts +9 -6
  27. package/dist/react/client/interactable-model-context.d.ts.map +1 -1
  28. package/dist/react/client/interactable-model-context.js +154 -32
  29. package/dist/react/client/interactable-model-context.js.map +1 -1
  30. package/dist/react/index.d.ts +15 -6
  31. package/dist/react/index.js +12 -5
  32. package/dist/react/interactables-legacy/Interactables.d.ts +12 -0
  33. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -0
  34. package/dist/react/interactables-legacy/Interactables.js +264 -0
  35. package/dist/react/interactables-legacy/Interactables.js.map +1 -0
  36. package/dist/react/interactables-legacy/interactable-model-context.d.ts +12 -0
  37. package/dist/react/interactables-legacy/interactable-model-context.d.ts.map +1 -0
  38. package/dist/react/interactables-legacy/interactable-model-context.js +54 -0
  39. package/dist/react/interactables-legacy/interactable-model-context.js.map +1 -0
  40. package/dist/react/interactables-legacy/scopes.d.ts +58 -0
  41. package/dist/react/interactables-legacy/scopes.d.ts.map +1 -0
  42. package/dist/react/interactables-legacy/scopes.js +0 -0
  43. package/dist/react/interactables-legacy/useAssistantInteractable.d.ts +31 -0
  44. package/dist/react/interactables-legacy/useAssistantInteractable.d.ts.map +1 -0
  45. package/dist/react/{model-context → interactables-legacy}/useAssistantInteractable.js +6 -2
  46. package/dist/react/interactables-legacy/useAssistantInteractable.js.map +1 -0
  47. package/dist/react/interactables-legacy/useInteractableState.d.ts +21 -0
  48. package/dist/react/interactables-legacy/useInteractableState.d.ts.map +1 -0
  49. package/dist/react/interactables-legacy/useInteractableState.js +84 -0
  50. package/dist/react/interactables-legacy/useInteractableState.js.map +1 -0
  51. package/dist/react/model-context/interactableTool.d.ts +53 -0
  52. package/dist/react/model-context/interactableTool.d.ts.map +1 -0
  53. package/dist/react/model-context/interactableTool.js +188 -0
  54. package/dist/react/model-context/interactableTool.js.map +1 -0
  55. package/dist/react/model-context/useAssistantContext.js +1 -1
  56. package/dist/react/model-context/useAssistantDataUI.js +1 -1
  57. package/dist/react/model-context/useAssistantInstructions.js +1 -1
  58. package/dist/react/model-context/useAssistantTool.js +1 -1
  59. package/dist/react/model-context/useAssistantToolUI.js +1 -1
  60. package/dist/react/model-context/useAuiToolOverrides.js +1 -1
  61. package/dist/react/model-context/useInteractable.d.ts +73 -0
  62. package/dist/react/model-context/useInteractable.d.ts.map +1 -0
  63. package/dist/react/model-context/useInteractable.js +100 -0
  64. package/dist/react/model-context/useInteractable.js.map +1 -0
  65. package/dist/react/model-context/useInteractableState.d.ts +8 -5
  66. package/dist/react/model-context/useInteractableState.d.ts.map +1 -1
  67. package/dist/react/model-context/useInteractableState.js +48 -48
  68. package/dist/react/model-context/useInteractableState.js.map +1 -1
  69. package/dist/react/model-context/useInteractableVersions.d.ts +19 -0
  70. package/dist/react/model-context/useInteractableVersions.d.ts.map +1 -0
  71. package/dist/react/model-context/useInteractableVersions.js +51 -0
  72. package/dist/react/model-context/useInteractableVersions.js.map +1 -0
  73. package/dist/react/primitive-hooks/useThreadMessageIds.d.ts +15 -0
  74. package/dist/react/primitive-hooks/useThreadMessageIds.d.ts.map +1 -0
  75. package/dist/react/primitive-hooks/useThreadMessageIds.js +26 -0
  76. package/dist/react/primitive-hooks/useThreadMessageIds.js.map +1 -0
  77. package/dist/react/primitive-hooks/useVoice.js +1 -1
  78. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +1 -1
  79. package/dist/react/primitives/composer/ComposerAttachments.js +1 -1
  80. package/dist/react/primitives/composer/ComposerQueue.js +1 -1
  81. package/dist/react/primitives/generativeUI/GenerativeUI.js +1 -1
  82. package/dist/react/primitives/message/MessageAttachments.js +1 -1
  83. package/dist/react/primitives/message/MessageGroupedParts.js +1 -1
  84. package/dist/react/primitives/message/MessageParts.js +1 -1
  85. package/dist/react/primitives/message/MessageQuote.js +1 -1
  86. package/dist/react/primitives/part/PartMessages.d.ts +5 -2
  87. package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
  88. package/dist/react/primitives/part/PartMessages.js +8 -8
  89. package/dist/react/primitives/part/PartMessages.js.map +1 -1
  90. package/dist/react/primitives/thread/ThreadMessages.d.ts +31 -1
  91. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  92. package/dist/react/primitives/thread/ThreadMessages.js +64 -2
  93. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  94. package/dist/react/primitives/thread/ThreadSuggestions.js +1 -1
  95. package/dist/react/primitives/threadList/ThreadListItems.js +1 -1
  96. package/dist/react/providers/MessageByIdProvider.d.ts +9 -0
  97. package/dist/react/providers/MessageByIdProvider.d.ts.map +1 -0
  98. package/dist/react/providers/MessageByIdProvider.js +57 -0
  99. package/dist/react/providers/MessageByIdProvider.js.map +1 -0
  100. package/dist/react/providers/ReadonlyThreadProvider.js +1 -1
  101. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +1 -1
  102. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +3 -0
  103. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  104. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +16 -1
  105. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  106. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +1 -1
  107. package/dist/react/runtimes/cloud/auiV0.js +1 -1
  108. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +1 -1
  109. package/dist/react/runtimes/createRuntimeExtras.d.ts +20 -0
  110. package/dist/react/runtimes/createRuntimeExtras.d.ts.map +1 -0
  111. package/dist/react/runtimes/createRuntimeExtras.js +43 -0
  112. package/dist/react/runtimes/createRuntimeExtras.js.map +1 -0
  113. package/dist/react/runtimes/useLocalRuntime.js +1 -1
  114. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  115. package/dist/react/runtimes/useRemoteThreadListRuntime.js +6 -2
  116. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  117. package/dist/react/runtimes/useStreamingTiming.d.ts +26 -0
  118. package/dist/react/runtimes/useStreamingTiming.d.ts.map +1 -0
  119. package/dist/react/runtimes/useStreamingTiming.js +43 -0
  120. package/dist/react/runtimes/useStreamingTiming.js.map +1 -0
  121. package/dist/react/types/scopes/interactables.d.ts +40 -24
  122. package/dist/react/types/scopes/interactables.d.ts.map +1 -1
  123. package/dist/react/types/store-augmentation.d.ts +3 -1
  124. package/dist/react/types/store-augmentation.d.ts.map +1 -1
  125. package/dist/react/utils/useJSONEqual.d.ts +11 -0
  126. package/dist/react/utils/useJSONEqual.d.ts.map +1 -0
  127. package/dist/react/utils/useJSONEqual.js +30 -0
  128. package/dist/react/utils/useJSONEqual.js.map +1 -0
  129. package/dist/runtime/base/base-composer-runtime-core.d.ts +5 -0
  130. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  131. package/dist/runtime/base/base-composer-runtime-core.js +13 -0
  132. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  133. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  134. package/dist/runtime/base/default-edit-composer-runtime-core.js +6 -1
  135. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  136. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  137. package/dist/runtime/base/default-thread-composer-runtime-core.js +4 -1
  138. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  139. package/dist/runtime/utils/message-repository.d.ts +1 -0
  140. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  141. package/dist/runtime/utils/message-repository.js +6 -3
  142. package/dist/runtime/utils/message-repository.js.map +1 -1
  143. package/dist/runtime/utils/streaming-timing.d.ts +59 -0
  144. package/dist/runtime/utils/streaming-timing.d.ts.map +1 -0
  145. package/dist/runtime/utils/streaming-timing.js +76 -0
  146. package/dist/runtime/utils/streaming-timing.js.map +1 -0
  147. package/dist/runtime/utils/thread-message-like.js +2 -1
  148. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  149. package/dist/runtimes/external-store/external-store-adapter.d.ts +26 -1
  150. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  151. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +9 -0
  152. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  153. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +19 -0
  154. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  155. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  156. package/dist/runtimes/local/local-thread-runtime-core.js +1 -1
  157. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  158. package/dist/runtimes/remote-thread-list/types.d.ts +12 -0
  159. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  160. package/dist/store/clients/suggestions.js +1 -1
  161. package/dist/store/clients/thread-message-client.js +1 -1
  162. package/dist/store/runtime-clients/composer-runtime-client.js +1 -1
  163. package/dist/store/runtime-clients/message-runtime-client.js +1 -1
  164. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  165. package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
  166. package/dist/tests/remote-thread-list-test-helpers.d.ts +1 -1
  167. package/dist/tests/remote-thread-list-test-helpers.d.ts.map +1 -1
  168. package/dist/tests/remote-thread-list-test-helpers.js +3 -2
  169. package/dist/tests/remote-thread-list-test-helpers.js.map +1 -1
  170. package/package.json +9 -9
  171. package/src/index.ts +17 -0
  172. package/src/internal.ts +7 -0
  173. package/src/model-context/interactable-composer-metadata.test.ts +550 -0
  174. package/src/model-context/interactable-composer-metadata.ts +395 -0
  175. package/src/model-context/registry.ts +5 -0
  176. package/src/model-context/types.ts +12 -0
  177. package/src/react/client/Interactables.test.ts +358 -0
  178. package/src/react/client/Interactables.ts +339 -86
  179. package/src/react/client/interactable-model-context.test.ts +418 -0
  180. package/src/react/client/interactable-model-context.ts +252 -58
  181. package/src/react/index.ts +72 -3
  182. package/src/react/interactables-legacy/Interactables.ts +332 -0
  183. package/src/react/interactables-legacy/interactable-model-context.test.ts +54 -0
  184. package/src/react/interactables-legacy/interactable-model-context.ts +80 -0
  185. package/src/react/interactables-legacy/scopes.ts +66 -0
  186. package/src/react/{model-context → interactables-legacy}/useAssistantInteractable.ts +10 -1
  187. package/src/react/interactables-legacy/useInteractableState.ts +67 -0
  188. package/src/react/model-context/interactableTool.tsx +175 -0
  189. package/src/react/model-context/useInteractable.ts +233 -0
  190. package/src/react/model-context/useInteractableState.ts +31 -23
  191. package/src/react/model-context/useInteractableVersions.ts +48 -0
  192. package/src/react/primitive-hooks/useThreadMessageIds.ts +27 -0
  193. package/src/react/primitives/part/PartMessages.test.tsx +17 -0
  194. package/src/react/primitives/part/PartMessages.tsx +13 -8
  195. package/src/react/primitives/thread/ThreadMessages.tsx +67 -0
  196. package/src/react/providers/MessageByIdProvider.tsx +23 -0
  197. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +21 -1
  198. package/src/react/runtimes/createRuntimeExtras.test.ts +58 -0
  199. package/src/react/runtimes/createRuntimeExtras.ts +68 -0
  200. package/src/react/runtimes/createRuntimeExtras.use.test.ts +50 -0
  201. package/src/react/runtimes/useRemoteThreadListRuntime.ts +13 -1
  202. package/src/react/runtimes/useStreamingTiming.ts +68 -0
  203. package/src/react/types/scopes/interactables.ts +45 -23
  204. package/src/react/types/store-augmentation.ts +4 -2
  205. package/src/react/utils/useJSONEqual.ts +20 -0
  206. package/src/runtime/base/base-composer-runtime-core.ts +13 -0
  207. package/src/runtime/base/default-edit-composer-runtime-core.ts +18 -1
  208. package/src/runtime/base/default-thread-composer-runtime-core.ts +11 -1
  209. package/src/runtime/utils/message-repository.ts +11 -7
  210. package/src/runtime/utils/streaming-timing.test.ts +301 -0
  211. package/src/runtime/utils/streaming-timing.ts +161 -0
  212. package/src/runtime/utils/thread-message-like.ts +2 -1
  213. package/src/runtimes/external-store/external-store-adapter.ts +28 -0
  214. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +33 -1
  215. package/src/runtimes/local/local-thread-runtime-core.ts +4 -1
  216. package/src/runtimes/remote-thread-list/types.ts +13 -0
  217. package/src/tests/MessageRepository.test.ts +2 -0
  218. package/src/tests/composer-can-send.test.ts +1 -0
  219. package/src/tests/default-edit-composer-runtime-core.test.ts +78 -1
  220. package/src/tests/default-thread-composer-runtime-core.test.ts +82 -0
  221. package/src/tests/external-store-thread-runtime-core.test.ts +337 -0
  222. package/src/tests/groupParts.test.ts +32 -0
  223. package/src/tests/remote-thread-list-reactive-threadId.test.ts +90 -0
  224. package/src/tests/remote-thread-list-test-helpers.ts +2 -1
  225. package/src/tests/thread-message-like.test.ts +50 -0
  226. package/dist/react/model-context/useAssistantInteractable.d.ts +0 -22
  227. package/dist/react/model-context/useAssistantInteractable.d.ts.map +0 -1
  228. package/dist/react/model-context/useAssistantInteractable.js.map +0 -1
@@ -0,0 +1,175 @@
1
+ import type { ReactNode } from "react";
2
+ import type { Unstable_InteractableStateSchema } from "../types/scopes/interactables";
3
+ import type { ToolDefinition } from "./toolbox";
4
+ import type { ToolCallMessagePartComponent } from "../types/MessagePartComponentTypes";
5
+ import {
6
+ unstable_useInteractable as useInteractable,
7
+ type Unstable_InferInteractableState,
8
+ type Unstable_InteractableVersionInfo,
9
+ } from "./useInteractable";
10
+ import { unstable_useInteractableState as useInteractableState } from "./useInteractableState";
11
+
12
+ export type Unstable_InteractableToolRenderProps<TState> = {
13
+ /**
14
+ * The live state. While `streaming` is true, fields the model has not
15
+ * finished generating may still be missing.
16
+ */
17
+ state: TState;
18
+ setState: (updater: TState | ((prev: TState) => TState)) => void;
19
+ /**
20
+ * This message's version of the instance: its state as of this point in the
21
+ * conversation, whether it is the most recent edit, and a `restore()` back
22
+ * to it. `undefined` while streaming.
23
+ */
24
+ version: Unstable_InteractableVersionInfo<TState> | undefined;
25
+ id: string;
26
+ /** True while the tool call's arguments are still streaming in. */
27
+ streaming: boolean;
28
+ };
29
+
30
+ export type Unstable_InteractableToolConfig<
31
+ TSchema extends Unstable_InteractableStateSchema,
32
+ > = {
33
+ description: string;
34
+ stateSchema: TSchema;
35
+ render: (
36
+ props: Unstable_InteractableToolRenderProps<
37
+ Unstable_InferInteractableState<TSchema>
38
+ >,
39
+ ) => ReactNode;
40
+ };
41
+
42
+ const noop = () => {};
43
+
44
+ const UPDATE_TOOL_PREFIX = "update_";
45
+
46
+ /**
47
+ * Defines a tool that creates a thread-scoped interactable from its arguments,
48
+ * for assignment to a toolkit entry — the entry key is the interactable name:
49
+ *
50
+ * ```tsx
51
+ * notepad: unstable_interactableTool({
52
+ * description: "A notepad the user can read and edit.",
53
+ * stateSchema: notepadSchema,
54
+ * render: (props) => <Notepad {...props} />,
55
+ * }),
56
+ * ```
57
+ *
58
+ * `render` shows at the creating call and — installed automatically — at every
59
+ * `update_{name}` call, with streaming previews, instance-id wiring, and
60
+ * registration handled. It receives the live `state`/`setState` plus this
61
+ * message's `version`; whether older messages render frozen history or stay
62
+ * live-editable is the render function's choice.
63
+ *
64
+ * @deprecated Unstable / Experimental (not actually removed).
65
+ */
66
+ export const unstable_interactableTool = <
67
+ TSchema extends Unstable_InteractableStateSchema,
68
+ >(
69
+ config: Unstable_InteractableToolConfig<TSchema>,
70
+ ): ToolDefinition<Record<string, unknown>, { success: true }> => {
71
+ type TState = Unstable_InferInteractableState<TSchema>;
72
+ type UpdateArgs = { id?: string | undefined } & Record<string, unknown>;
73
+ type UpdateResult =
74
+ | { success: true; id: string }
75
+ | { success: false; error: string };
76
+
77
+ const Anchor = ({
78
+ name,
79
+ id,
80
+ initial,
81
+ }: {
82
+ name: string;
83
+ id?: string | undefined;
84
+ initial: TState;
85
+ }) => {
86
+ const [state, { id: resolvedId, setState, version }] = useInteractable(
87
+ name,
88
+ {
89
+ id,
90
+ description: config.description,
91
+ stateSchema: config.stateSchema,
92
+ initialState: initial,
93
+ updateRender: UpdateToolUI,
94
+ },
95
+ );
96
+ return (
97
+ <>
98
+ {config.render({
99
+ state,
100
+ setState,
101
+ version,
102
+ id: resolvedId,
103
+ streaming: false,
104
+ })}
105
+ </>
106
+ );
107
+ };
108
+
109
+ const StreamingUpdate = ({ id }: { id: string }) => {
110
+ const [state, { setState }] = useInteractableState<TState>(id);
111
+ if (state === undefined) return null;
112
+ return (
113
+ <>
114
+ {config.render({
115
+ state,
116
+ setState,
117
+ version: undefined,
118
+ id,
119
+ streaming: true,
120
+ })}
121
+ </>
122
+ );
123
+ };
124
+
125
+ const UpdateToolUI: ToolCallMessagePartComponent<
126
+ UpdateArgs,
127
+ UpdateResult
128
+ > = ({ toolName, args, result }) => {
129
+ if (!result) {
130
+ return typeof args.id === "string" ? (
131
+ <StreamingUpdate id={args.id} />
132
+ ) : null;
133
+ }
134
+ if (!result.success) return null;
135
+ const { id: _id, ...partial } = args;
136
+ // The interactable name is the creating tool's name (the toolkit entry
137
+ // key), so the update tool's name reverses by stripping the prefix.
138
+ return (
139
+ <Anchor
140
+ name={toolName.slice(UPDATE_TOOL_PREFIX.length)}
141
+ initial={partial as TState}
142
+ />
143
+ );
144
+ };
145
+
146
+ const InteractableToolUI: ToolCallMessagePartComponent<
147
+ Record<string, unknown>,
148
+ { success: true }
149
+ > = ({ toolName, toolCallId, args, result }) => {
150
+ const state = args as TState;
151
+ if (!result) {
152
+ return (
153
+ <>
154
+ {config.render({
155
+ state,
156
+ setState: noop,
157
+ version: undefined,
158
+ id: toolCallId,
159
+ streaming: true,
160
+ })}
161
+ </>
162
+ );
163
+ }
164
+ return <Anchor name={toolName} id={toolCallId} initial={state} />;
165
+ };
166
+
167
+ return {
168
+ type: "frontend",
169
+ description: config.description,
170
+ parameters: config.stateSchema,
171
+ display: "standalone",
172
+ execute: async () => ({ success: true as const }),
173
+ render: InteractableToolUI,
174
+ };
175
+ };
@@ -0,0 +1,233 @@
1
+ "use client";
2
+
3
+ import { useEffect, useId, useMemo, useRef } from "react";
4
+ import { useAui, useAuiState } from "@assistant-ui/store";
5
+ import type { Unstable_InteractableStateSchema } from "../types/scopes/interactables";
6
+ import type { ToolCallMessagePartComponent } from "../types/MessagePartComponentTypes";
7
+ import {
8
+ unstable_getInteractableVersions as getInteractableVersions,
9
+ interactableToolName,
10
+ } from "../../model-context/interactable-composer-metadata";
11
+ import { unstable_useInteractableState as useInteractableState } from "./useInteractableState";
12
+ import { useJSONEqual } from "../utils/useJSONEqual";
13
+
14
+ /**
15
+ * The state type described by an interactable's `stateSchema`. Resolves the
16
+ * output type of a StandardSchemaV1 schema (e.g. Zod); plain JSON Schema
17
+ * falls back to `unknown`.
18
+ */
19
+ export type Unstable_InferInteractableState<TSchema> = TSchema extends {
20
+ "~standard": { types?: { output: infer TOutput } | undefined };
21
+ }
22
+ ? TOutput
23
+ : unknown;
24
+
25
+ /**
26
+ * One message's version of an interactable, for components rendered inside
27
+ * tool-call parts. Show `state` to display history; call `restore()` to set
28
+ * the live instance back to it.
29
+ */
30
+ export type Unstable_InteractableVersionInfo<TState> = {
31
+ /** The interactable's state as it was at this message. */
32
+ state: TState;
33
+ /** Whether this message holds the instance's most recent tool-driven version. */
34
+ isLatest: boolean;
35
+ /** Sets the live state back to this version. */
36
+ restore: () => void;
37
+ };
38
+
39
+ /**
40
+ * Unstable / Experimental — the interactables API is still evolving and may change in any release.
41
+ * @deprecated Unstable / Experimental (not actually removed).
42
+ */
43
+ export type Unstable_InteractableConfig<
44
+ TSchema extends Unstable_InteractableStateSchema,
45
+ > = {
46
+ description: string;
47
+ stateSchema: TSchema;
48
+ initialState: Unstable_InferInteractableState<TSchema>;
49
+ /** Unique instance ID; required to address this instance when multiple interactables share a name. Auto-generated if omitted. */
50
+ id?: string | undefined;
51
+ /**
52
+ * Component installed as the tool UI for this interactable's `update_{name}`
53
+ * tool calls, so a model edit re-renders the interactable at the message
54
+ * that made it instead of only mutating an earlier one. Prefer
55
+ * `unstable_interactableTool`, which wires this up. Pass a stable component reference;
56
+ * changing identity re-registers the tool UI.
57
+ */
58
+ updateRender?: ToolCallMessagePartComponent | undefined;
59
+ };
60
+
61
+ const useInteractable = <TSchema extends Unstable_InteractableStateSchema>(
62
+ name: string,
63
+ config: Unstable_InteractableConfig<TSchema>,
64
+ ): readonly [
65
+ Unstable_InferInteractableState<TSchema>,
66
+ {
67
+ id: string;
68
+ /**
69
+ * This message's version of the instance, when rendered inside a
70
+ * tool-created interactable message part; `undefined` elsewhere.
71
+ */
72
+ version:
73
+ | Unstable_InteractableVersionInfo<
74
+ Unstable_InferInteractableState<TSchema>
75
+ >
76
+ | undefined;
77
+ setState: (
78
+ updater:
79
+ | Unstable_InferInteractableState<TSchema>
80
+ | ((
81
+ prev: Unstable_InferInteractableState<TSchema>,
82
+ ) => Unstable_InferInteractableState<TSchema>),
83
+ ) => void;
84
+ isPending: boolean;
85
+ error: unknown;
86
+ flush: () => Promise<void>;
87
+ },
88
+ ] => {
89
+ const aui = useAui();
90
+
91
+ const autoId = useId().replace(/[^a-zA-Z0-9]/g, "");
92
+
93
+ // Whether this component renders inside a message part is fixed for its
94
+ // lifetime, so conditioning the selectors on it is safe.
95
+ const inPart = aui.part.source != null;
96
+ const updateToolName = interactableToolName(name);
97
+ const part = useAuiState((s) => {
98
+ if (!inPart) return undefined;
99
+ const p = s.part;
100
+ return p.type === "tool-call" ? p : undefined;
101
+ });
102
+
103
+ // Inside an update_{name} part, the instance id comes from the call itself:
104
+ // the result's resolved id (covers id-less calls), or the id argument.
105
+ let inferredId: string | undefined;
106
+ if (part?.toolName === updateToolName) {
107
+ const result = part.result as Record<string, unknown> | undefined;
108
+ const args = part.args as Record<string, unknown> | undefined;
109
+ if (result?.success !== false) {
110
+ if (typeof result?.id === "string") inferredId = result.id;
111
+ else if (typeof args?.id === "string") inferredId = args.id;
112
+ }
113
+ }
114
+
115
+ const id = config.id ?? inferredId ?? autoId;
116
+ const internalScope =
117
+ part?.toolName === name || part?.toolName === updateToolName
118
+ ? "thread"
119
+ : undefined;
120
+
121
+ const stateSchemaRef = useRef(config.stateSchema);
122
+ stateSchemaRef.current = config.stateSchema;
123
+ const initialStateRef = useRef(config.initialState);
124
+ initialStateRef.current = config.initialState;
125
+
126
+ const interactables = useAuiState(() => aui.unstable_interactables());
127
+
128
+ useEffect(() => {
129
+ return interactables.register({
130
+ id,
131
+ name,
132
+ description: config.description,
133
+ stateSchema: stateSchemaRef.current,
134
+ initialState: initialStateRef.current,
135
+ updateRender: config.updateRender,
136
+ ...(internalScope ? { scope: internalScope } : {}),
137
+ } as Parameters<typeof interactables.register>[0] & {
138
+ scope?: "thread" | undefined;
139
+ });
140
+ }, [
141
+ interactables,
142
+ id,
143
+ name,
144
+ config.description,
145
+ internalScope,
146
+ config.updateRender,
147
+ ]);
148
+
149
+ const myToolCallId = part?.toolCallId;
150
+
151
+ const [registeredState, methods] =
152
+ useInteractableState<Unstable_InferInteractableState<TSchema>>(id);
153
+ const { setState } = methods;
154
+
155
+ const versionValue = useAuiState(
156
+ useJSONEqual((s) => {
157
+ if (!internalScope || !myToolCallId) return undefined;
158
+ const versions = getInteractableVersions(s.thread.messages, id, name);
159
+ if (!versions.some((v) => v.origin === "create" && v.toolCallId === id)) {
160
+ return undefined;
161
+ }
162
+ const mine = versions.find((v) => v.toolCallId === myToolCallId);
163
+ if (!mine) return undefined;
164
+ const latestToolCallId = versions.findLast(
165
+ (v) => v.toolCallId !== undefined,
166
+ )?.toolCallId;
167
+ return { state: mine.state, isLatest: latestToolCallId === myToolCallId };
168
+ }),
169
+ );
170
+
171
+ const version = useMemo(
172
+ () =>
173
+ versionValue && {
174
+ state: versionValue.state as Unstable_InferInteractableState<TSchema>,
175
+ isLatest: versionValue.isLatest,
176
+ restore: () =>
177
+ setState(
178
+ versionValue.state as Unstable_InferInteractableState<TSchema>,
179
+ ),
180
+ },
181
+ [versionValue, setState],
182
+ );
183
+
184
+ const state =
185
+ registeredState === undefined ? config.initialState : registeredState;
186
+
187
+ return [state, { id, version, ...methods }] as const;
188
+ };
189
+
190
+ /**
191
+ * Registers an interactable with the AI assistant and returns its live state,
192
+ * like `useState` that the model can also read and update.
193
+ *
194
+ * Call this once per place that shows the interactable. Other components can
195
+ * read and write the same instance by passing its `id` to
196
+ * `unstable_useInteractableState`.
197
+ *
198
+ * For tool-created interactables rendered inside tool-call message parts,
199
+ * `version` carries this message's version of the instance — its state as of
200
+ * that point in the conversation, whether it is the most recent tool-driven
201
+ * version, and a `restore()` back to it. Whether older messages render frozen history or stay
202
+ * live-editable is the component's choice. Inside an `update_{name}` part the
203
+ * instance `id` is inferred from the call, so the same component works at the
204
+ * creating call and at update calls.
205
+ *
206
+ * @deprecated Unstable / Experimental (not actually removed).
207
+ */
208
+ export const unstable_useInteractable: <
209
+ TSchema extends Unstable_InteractableStateSchema,
210
+ >(
211
+ name: string,
212
+ config: Unstable_InteractableConfig<TSchema>,
213
+ ) => readonly [
214
+ Unstable_InferInteractableState<TSchema>,
215
+ {
216
+ id: string;
217
+ version:
218
+ | Unstable_InteractableVersionInfo<
219
+ Unstable_InferInteractableState<TSchema>
220
+ >
221
+ | undefined;
222
+ setState: (
223
+ updater:
224
+ | Unstable_InferInteractableState<TSchema>
225
+ | ((
226
+ prev: Unstable_InferInteractableState<TSchema>,
227
+ ) => Unstable_InferInteractableState<TSchema>),
228
+ ) => void;
229
+ isPending: boolean;
230
+ error: unknown;
231
+ flush: () => Promise<void>;
232
+ },
233
+ ] = useInteractable;
@@ -1,21 +1,16 @@
1
+ "use client";
2
+
1
3
  import { useCallback } from "react";
2
4
  import { useAui, useAuiState } from "@assistant-ui/store";
3
5
 
4
6
  type StateUpdater<TState> = TState | ((prev: TState) => TState);
5
7
 
6
- /**
7
- * Reads and writes the state of a registered interactable.
8
- *
9
- * Pair with {@link useAssistantInteractable} which handles registration.
10
- */
11
- export const useInteractableState = <TState>(
8
+ const useInteractableState = <TState>(
12
9
  id: string,
13
- fallback: TState,
14
10
  ): [
15
- TState,
11
+ TState | undefined,
16
12
  {
17
13
  setState: (updater: StateUpdater<TState>) => void;
18
- setSelected: (selected: boolean) => void;
19
14
  isPending: boolean;
20
15
  error: unknown;
21
16
  flush: () => Promise<void>;
@@ -23,15 +18,17 @@ export const useInteractableState = <TState>(
23
18
  ] => {
24
19
  const aui = useAui();
25
20
 
26
- const state =
27
- (useAuiState((s) => s.interactables.definitions[id]?.state) as TState) ??
28
- (fallback as TState);
21
+ const state = useAuiState(
22
+ (s) => s.unstable_interactables.definitions[id]?.state,
23
+ ) as TState | undefined;
29
24
 
30
- const persistenceStatus = useAuiState((s) => s.interactables.persistence[id]);
25
+ const persistenceStatus = useAuiState(
26
+ (s) => s.unstable_interactables.persistence[id],
27
+ );
31
28
 
32
29
  const setState = useCallback(
33
30
  (updater: StateUpdater<TState>) => {
34
- aui.interactables().setState(id, (prev) => {
31
+ aui.unstable_interactables().setState(id, (prev) => {
35
32
  if (typeof updater === "function") {
36
33
  return (updater as (prev: TState) => TState)(prev as TState);
37
34
  }
@@ -41,23 +38,34 @@ export const useInteractableState = <TState>(
41
38
  [aui, id],
42
39
  );
43
40
 
44
- const setSelected = useCallback(
45
- (selected: boolean) => {
46
- aui.interactables().setSelected(id, selected);
47
- },
48
- [aui, id],
49
- );
50
-
51
- const flush = useCallback(() => aui.interactables().flush(), [aui]);
41
+ const flush = useCallback(() => aui.unstable_interactables().flush(), [aui]);
52
42
 
53
43
  return [
54
44
  state,
55
45
  {
56
46
  setState,
57
- setSelected,
58
47
  isPending: persistenceStatus?.isPending ?? false,
59
48
  error: persistenceStatus?.error,
60
49
  flush,
61
50
  },
62
51
  ];
63
52
  };
53
+
54
+ /**
55
+ * Reads and writes the state of an interactable registered elsewhere, by id.
56
+ *
57
+ * Use this from secondary readers (children, siblings); the owning component
58
+ * registers with `unstable_useInteractable`, which returns state directly. Returns
59
+ * `undefined` until the owning interactable is registered.
60
+ *
61
+ * @deprecated Unstable / Experimental (not actually removed).
62
+ */
63
+ export const unstable_useInteractableState: <TState>(id: string) => [
64
+ TState | undefined,
65
+ {
66
+ setState: (updater: StateUpdater<TState>) => void;
67
+ isPending: boolean;
68
+ error: unknown;
69
+ flush: () => Promise<void>;
70
+ },
71
+ ] = useInteractableState;
@@ -0,0 +1,48 @@
1
+ import { useMemo } from "react";
2
+ import { useAuiState } from "@assistant-ui/store";
3
+ import {
4
+ unstable_getInteractableVersions as getInteractableVersions,
5
+ type Unstable_InteractableVersion,
6
+ } from "../../model-context/interactable-composer-metadata";
7
+ import { unstable_useInteractableState as useInteractableState } from "./useInteractableState";
8
+ import { useJSONEqual } from "../utils/useJSONEqual";
9
+
10
+ const useInteractableVersions = <TState = unknown>(
11
+ id: string,
12
+ name: string,
13
+ ): (Omit<Unstable_InteractableVersion, "state"> & {
14
+ state: TState;
15
+ restore: () => void;
16
+ })[] => {
17
+ const versions = useAuiState(
18
+ useJSONEqual((s) => getInteractableVersions(s.thread.messages, id, name)),
19
+ );
20
+ const [, { setState }] = useInteractableState<TState>(id);
21
+
22
+ return useMemo(
23
+ () =>
24
+ versions.map((v) => ({
25
+ ...v,
26
+ state: v.state as TState,
27
+ restore: () => setState(v.state as TState),
28
+ })),
29
+ [versions, setState],
30
+ );
31
+ };
32
+
33
+ /**
34
+ * Every version of a tool-created interactable recorded in the current
35
+ * thread, oldest first: the creating tool call, each user edit, and each
36
+ * `update_*` call. Each entry carries the full state as of that version and a
37
+ * `restore()` that sets the live instance back to it — enough for a version
38
+ * picker like an artifact's history dropdown.
39
+ *
40
+ * @deprecated Unstable / Experimental (not actually removed).
41
+ */
42
+ export const unstable_useInteractableVersions: <TState = unknown>(
43
+ id: string,
44
+ name: string,
45
+ ) => (Omit<Unstable_InteractableVersion, "state"> & {
46
+ state: TState;
47
+ restore: () => void;
48
+ })[] = useInteractableVersions;
@@ -0,0 +1,27 @@
1
+ import { useRef } from "react";
2
+ import { useAuiState } from "@assistant-ui/store";
3
+
4
+ const useThreadMessageIds = (): readonly string[] => {
5
+ const messages = useAuiState((s) => s.thread.messages);
6
+ const prevIdsRef = useRef<readonly string[]>([]);
7
+
8
+ const ids = messages.map((m) => m.id);
9
+ const prev = prevIdsRef.current;
10
+ if (prev.length !== ids.length || prev.some((id, i) => id !== ids[i])) {
11
+ prevIdsRef.current = ids;
12
+ }
13
+
14
+ return prevIdsRef.current;
15
+ };
16
+
17
+ /**
18
+ * Returns the ids of the messages in the current thread, in order.
19
+ *
20
+ * The returned array keeps a stable identity across content-only updates (e.g.
21
+ * streaming), changing reference only when the id sequence itself changes. Pair
22
+ * with `ThreadPrimitive.Unstable_MessageById` to drive a virtualized or custom
23
+ * message list.
24
+ *
25
+ * @deprecated Unstable / Experimental - may change in any release.
26
+ */
27
+ export const unstable_useThreadMessageIds = useThreadMessageIds;
@@ -0,0 +1,17 @@
1
+ import { expectTypeOf } from "vitest";
2
+ import { PartPrimitiveMessages } from "./PartMessages";
3
+
4
+ const checkPartMessagesProps = () => {
5
+ <PartPrimitiveMessages>{() => null}</PartPrimitiveMessages>;
6
+ <PartPrimitiveMessages components={{ Message: () => null }} />;
7
+
8
+ // @ts-expect-error neither components nor children is allowed
9
+ <PartPrimitiveMessages />;
10
+ // @ts-expect-error components must be defined, not undefined
11
+ <PartPrimitiveMessages components={undefined} />;
12
+ // @ts-expect-error components and children are mutually exclusive
13
+ <PartPrimitiveMessages components={{ Message: () => null }}>
14
+ {() => null}
15
+ </PartPrimitiveMessages>;
16
+ };
17
+ expectTypeOf(checkPartMessagesProps).toEqualTypeOf<() => void>();
@@ -8,11 +8,16 @@ import {
8
8
  } from "../thread/ThreadMessages";
9
9
 
10
10
  export namespace PartPrimitiveMessages {
11
- export type Props = {
12
- components?: ThreadPrimitiveMessages.Props["components"];
13
- /** Render function called for each message. Receives the message. */
14
- children?: (value: { message: ThreadMessage }) => ReactNode;
15
- };
11
+ export type Props =
12
+ | {
13
+ components: NonNullable<ThreadPrimitiveMessages.Props["components"]>;
14
+ children?: never;
15
+ }
16
+ | {
17
+ /** Render function called for each message. Receives the message. */
18
+ children: (value: { message: ThreadMessage }) => ReactNode;
19
+ components?: never;
20
+ };
16
21
  }
17
22
 
18
23
  const usePartMessages = (): readonly ThreadMessage[] | undefined => {
@@ -57,17 +62,17 @@ export const PartPrimitiveMessagesImpl: FC<PartPrimitiveMessages.Props> = ({
57
62
 
58
63
  if (!messages?.length) return null;
59
64
 
60
- if (children) {
65
+ if (components) {
61
66
  return (
62
67
  <ReadonlyThreadProvider messages={messages}>
63
- <ThreadPrimitiveMessagesImpl>{children}</ThreadPrimitiveMessagesImpl>
68
+ <ThreadPrimitiveMessagesImpl components={components} />
64
69
  </ReadonlyThreadProvider>
65
70
  );
66
71
  }
67
72
 
68
73
  return (
69
74
  <ReadonlyThreadProvider messages={messages}>
70
- <ThreadPrimitiveMessagesImpl components={components!} />
75
+ <ThreadPrimitiveMessagesImpl>{children}</ThreadPrimitiveMessagesImpl>
71
76
  </ReadonlyThreadProvider>
72
77
  );
73
78
  };