@assistant-ui/core 0.2.18 → 0.2.20

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 (231) 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.d.ts.map +1 -1
  82. package/dist/react/primitives/generativeUI/GenerativeUI.js +2 -2
  83. package/dist/react/primitives/generativeUI/GenerativeUI.js.map +1 -1
  84. package/dist/react/primitives/message/MessageAttachments.js +1 -1
  85. package/dist/react/primitives/message/MessageGroupedParts.js +1 -1
  86. package/dist/react/primitives/message/MessageParts.js +1 -1
  87. package/dist/react/primitives/message/MessageQuote.js +1 -1
  88. package/dist/react/primitives/part/PartMessages.d.ts +5 -2
  89. package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
  90. package/dist/react/primitives/part/PartMessages.js +8 -8
  91. package/dist/react/primitives/part/PartMessages.js.map +1 -1
  92. package/dist/react/primitives/thread/ThreadMessages.d.ts +31 -1
  93. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  94. package/dist/react/primitives/thread/ThreadMessages.js +64 -2
  95. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  96. package/dist/react/primitives/thread/ThreadSuggestions.js +1 -1
  97. package/dist/react/primitives/threadList/ThreadListItems.js +1 -1
  98. package/dist/react/providers/MessageByIdProvider.d.ts +9 -0
  99. package/dist/react/providers/MessageByIdProvider.d.ts.map +1 -0
  100. package/dist/react/providers/MessageByIdProvider.js +57 -0
  101. package/dist/react/providers/MessageByIdProvider.js.map +1 -0
  102. package/dist/react/providers/ReadonlyThreadProvider.js +1 -1
  103. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +1 -1
  104. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +3 -0
  105. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  106. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +16 -1
  107. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  108. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +1 -1
  109. package/dist/react/runtimes/cloud/auiV0.js +1 -1
  110. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +1 -1
  111. package/dist/react/runtimes/createRuntimeExtras.d.ts +20 -0
  112. package/dist/react/runtimes/createRuntimeExtras.d.ts.map +1 -0
  113. package/dist/react/runtimes/createRuntimeExtras.js +43 -0
  114. package/dist/react/runtimes/createRuntimeExtras.js.map +1 -0
  115. package/dist/react/runtimes/useLocalRuntime.js +1 -1
  116. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  117. package/dist/react/runtimes/useRemoteThreadListRuntime.js +6 -2
  118. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  119. package/dist/react/runtimes/useStreamingTiming.d.ts +26 -0
  120. package/dist/react/runtimes/useStreamingTiming.d.ts.map +1 -0
  121. package/dist/react/runtimes/useStreamingTiming.js +43 -0
  122. package/dist/react/runtimes/useStreamingTiming.js.map +1 -0
  123. package/dist/react/types/scopes/interactables.d.ts +40 -24
  124. package/dist/react/types/scopes/interactables.d.ts.map +1 -1
  125. package/dist/react/types/store-augmentation.d.ts +3 -1
  126. package/dist/react/types/store-augmentation.d.ts.map +1 -1
  127. package/dist/react/utils/useJSONEqual.d.ts +11 -0
  128. package/dist/react/utils/useJSONEqual.d.ts.map +1 -0
  129. package/dist/react/utils/useJSONEqual.js +30 -0
  130. package/dist/react/utils/useJSONEqual.js.map +1 -0
  131. package/dist/runtime/base/base-composer-runtime-core.d.ts +5 -0
  132. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  133. package/dist/runtime/base/base-composer-runtime-core.js +13 -0
  134. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  135. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  136. package/dist/runtime/base/default-edit-composer-runtime-core.js +6 -1
  137. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  138. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  139. package/dist/runtime/base/default-thread-composer-runtime-core.js +4 -1
  140. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  141. package/dist/runtime/utils/message-repository.d.ts +1 -0
  142. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  143. package/dist/runtime/utils/message-repository.js +6 -3
  144. package/dist/runtime/utils/message-repository.js.map +1 -1
  145. package/dist/runtime/utils/streaming-timing.d.ts +59 -0
  146. package/dist/runtime/utils/streaming-timing.d.ts.map +1 -0
  147. package/dist/runtime/utils/streaming-timing.js +76 -0
  148. package/dist/runtime/utils/streaming-timing.js.map +1 -0
  149. package/dist/runtime/utils/thread-message-like.js +2 -1
  150. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  151. package/dist/runtimes/external-store/external-store-adapter.d.ts +26 -1
  152. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  153. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +9 -0
  154. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  155. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +19 -0
  156. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  157. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  158. package/dist/runtimes/local/local-thread-runtime-core.js +1 -1
  159. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  160. package/dist/runtimes/remote-thread-list/types.d.ts +12 -0
  161. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  162. package/dist/store/clients/suggestions.js +1 -1
  163. package/dist/store/clients/thread-message-client.js +1 -1
  164. package/dist/store/runtime-clients/composer-runtime-client.js +1 -1
  165. package/dist/store/runtime-clients/message-runtime-client.js +1 -1
  166. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  167. package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
  168. package/dist/tests/remote-thread-list-test-helpers.d.ts +1 -1
  169. package/dist/tests/remote-thread-list-test-helpers.d.ts.map +1 -1
  170. package/dist/tests/remote-thread-list-test-helpers.js +3 -2
  171. package/dist/tests/remote-thread-list-test-helpers.js.map +1 -1
  172. package/package.json +9 -9
  173. package/src/index.ts +17 -0
  174. package/src/internal.ts +7 -0
  175. package/src/model-context/interactable-composer-metadata.test.ts +550 -0
  176. package/src/model-context/interactable-composer-metadata.ts +395 -0
  177. package/src/model-context/registry.ts +5 -0
  178. package/src/model-context/types.ts +12 -0
  179. package/src/react/client/Interactables.test.ts +358 -0
  180. package/src/react/client/Interactables.ts +339 -86
  181. package/src/react/client/interactable-model-context.test.ts +418 -0
  182. package/src/react/client/interactable-model-context.ts +252 -58
  183. package/src/react/index.ts +72 -3
  184. package/src/react/interactables-legacy/Interactables.ts +332 -0
  185. package/src/react/interactables-legacy/interactable-model-context.test.ts +54 -0
  186. package/src/react/interactables-legacy/interactable-model-context.ts +80 -0
  187. package/src/react/interactables-legacy/scopes.ts +66 -0
  188. package/src/react/{model-context → interactables-legacy}/useAssistantInteractable.ts +10 -1
  189. package/src/react/interactables-legacy/useInteractableState.ts +67 -0
  190. package/src/react/model-context/interactableTool.tsx +175 -0
  191. package/src/react/model-context/useInteractable.ts +233 -0
  192. package/src/react/model-context/useInteractableState.ts +31 -23
  193. package/src/react/model-context/useInteractableVersions.ts +48 -0
  194. package/src/react/primitive-hooks/useThreadMessageIds.ts +27 -0
  195. package/src/react/primitives/generativeUI/GenerativeUI.tsx +5 -1
  196. package/src/react/primitives/part/PartMessages.test.tsx +17 -0
  197. package/src/react/primitives/part/PartMessages.tsx +13 -8
  198. package/src/react/primitives/thread/ThreadMessages.tsx +67 -0
  199. package/src/react/providers/MessageByIdProvider.tsx +23 -0
  200. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +21 -1
  201. package/src/react/runtimes/createRuntimeExtras.test.ts +58 -0
  202. package/src/react/runtimes/createRuntimeExtras.ts +68 -0
  203. package/src/react/runtimes/createRuntimeExtras.use.test.ts +50 -0
  204. package/src/react/runtimes/useRemoteThreadListRuntime.ts +13 -1
  205. package/src/react/runtimes/useStreamingTiming.ts +68 -0
  206. package/src/react/types/scopes/interactables.ts +45 -23
  207. package/src/react/types/store-augmentation.ts +4 -2
  208. package/src/react/utils/useJSONEqual.ts +20 -0
  209. package/src/runtime/base/base-composer-runtime-core.ts +13 -0
  210. package/src/runtime/base/default-edit-composer-runtime-core.ts +18 -1
  211. package/src/runtime/base/default-thread-composer-runtime-core.ts +11 -1
  212. package/src/runtime/utils/message-repository.ts +11 -7
  213. package/src/runtime/utils/streaming-timing.test.ts +301 -0
  214. package/src/runtime/utils/streaming-timing.ts +161 -0
  215. package/src/runtime/utils/thread-message-like.ts +2 -1
  216. package/src/runtimes/external-store/external-store-adapter.ts +28 -0
  217. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +33 -1
  218. package/src/runtimes/local/local-thread-runtime-core.ts +4 -1
  219. package/src/runtimes/remote-thread-list/types.ts +13 -0
  220. package/src/tests/MessageRepository.test.ts +2 -0
  221. package/src/tests/composer-can-send.test.ts +1 -0
  222. package/src/tests/default-edit-composer-runtime-core.test.ts +78 -1
  223. package/src/tests/default-thread-composer-runtime-core.test.ts +82 -0
  224. package/src/tests/external-store-thread-runtime-core.test.ts +337 -0
  225. package/src/tests/groupParts.test.ts +32 -0
  226. package/src/tests/remote-thread-list-reactive-threadId.test.ts +90 -0
  227. package/src/tests/remote-thread-list-test-helpers.ts +2 -1
  228. package/src/tests/thread-message-like.test.ts +50 -0
  229. package/dist/react/model-context/useAssistantInteractable.d.ts +0 -22
  230. package/dist/react/model-context/useAssistantInteractable.d.ts.map +0 -1
  231. package/dist/react/model-context/useAssistantInteractable.js.map +0 -1
@@ -1,83 +1,277 @@
1
1
  import type { Tool } from "assistant-stream";
2
- import type {
3
- InteractableDefinition,
4
- InteractableStateSchema,
5
- } from "../types/scopes/interactables";
6
-
7
- export function shallowMerge(prev: unknown, partial: unknown): unknown {
8
- if (
9
- typeof prev !== "object" ||
10
- prev === null ||
11
- typeof partial !== "object" ||
12
- partial === null ||
13
- Array.isArray(prev) ||
14
- Array.isArray(partial)
15
- ) {
16
- return partial;
2
+ import { toJSONSchema } from "assistant-stream";
3
+ import type { Unstable_InteractableDefinition } from "../types/scopes/interactables";
4
+ import {
5
+ interactableToolName,
6
+ shallowMergeInteractableState,
7
+ type Unstable_InteractableSnapshotEntry,
8
+ } from "../../model-context/interactable-composer-metadata";
9
+ import { generateId } from "../../utils/id";
10
+ import { isRecord } from "../../utils/json/is-json";
11
+
12
+ export type PartialJSONSchema = ReturnType<typeof toJSONSchema>;
13
+
14
+ const ID_PROPERTY = {
15
+ type: "string" as const,
16
+ description:
17
+ "The id of the instance to update, as shown in its state snapshot in the conversation.",
18
+ };
19
+
20
+ const hasIdProperty = (schema: Record<string, unknown>) => {
21
+ const properties = schema.properties;
22
+ return isRecord(properties) && properties.id !== undefined;
23
+ };
24
+
25
+ const withRequiredItemId = (schema: Record<string, unknown>) => ({
26
+ ...schema,
27
+ required: ["id"],
28
+ });
29
+
30
+ // The framework assigns ids to new items, so the model never sees the `id`
31
+ // field when adding (it can't address an item that doesn't exist yet).
32
+ const withoutItemId = (schema: Record<string, unknown>) => {
33
+ const { id: _omitted, ...properties } = isRecord(schema.properties)
34
+ ? schema.properties
35
+ : {};
36
+ const required = Array.isArray(schema.required)
37
+ ? schema.required.filter((key) => key !== "id")
38
+ : schema.required;
39
+ return { ...schema, properties, required };
40
+ };
41
+
42
+ const toArrayUpdateSchema = (schema: unknown, field: string) => {
43
+ if (!isRecord(schema) || schema.type !== "array") return schema;
44
+ const itemSchema = schema.items;
45
+ if (Array.isArray(itemSchema) || !isRecord(itemSchema)) return schema;
46
+
47
+ const idKeyed = hasIdProperty(itemSchema);
48
+ const properties: Record<string, unknown> = {
49
+ add: {
50
+ type: "array",
51
+ items: idKeyed ? withoutItemId(itemSchema) : itemSchema,
52
+ },
53
+ remove: {
54
+ type: "array",
55
+ items: idKeyed ? ID_PROPERTY : itemSchema,
56
+ },
57
+ clear: { type: "boolean" },
58
+ };
59
+
60
+ if (idKeyed) {
61
+ properties.update = {
62
+ type: "array",
63
+ items: withRequiredItemId(itemSchema),
64
+ };
65
+ }
66
+
67
+ return {
68
+ type: "object" as const,
69
+ description:
70
+ `Operations for array field "${field}". To change one item use update ` +
71
+ `with its id; add new items (their ids are assigned for you), remove items ` +
72
+ `by id, or clear to empty. Change only the items you mean to — never resend ` +
73
+ `the whole array to edit one item.`,
74
+ properties,
75
+ additionalProperties: false,
76
+ };
77
+ };
78
+
79
+ // Top-level array fields whose items carry an `id` — the fields the framework
80
+ // mints ids for on add.
81
+ const idKeyedArrayFieldNames = (
82
+ properties: Record<string, unknown>,
83
+ ): Set<string> => {
84
+ const names = new Set<string>();
85
+ for (const [key, value] of Object.entries(properties)) {
86
+ if (
87
+ isRecord(value) &&
88
+ value.type === "array" &&
89
+ isRecord(value.items) &&
90
+ hasIdProperty(value.items)
91
+ ) {
92
+ names.add(key);
93
+ }
94
+ }
95
+ return names;
96
+ };
97
+
98
+ const withArrayUpdateSchemas = (properties: Record<string, unknown>) =>
99
+ Object.fromEntries(
100
+ Object.entries(properties).map(([key, value]) => [
101
+ key,
102
+ toArrayUpdateSchema(value, key),
103
+ ]),
104
+ );
105
+
106
+ /**
107
+ * Wraps an interactable's partial state schema with the required `id`
108
+ * parameter. Falls back to a permissive schema when the partial conversion
109
+ * failed at registration time.
110
+ */
111
+ function withRequiredId(partial: PartialJSONSchema | undefined) {
112
+ if (!partial || typeof partial !== "object" || partial.type !== "object") {
113
+ return {
114
+ type: "object" as const,
115
+ properties: { id: ID_PROPERTY },
116
+ required: ["id"],
117
+ additionalProperties: true,
118
+ };
17
119
  }
120
+ if (process.env.NODE_ENV !== "production" && partial.properties?.id) {
121
+ console.warn(
122
+ `[Interactables] a top-level "id" field in an interactable's stateSchema is ` +
123
+ `reserved for instance addressing by the update tool and cannot be updated ` +
124
+ `by the model. Rename the field to make it model-writable.`,
125
+ );
126
+ }
127
+ const { id: _reserved, ...properties } = partial.properties ?? {};
18
128
  return {
19
- ...(prev as Record<string, unknown>),
20
- ...(partial as Record<string, unknown>),
129
+ ...partial,
130
+ properties: { id: ID_PROPERTY, ...withArrayUpdateSchemas(properties) },
131
+ required: ["id"],
21
132
  };
22
133
  }
23
134
 
24
135
  export function buildInteractableModelContext(
25
- definitions: Record<string, InteractableDefinition>,
26
- partialSchemaCache: Map<string, InteractableStateSchema>,
136
+ definitions: Record<string, Unstable_InteractableDefinition>,
137
+ partialSchemaCache: Map<string, PartialJSONSchema>,
27
138
  setDefState: (id: string, updater: (prev: unknown) => unknown) => void,
28
- ): { system: string; tools: Record<string, Tool<any, any>> } | undefined {
139
+ streamBaselines = new Map<string, { targetId: string; state: unknown }>(),
140
+ ):
141
+ | {
142
+ tools: Record<string, Tool<any, any>>;
143
+ unstable_composerMetadata?: Record<string, unknown>;
144
+ }
145
+ | undefined {
29
146
  const entries = Object.values(definitions);
30
147
  if (entries.length === 0) return undefined;
31
148
 
32
- const byName = new Map<string, InteractableDefinition[]>();
149
+ const byName = new Map<string, Unstable_InteractableDefinition[]>();
33
150
  for (const def of entries) {
34
151
  const list = byName.get(def.name) ?? [];
35
152
  list.push(def);
36
153
  byName.set(def.name, list);
37
154
  }
38
155
 
39
- const systemParts: string[] = [];
40
156
  const tools: Record<string, Tool<any, any>> = {};
41
157
 
42
158
  for (const [name, instances] of byName) {
43
- const isMulti = instances.length > 1;
44
-
45
- for (const def of instances) {
46
- const selectedTag = def.selected ? " (SELECTED)" : "";
47
- const idTag = isMulti ? ` [id="${def.id}"]` : "";
48
-
49
- systemParts.push(
50
- `Interactable component "${name}"${idTag}${selectedTag} (${def.description}). Current state: ${JSON.stringify(def.state)}`,
51
- );
52
-
53
- const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "_");
54
- const safeId = def.id.replace(/[^a-zA-Z0-9_-]/g, "_");
55
- const toolName = isMulti
56
- ? `update_${safeName}_${safeId}`
57
- : `update_${safeName}`;
58
-
59
- const partialSchema = partialSchemaCache.get(def.id) ?? def.stateSchema;
60
-
61
- tools[toolName] = {
62
- type: "frontend" as const,
63
- description: `Update the state of interactable component "${name}"${isMulti ? ` (id: ${def.id})` : ""}. Only include the fields you want to change; omitted fields keep their current values. ${def.description}`,
64
- parameters: partialSchema,
65
- streamCall: async (reader) => {
66
- try {
67
- for await (const partialArgs of reader.args.streamValues()) {
68
- setDefState(def.id, (prev) => shallowMerge(prev, partialArgs));
159
+ const toolName = interactableToolName(name);
160
+ if (tools[toolName]) {
161
+ if (process.env.NODE_ENV !== "production") {
162
+ console.warn(
163
+ `[Interactables] interactable names "${name}" and another registered name ` +
164
+ `both sanitize to the tool name "${toolName}". Rename one of them.`,
165
+ );
166
+ }
167
+ continue;
168
+ }
169
+
170
+ const first = instances[0]!;
171
+ const partialSchema = partialSchemaCache.get(first.id);
172
+ const idKeyedFields =
173
+ partialSchema && isRecord(partialSchema.properties)
174
+ ? idKeyedArrayFieldNames(partialSchema.properties)
175
+ : new Set<string>();
176
+
177
+ // `id` resolves to a definition of this name; an id-less call is accepted
178
+ // only while exactly one instance exists.
179
+ const resolveTarget = (
180
+ id: unknown,
181
+ ): Unstable_InteractableDefinition | undefined => {
182
+ if (typeof id === "string") {
183
+ const def = definitions[id];
184
+ return def?.name === name ? def : undefined;
185
+ }
186
+ return instances.length === 1 ? first : undefined;
187
+ };
188
+
189
+ tools[toolName] = {
190
+ type: "frontend",
191
+ description:
192
+ `Update the state of interactable component "${name}". ${first.description} ` +
193
+ `Pass the id of the instance to update — instance ids and current state ` +
194
+ `appear in the conversation as state snapshots. Only include the fields ` +
195
+ `you want to change; omitted fields keep their current values.`,
196
+ parameters: withRequiredId(partialSchema),
197
+ streamCall: async (reader, { toolCallId }) => {
198
+ try {
199
+ for await (const partialArgs of reader.args.streamValues()) {
200
+ if (!partialArgs || typeof partialArgs !== "object") continue;
201
+ const args = partialArgs as Record<string, unknown>;
202
+ const keys = Object.keys(args);
203
+ const idIndex = keys.indexOf("id");
204
+ if (idIndex === keys.length - 1) continue;
205
+
206
+ const { id, ...partial } = args;
207
+ if (Object.keys(partial).length === 0) continue;
208
+ const target = resolveTarget(id);
209
+ if (!target) continue;
210
+
211
+ const baseline = streamBaselines.get(toolCallId);
212
+ const arrayBaseline =
213
+ baseline?.targetId === target.id ? baseline.state : target.state;
214
+ if (!baseline || baseline.targetId !== target.id) {
215
+ streamBaselines.set(toolCallId, {
216
+ targetId: target.id,
217
+ state: target.state,
218
+ });
69
219
  }
70
- } catch {
71
- // Non-fatal: execute handles the final state
220
+
221
+ setDefState(target.id, (prev) =>
222
+ shallowMergeInteractableState(prev, partial, { arrayBaseline }),
223
+ );
72
224
  }
73
- },
74
- execute: async (partialState: unknown) => {
75
- setDefState(def.id, (prev) => shallowMerge(prev, partialState));
76
- return { success: true };
77
- },
78
- };
79
- }
225
+ } catch {
226
+ // Non-fatal: execute handles the final state
227
+ }
228
+ },
229
+ execute: async (args: unknown, { toolCallId }) => {
230
+ const { id, ...partial } = (args ?? {}) as Record<string, unknown>;
231
+ const target = resolveTarget(id);
232
+ if (!target) {
233
+ const validIds = instances.map((d) => d.id);
234
+ return {
235
+ success: false,
236
+ error: `Unknown id ${JSON.stringify(id)} for interactable "${name}". Valid ids: ${validIds.join(", ")}`,
237
+ };
238
+ }
239
+ const baseline = streamBaselines.get(toolCallId);
240
+ streamBaselines.delete(toolCallId);
241
+ const addedItemIds: Record<string, string[]> = {};
242
+ setDefState(target.id, (prev) =>
243
+ shallowMergeInteractableState(prev, partial, {
244
+ arrayBaseline:
245
+ baseline?.targetId === target.id ? baseline.state : undefined,
246
+ idFactory: (field) => {
247
+ const itemId = generateId();
248
+ (addedItemIds[field] ??= []).push(itemId);
249
+ return itemId;
250
+ },
251
+ idKeyedFields,
252
+ }),
253
+ );
254
+ // The resolved id lets an id-less call's UI (and the model) address
255
+ // the instance that was actually updated.
256
+ const result: {
257
+ success: true;
258
+ id: string;
259
+ addedItemIds?: Record<string, string[]>;
260
+ } = { success: true, id: target.id };
261
+ if (Object.keys(addedItemIds).length > 0) {
262
+ result.addedItemIds = addedItemIds;
263
+ }
264
+ return result;
265
+ },
266
+ };
80
267
  }
81
268
 
82
- return { system: systemParts.join("\n"), tools };
269
+ const interactables: Unstable_InteractableSnapshotEntry[] = entries.map(
270
+ (def) => ({
271
+ id: def.id,
272
+ name: def.name,
273
+ state: def.state,
274
+ }),
275
+ );
276
+ return { tools, unstable_composerMetadata: { interactables } };
83
277
  }
@@ -52,11 +52,42 @@ export {
52
52
  defineMcpToolkit,
53
53
  type McpToolkitDefinition,
54
54
  } from "./model-context/define-mcp-toolkit";
55
+ /**
56
+ * @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API.
57
+ * Scheduled for removal on/after 2026-09-14. See
58
+ * {@link https://www.assistant-ui.com/docs/tools/interactables#migrating-from-the-previous-api | Interactables migration guide}.
59
+ */
55
60
  export {
56
61
  useAssistantInteractable,
57
62
  type AssistantInteractableProps,
58
- } from "./model-context/useAssistantInteractable";
59
- export { useInteractableState } from "./model-context/useInteractableState";
63
+ } from "./interactables-legacy/useAssistantInteractable";
64
+ /**
65
+ * @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API.
66
+ * Scheduled for removal on/after 2026-09-14. See
67
+ * {@link https://www.assistant-ui.com/docs/tools/interactables#migrating-from-the-previous-api | Interactables migration guide}.
68
+ */
69
+ export { useInteractableState } from "./interactables-legacy/useInteractableState";
70
+ export {
71
+ /** @deprecated Unstable / Experimental — may change in any release. */
72
+ unstable_useInteractable,
73
+ type Unstable_InteractableConfig,
74
+ type Unstable_InferInteractableState,
75
+ type Unstable_InteractableVersionInfo,
76
+ } from "./model-context/useInteractable";
77
+ export {
78
+ /** @deprecated Unstable / Experimental — may change in any release. */
79
+ unstable_useInteractableState,
80
+ } from "./model-context/useInteractableState";
81
+ export {
82
+ /** @deprecated Unstable / Experimental — may change in any release. */
83
+ unstable_useInteractableVersions,
84
+ } from "./model-context/useInteractableVersions";
85
+ export {
86
+ /** @deprecated Unstable / Experimental — may change in any release. */
87
+ unstable_interactableTool,
88
+ type Unstable_InteractableToolConfig,
89
+ type Unstable_InteractableToolRenderProps,
90
+ } from "./model-context/interactableTool";
60
91
  export {
61
92
  useToolArgsStatus,
62
93
  type ToolArgsStatus,
@@ -65,7 +96,16 @@ export {
65
96
  // client
66
97
  export { Tools, type McpAppResourceOutput } from "./client/Tools";
67
98
  export { DataRenderers } from "./client/DataRenderers";
68
- export { Interactables } from "./client/Interactables";
99
+ /**
100
+ * @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API.
101
+ * Scheduled for removal on/after 2026-09-14. See
102
+ * {@link https://www.assistant-ui.com/docs/tools/interactables#migrating-from-the-previous-api | Interactables migration guide}.
103
+ */
104
+ export { Interactables } from "./interactables-legacy/Interactables";
105
+ export {
106
+ /** @deprecated Unstable / Experimental — may change in any release. */
107
+ unstable_Interactables,
108
+ } from "./client/Interactables";
69
109
 
70
110
  // types
71
111
  export type {
@@ -107,15 +147,36 @@ export type {
107
147
  DataRenderersClientSchema,
108
148
  } from "./types/scopes/dataRenderers";
109
149
  export type {
150
+ /** @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API. */
110
151
  InteractableStateSchema,
152
+ /** @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API. */
111
153
  InteractablesState,
154
+ /** @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API. */
112
155
  InteractableDefinition,
156
+ /** @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API. */
113
157
  InteractableRegistration,
158
+ /** @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API. */
114
159
  InteractablesMethods,
160
+ /** @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API. */
115
161
  InteractablePersistedState,
162
+ /** @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API. */
116
163
  InteractablePersistenceAdapter,
164
+ /** @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API. */
117
165
  InteractablePersistenceStatus,
166
+ /** @deprecated Since 2026-06-14 — migrate to the Unstable / Experimental API. */
118
167
  InteractablesClientSchema,
168
+ } from "./interactables-legacy/scopes";
169
+ export type {
170
+ Unstable_InteractableStateSchema,
171
+ Unstable_InteractablesState,
172
+ Unstable_InteractableDefinition,
173
+ Unstable_InteractableRegistration,
174
+ Unstable_InteractablesMethods,
175
+ Unstable_InteractablePersistedState,
176
+ Unstable_InteractablePersistenceAdapter,
177
+ Unstable_InteractablePersistenceStatus,
178
+ Unstable_InteractablesClientSchema,
179
+ Unstable_InteractablesConfig,
119
180
  } from "./types/scopes/interactables";
120
181
 
121
182
  // providers
@@ -157,6 +218,12 @@ export {
157
218
  } from "./runtimes/external-message-converter";
158
219
  export type { JoinStrategy } from "./runtimes/external-message-converter";
159
220
  export { createMessageConverter } from "./runtimes/createMessageConverter";
221
+ export {
222
+ useStreamingTiming,
223
+ type StreamingTimingAccessors,
224
+ type StreamingTimingOptions,
225
+ type StreamingTimingState,
226
+ } from "./runtimes/useStreamingTiming";
160
227
  export { RemoteThreadListHookInstanceManager } from "./runtimes/RemoteThreadListHookInstanceManager";
161
228
  export { RemoteThreadListThreadListRuntimeCore } from "./runtimes/RemoteThreadListThreadListRuntimeCore";
162
229
  export { useRemoteThreadListRuntime } from "./runtimes/useRemoteThreadListRuntime";
@@ -184,6 +251,7 @@ export {
184
251
  ThreadPrimitiveMessages,
185
252
  ThreadPrimitiveMessagesImpl,
186
253
  ThreadPrimitiveMessageByIndex,
254
+ ThreadPrimitiveUnstable_MessageById,
187
255
  } from "./primitives/thread/ThreadMessages";
188
256
  export {
189
257
  MessagePrimitiveParts,
@@ -234,6 +302,7 @@ export { getMessageQuote } from "./utils/getMessageQuote";
234
302
 
235
303
  // Primitive hooks (shared behavior logic)
236
304
  export { useThreadMessages } from "./primitive-hooks/useThreadMessages";
305
+ export { unstable_useThreadMessageIds } from "./primitive-hooks/useThreadMessageIds";
237
306
  export { useThreadIsRunning } from "./primitive-hooks/useThreadIsRunning";
238
307
  export { useThreadIsEmpty } from "./primitive-hooks/useThreadIsEmpty";
239
308
  export { useComposerSend } from "./primitive-hooks/useComposerSend";