@assistant-ui/core 0.3.1 → 0.3.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.
Files changed (183) hide show
  1. package/dist/index.d.ts +4 -4
  2. package/dist/index.js.map +1 -1
  3. package/dist/internal.d.ts +6 -7
  4. package/dist/internal.js +5 -6
  5. package/dist/react/AssistantProvider.d.ts +1 -1
  6. package/dist/react/AssistantProvider.d.ts.map +1 -1
  7. package/dist/react/AssistantProvider.js +35 -9
  8. package/dist/react/AssistantProvider.js.map +1 -1
  9. package/dist/react/AssistantRuntimeProvider.d.ts +1 -1
  10. package/dist/react/AssistantRuntimeProvider.js +1 -1
  11. package/dist/react/RuntimeAdapter.js +2 -2
  12. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  13. package/dist/react/adapters/LocalStorageThreadListAdapter.js +17 -33
  14. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  15. package/dist/react/client/DataRenderers.js +1 -1
  16. package/dist/react/client/Interactables.js +1 -1
  17. package/dist/react/client/Tools.js +2 -2
  18. package/dist/react/index.d.ts +2 -1
  19. package/dist/react/index.js +2 -1
  20. package/dist/react/interactables-legacy/Interactables.js +1 -1
  21. package/dist/react/interactables-legacy/useAssistantInteractable.js +1 -1
  22. package/dist/react/interactables-legacy/useInteractableState.js +2 -2
  23. package/dist/react/model-context/useAssistantContext.js +1 -1
  24. package/dist/react/model-context/useAssistantDataUI.js +2 -2
  25. package/dist/react/model-context/useAssistantInstructions.js +2 -2
  26. package/dist/react/model-context/useAssistantTool.js +2 -2
  27. package/dist/react/model-context/useAssistantToolUI.js +2 -2
  28. package/dist/react/model-context/useAuiToolOverrides.js +1 -1
  29. package/dist/react/model-context/useInlineRender.js +1 -1
  30. package/dist/react/model-context/useInteractable.js +1 -1
  31. package/dist/react/model-context/useInteractableState.js +2 -2
  32. package/dist/react/model-context/useInteractableVersions.js +2 -2
  33. package/dist/react/model-context/useToolArgsStatus.js +2 -2
  34. package/dist/react/primitive-hooks/useActionBarCopy.js +52 -25
  35. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  36. package/dist/react/primitive-hooks/useActionBarEdit.js +2 -2
  37. package/dist/react/primitive-hooks/useActionBarFeedback.js +2 -2
  38. package/dist/react/primitive-hooks/useActionBarReload.js +2 -2
  39. package/dist/react/primitive-hooks/useActionBarSpeak.js +2 -2
  40. package/dist/react/primitive-hooks/useActionBarStopSpeaking.js +2 -2
  41. package/dist/react/primitive-hooks/useBranchPickerNext.js +2 -2
  42. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +2 -2
  43. package/dist/react/primitive-hooks/useComposerAddAttachment.js +2 -2
  44. package/dist/react/primitive-hooks/useComposerCancel.js +2 -2
  45. package/dist/react/primitive-hooks/useComposerDictate.js +2 -2
  46. package/dist/react/primitive-hooks/useComposerSend.js +2 -2
  47. package/dist/react/primitive-hooks/useEditComposerCancel.js +2 -2
  48. package/dist/react/primitive-hooks/useEditComposerSend.js +2 -2
  49. package/dist/react/primitive-hooks/useMessageBranching.js +2 -2
  50. package/dist/react/primitive-hooks/useMessageReload.js +2 -2
  51. package/dist/react/primitive-hooks/useSuggestionTrigger.js +2 -2
  52. package/dist/react/primitive-hooks/useThreadListItemArchive.js +2 -2
  53. package/dist/react/primitive-hooks/useThreadListItemDelete.js +2 -2
  54. package/dist/react/primitive-hooks/useThreadListItemTrigger.js +2 -2
  55. package/dist/react/primitive-hooks/useThreadListItemUnarchive.js +2 -2
  56. package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -2
  57. package/dist/react/primitive-hooks/useThreadListNew.js +2 -2
  58. package/dist/react/primitive-hooks/useThreadMessageIds.js +1 -1
  59. package/dist/react/primitive-hooks/useVoice.js +2 -2
  60. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +2 -2
  61. package/dist/react/primitives/composer/ComposerAttachments.js +2 -2
  62. package/dist/react/primitives/composer/ComposerIf.js +1 -1
  63. package/dist/react/primitives/composer/ComposerQueue.js +1 -1
  64. package/dist/react/primitives/generativeUI/GenerativeUI.js +2 -2
  65. package/dist/react/primitives/message/MessageAttachments.js +2 -2
  66. package/dist/react/primitives/message/MessageGroupedParts.d.ts +2 -1
  67. package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
  68. package/dist/react/primitives/message/MessageGroupedParts.js +3 -3
  69. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  70. package/dist/react/primitives/message/MessageParts.js +2 -2
  71. package/dist/react/primitives/message/MessageQuote.js +2 -2
  72. package/dist/react/primitives/part/PartMessages.js +2 -2
  73. package/dist/react/primitives/thread/ThreadMessages.js +2 -2
  74. package/dist/react/primitives/thread/ThreadSuggestions.js +2 -2
  75. package/dist/react/primitives/threadList/ThreadListItems.js +2 -2
  76. package/dist/react/primitives/threadListItem/ThreadListItemTitle.js +1 -1
  77. package/dist/react/providers/AttachmentByIndexProvider.js +1 -1
  78. package/dist/react/providers/ChainOfThoughtByIndicesProvider.js +1 -1
  79. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +1 -1
  80. package/dist/react/providers/MessageByIdProvider.js +1 -1
  81. package/dist/react/providers/MessageByIndexProvider.js +1 -1
  82. package/dist/react/providers/PartByIndexProvider.js +1 -1
  83. package/dist/react/providers/QueueItemByIndexProvider.js +1 -1
  84. package/dist/react/providers/ReadonlyThreadProvider.js +2 -2
  85. package/dist/react/providers/SuggestionByIndexProvider.js +1 -1
  86. package/dist/react/providers/TextMessagePartProvider.js +2 -2
  87. package/dist/react/providers/ThreadListItemByIndexProvider.js +1 -1
  88. package/dist/react/providers/ThreadListItemRuntimeProvider.js +1 -1
  89. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  90. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +14 -13
  91. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  92. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +3 -0
  93. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  94. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +22 -12
  95. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  96. package/dist/react/runtimes/RuntimeAdapterProvider.js +1 -1
  97. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +9 -23
  98. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  99. package/dist/react/runtimes/cloud/auiV0.d.ts +24 -2
  100. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  101. package/dist/react/runtimes/cloud/auiV0.js +24 -5
  102. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  103. package/dist/react/runtimes/external-message-converter.js +1 -1
  104. package/dist/react/runtimes/useExternalStoreRuntime.js +1 -1
  105. package/dist/react/runtimes/useExternalStoreSharedOptions.js +1 -1
  106. package/dist/react/runtimes/useLocalRuntime.js +1 -1
  107. package/dist/react/runtimes/useRemoteThreadListRuntime.js +7 -13
  108. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  109. package/dist/react/utils/useJSONEqual.js +1 -1
  110. package/dist/runtime/api/message-runtime.d.ts.map +1 -1
  111. package/dist/runtime/api/message-runtime.js +5 -1
  112. package/dist/runtime/api/message-runtime.js.map +1 -1
  113. package/dist/runtime/internal.d.ts +4 -4
  114. package/dist/runtime/internal.js +4 -4
  115. package/dist/runtime/utils/thread-message-like.js +2 -2
  116. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  117. package/dist/runtimes/remote-thread-list/types.d.ts +5 -4
  118. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  119. package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
  120. package/dist/store/clients/chain-of-thought-client.js +30 -24
  121. package/dist/store/clients/chain-of-thought-client.js.map +1 -1
  122. package/dist/store/clients/model-context-client.js +1 -1
  123. package/dist/store/clients/no-op-composer-client.js +1 -1
  124. package/dist/store/clients/suggestions.js +2 -2
  125. package/dist/store/clients/thread-message-client.d.ts.map +1 -1
  126. package/dist/store/clients/thread-message-client.js +103 -91
  127. package/dist/store/clients/thread-message-client.js.map +1 -1
  128. package/dist/store/runtime-clients/composer-runtime-client.js +2 -2
  129. package/dist/store/runtime-clients/message-runtime-client.js +2 -2
  130. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +2 -2
  131. package/dist/store/runtime-clients/thread-list-runtime-client.js +2 -2
  132. package/dist/store/runtime-clients/thread-runtime-client.js +2 -2
  133. package/dist/store/scopes/thread.d.ts +1 -1
  134. package/dist/store/scopes/thread.d.ts.map +1 -1
  135. package/dist/types/index.d.ts +2 -2
  136. package/dist/types/message.d.ts +11 -1
  137. package/dist/types/message.d.ts.map +1 -1
  138. package/dist/types/message.js.map +1 -1
  139. package/dist/utils/data-url.js +2 -2
  140. package/dist/utils/data-url.js.map +1 -1
  141. package/dist/utils/getGroupStatus.d.ts +9 -0
  142. package/dist/utils/getGroupStatus.d.ts.map +1 -0
  143. package/dist/utils/getGroupStatus.js +15 -0
  144. package/dist/utils/getGroupStatus.js.map +1 -0
  145. package/dist/utils/normalizePartStatus.d.ts +8 -0
  146. package/dist/utils/normalizePartStatus.d.ts.map +1 -0
  147. package/dist/utils/normalizePartStatus.js +39 -0
  148. package/dist/utils/normalizePartStatus.js.map +1 -0
  149. package/package.json +4 -4
  150. package/src/index.ts +1 -0
  151. package/src/internal.ts +1 -7
  152. package/src/react/AssistantProvider.tsx +28 -15
  153. package/src/react/AssistantRuntimeProvider.test.tsx +101 -0
  154. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +28 -7
  155. package/src/react/index.ts +4 -0
  156. package/src/react/primitive-hooks/useActionBarCopy.test.ts +109 -14
  157. package/src/react/primitive-hooks/useActionBarCopy.ts +27 -2
  158. package/src/react/primitives/message/MessageGroupedParts.tsx +4 -4
  159. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +16 -13
  160. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +53 -10
  161. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +7 -5
  162. package/src/react/runtimes/cloud/auiV0.ts +51 -12
  163. package/src/react/runtimes/useRemoteThreadListRuntime.ts +9 -15
  164. package/src/runtime/api/message-runtime.test.ts +140 -0
  165. package/src/runtime/api/message-runtime.ts +10 -5
  166. package/src/runtime/utils/thread-message-like.ts +2 -2
  167. package/src/runtimes/remote-thread-list/types.ts +5 -4
  168. package/src/store/clients/chain-of-thought-client.ts +2 -7
  169. package/src/store/clients/thread-message-client.test.ts +84 -0
  170. package/src/store/clients/thread-message-client.ts +14 -4
  171. package/src/store/scopes/thread.ts +1 -1
  172. package/src/tests/auiV0Encode.test.ts +93 -0
  173. package/src/tests/remote-thread-list-reactive-threadId.test.ts +0 -92
  174. package/src/tests/thread-message-like.test.ts +15 -0
  175. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +293 -0
  176. package/src/types/index.ts +1 -0
  177. package/src/types/message.ts +19 -0
  178. package/src/utils/data-url.test.ts +26 -2
  179. package/src/utils/data-url.ts +2 -2
  180. package/src/utils/getGroupStatus.test.ts +36 -0
  181. package/src/utils/getGroupStatus.ts +31 -0
  182. package/src/utils/normalizePartStatus.test.ts +59 -0
  183. package/src/utils/normalizePartStatus.ts +55 -0
@@ -0,0 +1,9 @@
1
+ import { MessagePartStatus, ToolCallMessagePartStatus } from "../types/message.js";
2
+ //#region src/utils/getGroupStatus.d.ts
3
+ type PartWithStatus = {
4
+ readonly status: MessagePartStatus | ToolCallMessagePartStatus;
5
+ };
6
+ declare const getGroupStatus: (parts: readonly (PartWithStatus | undefined)[], indices?: readonly number[]) => MessagePartStatus | ToolCallMessagePartStatus;
7
+ //#endregion
8
+ export { getGroupStatus };
9
+ //# sourceMappingURL=getGroupStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getGroupStatus.d.ts","names":[],"sources":["../../src/utils/getGroupStatus.ts"],"mappings":";;KAMK;WACM,QAAQ,oBAAoB;;cAG1B,iBACX,iBAAiB,+BACjB,gCACC,oBAAoB"}
@@ -0,0 +1,15 @@
1
+ import { COMPLETE_STATUS, RUNNING_STATUS } from "./normalizePartStatus.js";
2
+ //#region src/utils/getGroupStatus.ts
3
+ const getGroupStatus = (parts, indices) => {
4
+ if (indices) {
5
+ for (const index of indices) if (parts[index]?.status.type === "running") return RUNNING_STATUS;
6
+ const lastIndex = indices.at(-1);
7
+ return lastIndex === void 0 ? COMPLETE_STATUS : parts[lastIndex]?.status ?? COMPLETE_STATUS;
8
+ }
9
+ for (const part of parts) if (part?.status.type === "running") return RUNNING_STATUS;
10
+ return parts.at(-1)?.status ?? COMPLETE_STATUS;
11
+ };
12
+ //#endregion
13
+ export { getGroupStatus };
14
+
15
+ //# sourceMappingURL=getGroupStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getGroupStatus.js","names":["MessagePartStatus","ToolCallMessagePartStatus","COMPLETE_STATUS","RUNNING_STATUS","PartWithStatus","status","getGroupStatus","parts","indices","index","type","lastIndex","at","undefined","part"],"sources":["../../src/utils/getGroupStatus.ts"],"sourcesContent":["import type {\n MessagePartStatus,\n ToolCallMessagePartStatus,\n} from \"../types/message\";\nimport { COMPLETE_STATUS, RUNNING_STATUS } from \"./normalizePartStatus\";\n\ntype PartWithStatus = {\n readonly status: MessagePartStatus | ToolCallMessagePartStatus;\n};\n\nexport const getGroupStatus = (\n parts: readonly (PartWithStatus | undefined)[],\n indices?: readonly number[],\n): MessagePartStatus | ToolCallMessagePartStatus => {\n if (indices) {\n for (const index of indices) {\n if (parts[index]?.status.type === \"running\") return RUNNING_STATUS;\n }\n\n const lastIndex = indices.at(-1);\n return lastIndex === undefined\n ? COMPLETE_STATUS\n : (parts[lastIndex]?.status ?? COMPLETE_STATUS);\n }\n\n for (const part of parts) {\n if (part?.status.type === \"running\") return RUNNING_STATUS;\n }\n\n return parts.at(-1)?.status ?? COMPLETE_STATUS;\n};\n"],"mappings":";;AAUA,MAAaM,kBACXC,OACAC,YACkD;CAClD,IAAIA,SAAS;EACX,KAAK,MAAMC,SAASD,SAClB,IAAID,MAAME,MAAM,EAAEJ,OAAOK,SAAS,WAAW,OAAOP;EAGtD,MAAMQ,YAAYH,QAAQI,GAAG,EAAE;EAC/B,OAAOD,cAAcE,KAAAA,IACjBX,kBACCK,MAAMI,UAAU,EAAEN,UAAUH;CACnC;CAEA,KAAK,MAAMY,QAAQP,OACjB,IAAIO,MAAMT,OAAOK,SAAS,WAAW,OAAOP;CAG9C,OAAOI,MAAMK,GAAG,EAAE,CAAC,EAAEP,UAAUH;AACjC"}
@@ -0,0 +1,8 @@
1
+ import { MessagePartStatus, ThreadAssistantMessagePart, ThreadUserMessagePart } from "../types/message.js";
2
+ //#region src/utils/normalizePartStatus.d.ts
3
+ declare const COMPLETE_STATUS: MessagePartStatus;
4
+ declare const RUNNING_STATUS: MessagePartStatus;
5
+ declare const normalizePartStatus: (part: ThreadUserMessagePart | ThreadAssistantMessagePart) => MessagePartStatus | undefined;
6
+ //#endregion
7
+ export { COMPLETE_STATUS, RUNNING_STATUS, normalizePartStatus };
8
+ //# sourceMappingURL=normalizePartStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizePartStatus.d.ts","names":[],"sources":["../../src/utils/normalizePartStatus.ts"],"mappings":";;cAOa,iBAAiB;cAGjB,gBAAgB;cAoBhB,sBACX,MAAM,wBAAwB,+BAC7B"}
@@ -0,0 +1,39 @@
1
+ //#region src/utils/normalizePartStatus.ts
2
+ const COMPLETE_STATUS = Object.freeze({ type: "complete" });
3
+ const RUNNING_STATUS = Object.freeze({ type: "running" });
4
+ const INCOMPLETE_STATUSES = Object.freeze({
5
+ cancelled: Object.freeze({
6
+ type: "incomplete",
7
+ reason: "cancelled"
8
+ }),
9
+ length: Object.freeze({
10
+ type: "incomplete",
11
+ reason: "length"
12
+ }),
13
+ "content-filter": Object.freeze({
14
+ type: "incomplete",
15
+ reason: "content-filter"
16
+ }),
17
+ other: Object.freeze({
18
+ type: "incomplete",
19
+ reason: "other"
20
+ }),
21
+ error: Object.freeze({
22
+ type: "incomplete",
23
+ reason: "error"
24
+ })
25
+ });
26
+ const normalizePartStatus = (part) => {
27
+ const status = part.status;
28
+ if (!status || typeof status !== "object") return void 0;
29
+ const { type } = status;
30
+ if (type === "running") return RUNNING_STATUS;
31
+ if (type === "complete") return COMPLETE_STATUS;
32
+ if (type !== "incomplete") return void 0;
33
+ const { reason } = status;
34
+ return INCOMPLETE_STATUSES[reason === "cancelled" || reason === "length" || reason === "content-filter" || reason === "other" || reason === "error" ? reason : "other"];
35
+ };
36
+ //#endregion
37
+ export { COMPLETE_STATUS, RUNNING_STATUS, normalizePartStatus };
38
+
39
+ //# sourceMappingURL=normalizePartStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizePartStatus.js","names":["MessagePartStatus","MessagePartStreamStatus","ThreadAssistantMessagePart","ThreadUserMessagePart","COMPLETE_STATUS","Object","freeze","type","RUNNING_STATUS","IncompleteMessagePartStatus","Extract","INCOMPLETE_STATUSES","Readonly","Record","cancelled","reason","length","other","error","normalizePartStatus","part","status","undefined","normalizedReason"],"sources":["../../src/utils/normalizePartStatus.ts"],"sourcesContent":["import type {\n MessagePartStatus,\n MessagePartStreamStatus,\n ThreadAssistantMessagePart,\n ThreadUserMessagePart,\n} from \"../types/message\";\n\nexport const COMPLETE_STATUS: MessagePartStatus = Object.freeze({\n type: \"complete\",\n});\nexport const RUNNING_STATUS: MessagePartStatus = Object.freeze({\n type: \"running\",\n});\ntype IncompleteMessagePartStatus = Extract<\n MessagePartStreamStatus,\n { readonly type: \"incomplete\" }\n>;\nconst INCOMPLETE_STATUSES: Readonly<\n Record<IncompleteMessagePartStatus[\"reason\"], IncompleteMessagePartStatus>\n> = Object.freeze({\n cancelled: Object.freeze({ type: \"incomplete\", reason: \"cancelled\" }),\n length: Object.freeze({ type: \"incomplete\", reason: \"length\" }),\n \"content-filter\": Object.freeze({\n type: \"incomplete\",\n reason: \"content-filter\",\n }),\n other: Object.freeze({ type: \"incomplete\", reason: \"other\" }),\n error: Object.freeze({ type: \"incomplete\", reason: \"error\" }),\n});\n\nexport const normalizePartStatus = (\n part: ThreadUserMessagePart | ThreadAssistantMessagePart,\n): MessagePartStatus | undefined => {\n const status = (part as { readonly status?: unknown }).status;\n if (!status || typeof status !== \"object\") return undefined;\n\n const { type } = status as { readonly type?: unknown };\n if (type === \"running\") return RUNNING_STATUS;\n if (type === \"complete\") return COMPLETE_STATUS;\n if (type !== \"incomplete\") return undefined;\n\n const { reason } = status as {\n readonly reason?: unknown;\n };\n const normalizedReason: IncompleteMessagePartStatus[\"reason\"] =\n reason === \"cancelled\" ||\n reason === \"length\" ||\n reason === \"content-filter\" ||\n reason === \"other\" ||\n reason === \"error\"\n ? reason\n : \"other\";\n\n return INCOMPLETE_STATUSES[normalizedReason];\n};\n"],"mappings":";AAOA,MAAaI,kBAAqCC,OAAOC,OAAO,EAC9DC,MAAM,WACR,CAAC;AACD,MAAaC,iBAAoCH,OAAOC,OAAO,EAC7DC,MAAM,UACR,CAAC;AAKD,MAAMI,sBAEFN,OAAOC,OAAO;CAChBQ,WAAWT,OAAOC,OAAO;EAAEC,MAAM;EAAcQ,QAAQ;CAAY,CAAC;CACpEC,QAAQX,OAAOC,OAAO;EAAEC,MAAM;EAAcQ,QAAQ;CAAS,CAAC;CAC9D,kBAAkBV,OAAOC,OAAO;EAC9BC,MAAM;EACNQ,QAAQ;CACV,CAAC;CACDE,OAAOZ,OAAOC,OAAO;EAAEC,MAAM;EAAcQ,QAAQ;CAAQ,CAAC;CAC5DG,OAAOb,OAAOC,OAAO;EAAEC,MAAM;EAAcQ,QAAQ;CAAQ,CAAC;AAC9D,CAAC;AAED,MAAaI,uBACXC,SACkC;CAClC,MAAMC,SAAUD,KAAuCC;CACvD,IAAI,CAACA,UAAU,OAAOA,WAAW,UAAU,OAAOC,KAAAA;CAElD,MAAM,EAAEf,SAASc;CACjB,IAAId,SAAS,WAAW,OAAOC;CAC/B,IAAID,SAAS,YAAY,OAAOH;CAChC,IAAIG,SAAS,cAAc,OAAOe,KAAAA;CAElC,MAAM,EAAEP,WAAWM;CAYnB,OAAOV,oBARLI,WAAW,eACXA,WAAW,YACXA,WAAW,oBACXA,WAAW,WACXA,WAAW,UACPA,SACA;AAGR"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/core",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Framework-agnostic core runtime for assistant-ui",
5
5
  "keywords": [
6
6
  "assistant",
@@ -59,7 +59,7 @@
59
59
  ],
60
60
  "sideEffects": false,
61
61
  "dependencies": {
62
- "assistant-stream": "^0.3.30",
62
+ "assistant-stream": "^0.3.31",
63
63
  "nanoid": "^6.0.0"
64
64
  },
65
65
  "optionalDevDependencies": {
@@ -95,8 +95,8 @@
95
95
  "react-dom": "^19.2.8",
96
96
  "vitest": "^4.1.10",
97
97
  "zustand": "^5.0.14",
98
- "@assistant-ui/store": "0.3.1",
99
- "@assistant-ui/tap": "0.9.7",
98
+ "@assistant-ui/store": "0.3.2",
99
+ "@assistant-ui/tap": "0.9.9",
100
100
  "@assistant-ui/vite": "0.0.11",
101
101
  "@assistant-ui/x-buildutils": "0.0.20",
102
102
  "assistant-cloud": "0.1.37"
package/src/index.ts CHANGED
@@ -34,6 +34,7 @@ export type {
34
34
  ThreadAssistantMessagePart,
35
35
  // Message status
36
36
  MessagePartStatus,
37
+ MessagePartStreamStatus,
37
38
  ToolCallMessagePartStatus,
38
39
  MessageStatus,
39
40
  // Thread messages
package/src/internal.ts CHANGED
@@ -32,6 +32,7 @@ export {
32
32
 
33
33
  // Message utilities
34
34
  export { getThreadMessageText } from "./utils/text";
35
+ export { toMessagePartStatus } from "./runtime/api/message-runtime";
35
36
  export { resolveToolApprovalResponse } from "./runtime/utils/resolveToolApprovalResponse";
36
37
  export { consumeSuggestionResult } from "./adapters/suggestion";
37
38
 
@@ -58,12 +59,5 @@ export { isJSONValue, isRecord } from "./utils/json/is-json";
58
59
  // outbound part conversion lives in one place.
59
60
  export { httpUrlPattern, parseDataUrl } from "./utils/data-url";
60
61
 
61
- // Runtime extras helper for external-store adapters. Internal because the
62
- // tap-native runtime path replaces the `thread.extras` side-channel it wraps.
63
- export {
64
- createRuntimeExtras,
65
- type RuntimeExtras,
66
- } from "./react/runtimes/createRuntimeExtras";
67
-
68
62
  export * from "./runtime/internal";
69
63
  export * from "./runtimes/internal";
@@ -19,24 +19,37 @@ export type AssistantProviderBaseProps = PropsWithChildren<{
19
19
  aui?: AssistantClient | null;
20
20
  }>;
21
21
 
22
+ const AssistantProviderInner: FC<
23
+ PropsWithChildren<{ runtime: AssistantRuntime }>
24
+ > = ({ runtime, children }) => {
25
+ // The runtime has a stable identity but mutates in place: its options are
26
+ // pushed in by an unconditional effect inside <RenderComponent />, so that
27
+ // element must be re-created every commit for React to re-render it and
28
+ // re-run the effect. React Compiler caches <RenderComponent /> on the
29
+ // stable RenderComponent type, which silences the effect and stops option
30
+ // changes (e.g. unstable_enableMessageQueue) from reaching the runtime.
31
+ "use no memo";
32
+ const aui = useAui({ threads: RuntimeAdapter(runtime) });
33
+ const RenderComponent = getRenderComponent(runtime);
34
+ return (
35
+ <AuiProvider value={aui}>
36
+ {RenderComponent && <RenderComponent />}
37
+ {children}
38
+ </AuiProvider>
39
+ );
40
+ };
41
+
22
42
  export const AssistantProviderBase: FC<AssistantProviderBaseProps> = memo(
23
- ({ runtime, aui: parent = null, children }) => {
24
- // The runtime has a stable identity but mutates in place: its options are
25
- // pushed in by an unconditional effect inside <RenderComponent />, so that
26
- // element must be re-created every commit for React to re-render it and
27
- // re-run the effect. React Compiler caches <RenderComponent /> on the
28
- // stable RenderComponent type, which silences the effect and stops option
29
- // changes (e.g. unstable_enableMessageQueue) from reaching the runtime.
30
- "use no memo";
31
- const aui = useAui({ threads: RuntimeAdapter(runtime) }, { parent });
32
- const RenderComponent = getRenderComponent(runtime);
43
+ ({ runtime, aui = null, children }) => {
33
44
  const inner = (
34
- <AuiProvider value={aui}>
35
- {RenderComponent && <RenderComponent />}
45
+ <AssistantProviderInner runtime={runtime}>
36
46
  {children}
37
- </AuiProvider>
47
+ </AssistantProviderInner>
48
+ );
49
+ return aui ? (
50
+ <AuiProvider value={aui}>{inner}</AuiProvider>
51
+ ) : (
52
+ <AuiProvider value={null}>{inner}</AuiProvider>
38
53
  );
39
- if (!parent) return inner;
40
- return <AuiProvider value={parent}>{inner}</AuiProvider>;
41
54
  },
42
55
  );
@@ -0,0 +1,101 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import type { ReactNode } from "react";
4
+ import { useEffect } from "react";
5
+ import { cleanup, render } from "@testing-library/react";
6
+ import { afterEach, describe, expect, it } from "vitest";
7
+ import { resource } from "@assistant-ui/tap";
8
+ import { AuiProvider, useAui, type AssistantClient } from "@assistant-ui/store";
9
+ import type { ChatModelAdapter } from "../runtime/utils/chat-model-adapter";
10
+ import { AssistantRuntimeProvider } from "./AssistantRuntimeProvider";
11
+ import { useLocalRuntime } from "./runtimes/useLocalRuntime";
12
+
13
+ type AnyClient = Record<string, any>;
14
+
15
+ const chatModel: ChatModelAdapter = {
16
+ run: async () => ({ content: [] }),
17
+ };
18
+
19
+ const makeCounterClient = (log?: string[]) => {
20
+ const useCounterClient = () => {
21
+ useEffect(() => {
22
+ log?.push("parent tap effect");
23
+ }, []);
24
+ return { getState: () => ({ count: 0 }) };
25
+ };
26
+ return resource(useCounterClient);
27
+ };
28
+
29
+ afterEach(() => {
30
+ cleanup();
31
+ });
32
+
33
+ describe("AssistantRuntimeProvider aui composition", () => {
34
+ it("isolates from ambient context by default", () => {
35
+ let aui!: AnyClient;
36
+ const Consumer = () => {
37
+ aui = useAui();
38
+ return null;
39
+ };
40
+
41
+ const Ambient = ({ children }: { children: ReactNode }) => {
42
+ const ambient = useAui({
43
+ parentScope: makeCounterClient()(),
44
+ } as unknown as useAui.Props);
45
+ return <AuiProvider value={ambient}>{children}</AuiProvider>;
46
+ };
47
+
48
+ const App = () => {
49
+ const runtime = useLocalRuntime(chatModel);
50
+ return (
51
+ <AssistantRuntimeProvider runtime={runtime}>
52
+ <Consumer />
53
+ </AssistantRuntimeProvider>
54
+ );
55
+ };
56
+
57
+ render(
58
+ <Ambient>
59
+ <App />
60
+ </Ambient>,
61
+ );
62
+
63
+ expect(aui.threads).toBeDefined();
64
+ expect(() => aui.parentScope.getState()).toThrow(
65
+ /The current scope does not have a "parentScope" property/,
66
+ );
67
+ });
68
+
69
+ it("inherits a prop-passed parent and mounts its effects", () => {
70
+ const log: string[] = [];
71
+ let aui!: AnyClient;
72
+ const Consumer = () => {
73
+ aui = useAui();
74
+ useEffect(() => {
75
+ log.push("consumer effect");
76
+ }, []);
77
+ return null;
78
+ };
79
+
80
+ const App = ({ parent }: { parent: AssistantClient }) => {
81
+ const runtime = useLocalRuntime(chatModel);
82
+ return (
83
+ <AssistantRuntimeProvider runtime={runtime} aui={parent}>
84
+ <Consumer />
85
+ </AssistantRuntimeProvider>
86
+ );
87
+ };
88
+
89
+ const Root = () => {
90
+ const parent = useAui({
91
+ parentScope: makeCounterClient(log)(),
92
+ } as unknown as useAui.Props);
93
+ return <App parent={parent} />;
94
+ };
95
+
96
+ render(<Root />);
97
+
98
+ expect(aui.parentScope.getState()).toEqual({ count: 0 });
99
+ expect(log).toEqual(["parent tap effect", "consumer effect"]);
100
+ });
101
+ });
@@ -1,5 +1,12 @@
1
1
  import { type AssistantStream, createAssistantStream } from "assistant-stream";
2
- import { type FC, type PropsWithChildren, useMemo } from "react";
2
+ import {
3
+ type FC,
4
+ type PropsWithChildren,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from "react";
3
10
  import { useAui } from "@assistant-ui/store";
4
11
  import type {
5
12
  RemoteThreadInitializeResponse,
@@ -276,22 +283,26 @@ export const parseStoredMessageRepository = (
276
283
 
277
284
  class AsyncStorageHistoryAdapter implements ThreadHistoryAdapter {
278
285
  private storage: AsyncStorageLike;
279
- private aui: ReturnType<typeof useAui>;
286
+ private getAui: () => ReturnType<typeof useAui>;
280
287
  private prefix: string;
281
288
  private mutationQueue: KeyedMutationQueue;
282
289
 
283
290
  constructor(
284
291
  storage: AsyncStorageLike,
285
- aui: ReturnType<typeof useAui>,
292
+ getAui: () => ReturnType<typeof useAui>,
286
293
  prefix: string,
287
294
  mutationQueue: KeyedMutationQueue,
288
295
  ) {
289
296
  this.storage = storage;
290
- this.aui = aui;
297
+ this.getAui = getAui;
291
298
  this.prefix = prefix;
292
299
  this.mutationQueue = mutationQueue;
293
300
  }
294
301
 
302
+ private get aui(): ReturnType<typeof useAui> {
303
+ return this.getAui();
304
+ }
305
+
295
306
  private _messagesKey(remoteId: string) {
296
307
  return `${this.prefix}messages:${remoteId}`;
297
308
  }
@@ -334,9 +345,19 @@ const createHistoryProvider = (
334
345
  ): FC<PropsWithChildren> => {
335
346
  const Provider: FC<PropsWithChildren> = ({ children }) => {
336
347
  const aui = useAui();
337
- const history = useMemo(
338
- () => new AsyncStorageHistoryAdapter(storage, aui, prefix, mutationQueue),
339
- [aui],
348
+ // Not useEffectEvent: history adapter methods run during render (SSR load).
349
+ const auiRef = useRef(aui);
350
+ useEffect(() => {
351
+ auiRef.current = aui;
352
+ });
353
+ const [history] = useState(
354
+ () =>
355
+ new AsyncStorageHistoryAdapter(
356
+ storage,
357
+ () => auiRef.current,
358
+ prefix,
359
+ mutationQueue,
360
+ ),
340
361
  );
341
362
  const adapters = useMemo(() => ({ history }), [history]);
342
363
 
@@ -226,6 +226,10 @@ export {
226
226
  type StreamingTimingOptions,
227
227
  type StreamingTimingState,
228
228
  } from "./runtimes/useStreamingTiming";
229
+ export {
230
+ createRuntimeExtras,
231
+ type RuntimeExtras,
232
+ } from "./runtimes/createRuntimeExtras";
229
233
  export { RemoteThreadListHookInstanceManager } from "./runtimes/RemoteThreadListHookInstanceManager";
230
234
  export { RemoteThreadListThreadListRuntimeCore } from "./runtimes/RemoteThreadListThreadListRuntimeCore";
231
235
  export { useRemoteThreadListRuntime } from "./runtimes/useRemoteThreadListRuntime";
@@ -1,3 +1,5 @@
1
+ /** @vitest-environment jsdom */
2
+ import { cleanup, renderHook } from "@testing-library/react";
1
3
  import { afterEach, describe, expect, it, vi } from "vitest";
2
4
 
3
5
  const mocks = vi.hoisted(() => {
@@ -23,18 +25,18 @@ const mocks = vi.hoisted(() => {
23
25
  setIsCopied,
24
26
  },
25
27
  },
28
+ currentAui: {
29
+ message: {
30
+ getCopyText: () => "Hello",
31
+ setIsCopied,
32
+ },
33
+ },
26
34
  };
27
35
  });
28
36
 
29
- vi.mock("react", async (importOriginal) => ({
30
- ...(await importOriginal<typeof import("react")>()),
31
- useCallback: ((callback: unknown) =>
32
- callback) as typeof import("react").useCallback,
33
- }));
34
-
35
37
  vi.mock("@assistant-ui/store", async (importOriginal) => ({
36
38
  ...(await importOriginal<typeof import("@assistant-ui/store")>()),
37
- useAui: () => mocks.aui,
39
+ useAui: () => mocks.currentAui,
38
40
  useAuiState: ((selector: (state: typeof mocks.state) => unknown) =>
39
41
  selector(mocks.state)) as typeof import("@assistant-ui/store").useAuiState,
40
42
  }));
@@ -42,25 +44,28 @@ vi.mock("@assistant-ui/store", async (importOriginal) => ({
42
44
  import { useActionBarCopy } from "./useActionBarCopy";
43
45
 
44
46
  afterEach(() => {
47
+ cleanup();
45
48
  vi.clearAllMocks();
49
+ vi.useRealTimers();
50
+ mocks.currentAui = mocks.aui;
46
51
  });
47
52
 
48
53
  describe("useActionBarCopy", () => {
49
54
  it("does not report copy success without a clipboard handler", async () => {
50
- const { copy, disabled } = useActionBarCopy();
55
+ const { result } = renderHook(() => useActionBarCopy());
51
56
 
52
- copy();
57
+ result.current.copy();
53
58
  await Promise.resolve();
54
59
 
55
- expect(disabled).toBe(true);
60
+ expect(result.current.disabled).toBe(true);
56
61
  expect(mocks.setIsCopied).not.toHaveBeenCalled();
57
62
  });
58
63
 
59
64
  it("reports copy success after the clipboard handler resolves", async () => {
60
65
  const copyToClipboard = vi.fn();
61
- const { copy } = useActionBarCopy({ copyToClipboard });
66
+ const { result } = renderHook(() => useActionBarCopy({ copyToClipboard }));
62
67
 
63
- copy();
68
+ result.current.copy();
64
69
  await Promise.resolve();
65
70
 
66
71
  expect(copyToClipboard).toHaveBeenCalledWith("Hello");
@@ -69,11 +74,101 @@ describe("useActionBarCopy", () => {
69
74
 
70
75
  it("does not report copy success when the clipboard handler rejects", async () => {
71
76
  const copyToClipboard = vi.fn().mockRejectedValue(new Error("denied"));
72
- const { copy } = useActionBarCopy({ copyToClipboard });
77
+ const { result } = renderHook(() => useActionBarCopy({ copyToClipboard }));
78
+
79
+ result.current.copy();
80
+ await Promise.resolve();
81
+
82
+ expect(mocks.setIsCopied).not.toHaveBeenCalled();
83
+ });
84
+
85
+ it("keeps copy success visible for the full duration after copying again", async () => {
86
+ vi.useFakeTimers();
87
+ const copyToClipboard = vi.fn();
88
+ const { result } = renderHook(() =>
89
+ useActionBarCopy({
90
+ copiedDuration: 3000,
91
+ copyToClipboard,
92
+ }),
93
+ );
94
+
95
+ result.current.copy();
96
+ await Promise.resolve();
97
+ await vi.advanceTimersByTimeAsync(1000);
98
+
99
+ result.current.copy();
100
+ await Promise.resolve();
101
+ mocks.setIsCopied.mockClear();
102
+
103
+ await vi.advanceTimersByTimeAsync(2000);
104
+ expect(mocks.setIsCopied).not.toHaveBeenCalledWith(false);
73
105
 
74
- copy();
106
+ await vi.advanceTimersByTimeAsync(1000);
107
+ expect(mocks.setIsCopied).toHaveBeenCalledWith(false);
108
+ });
109
+
110
+ it("resets copy feedback when unmounted", async () => {
111
+ vi.useFakeTimers();
112
+ const copyToClipboard = vi.fn();
113
+ const { result, unmount } = renderHook(() =>
114
+ useActionBarCopy({ copyToClipboard }),
115
+ );
116
+
117
+ result.current.copy();
118
+ await Promise.resolve();
119
+ expect(vi.getTimerCount()).toBe(1);
120
+
121
+ mocks.setIsCopied.mockClear();
122
+ unmount();
123
+
124
+ expect(vi.getTimerCount()).toBe(0);
125
+ expect(mocks.setIsCopied).toHaveBeenCalledWith(false);
126
+ });
127
+
128
+ it("ignores clipboard success after unmount", async () => {
129
+ vi.useFakeTimers();
130
+ let resolveCopy: (() => void) | undefined;
131
+ const copyToClipboard = vi.fn(
132
+ () =>
133
+ new Promise<void>((resolve) => {
134
+ resolveCopy = resolve;
135
+ }),
136
+ );
137
+ const { result, unmount } = renderHook(() =>
138
+ useActionBarCopy({ copyToClipboard }),
139
+ );
140
+
141
+ result.current.copy();
142
+ unmount();
143
+ resolveCopy?.();
75
144
  await Promise.resolve();
76
145
 
77
146
  expect(mocks.setIsCopied).not.toHaveBeenCalled();
147
+ expect(vi.getTimerCount()).toBe(0);
148
+ });
149
+
150
+ it("resets feedback for the previous message scope", async () => {
151
+ vi.useFakeTimers();
152
+ const copyToClipboard = vi.fn();
153
+ const nextSetIsCopied = vi.fn();
154
+ const { result, rerender } = renderHook(() =>
155
+ useActionBarCopy({ copyToClipboard }),
156
+ );
157
+
158
+ result.current.copy();
159
+ await Promise.resolve();
160
+ mocks.setIsCopied.mockClear();
161
+
162
+ mocks.currentAui = {
163
+ message: {
164
+ getCopyText: () => "Next message",
165
+ setIsCopied: nextSetIsCopied,
166
+ },
167
+ };
168
+ rerender();
169
+
170
+ expect(mocks.setIsCopied).toHaveBeenCalledWith(false);
171
+ expect(nextSetIsCopied).not.toHaveBeenCalled();
172
+ expect(vi.getTimerCount()).toBe(0);
78
173
  });
79
174
  });
@@ -1,4 +1,4 @@
1
- import { useCallback } from "react";
1
+ import { useCallback, useEffect, useRef } from "react";
2
2
  import { useAui, useAuiState } from "@assistant-ui/store";
3
3
 
4
4
  export type UseActionBarCopyOptions = {
@@ -21,19 +21,44 @@ export const useActionBarCopy = ({
21
21
  const isCopied = useAuiState((s) => s.message.isCopied);
22
22
  const isEditing = useAuiState((s) => s.composer.isEditing);
23
23
  const composerValue = useAuiState((s) => s.composer.text);
24
+ const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
25
+ undefined,
26
+ );
27
+ const scopeGenerationRef = useRef(0);
28
+
29
+ useEffect(
30
+ () => () => {
31
+ scopeGenerationRef.current += 1;
32
+ if (copiedTimerRef.current === undefined) return;
33
+
34
+ clearTimeout(copiedTimerRef.current);
35
+ copiedTimerRef.current = undefined;
36
+ aui.message.setIsCopied(false);
37
+ },
38
+ [aui],
39
+ );
24
40
 
25
41
  const copy = useCallback(() => {
26
42
  if (!copyToClipboard) return;
27
43
 
28
44
  const valueToCopy = isEditing ? composerValue : aui.message.getCopyText();
29
45
  if (!valueToCopy) return;
46
+ const scopeGeneration = scopeGenerationRef.current;
30
47
 
31
48
  // The rejection handler swallows clipboard write failures (permission denied,
32
49
  // API unavailable) so they don't surface as unhandled promise rejections.
33
50
  Promise.resolve(copyToClipboard(valueToCopy)).then(
34
51
  () => {
52
+ if (scopeGeneration !== scopeGenerationRef.current) return;
53
+
54
+ if (copiedTimerRef.current !== undefined) {
55
+ clearTimeout(copiedTimerRef.current);
56
+ }
35
57
  aui.message.setIsCopied(true);
36
- setTimeout(() => aui.message.setIsCopied(false), copiedDuration);
58
+ copiedTimerRef.current = setTimeout(() => {
59
+ copiedTimerRef.current = undefined;
60
+ aui.message.setIsCopied(false);
61
+ }, copiedDuration);
37
62
  },
38
63
  () => {},
39
64
  );
@@ -8,6 +8,7 @@ import type {
8
8
  MessagePartStatus,
9
9
  ToolCallMessagePartStatus,
10
10
  } from "../../../types/message";
11
+ import { getGroupStatus } from "../../../utils/getGroupStatus";
11
12
  import {
12
13
  buildGroupTree,
13
14
  GROUPBY_MEMO_KEY,
@@ -21,7 +22,8 @@ export namespace MessagePrimitiveGroupedParts {
21
22
  * A coalesced group of adjacent parts. Surfaced through the same
22
23
  * `{ part }` channel as a leaf {@link EnrichedPartState} so consumers
23
24
  * dispatch on a single `switch (part.type)`. `type` is the group key
24
- * (always `"group-…"`); `status` mirrors the last contained part.
25
+ * (always `"group-…"`); `status` is running when any contained part runs,
26
+ * otherwise it mirrors the last contained part.
25
27
  */
26
28
  export type GroupPart<TKey extends `group-${string}` = `group-${string}`> = {
27
29
  readonly type: TKey;
@@ -133,8 +135,6 @@ export namespace MessagePrimitiveGroupedParts {
133
135
  };
134
136
  }
135
137
 
136
- const COMPLETE_STATUS: MessagePartStatus = Object.freeze({ type: "complete" });
137
-
138
138
  const shouldShowIndicator = (
139
139
  mode: MessagePrimitiveGroupedParts.IndicatorMode,
140
140
  parts: readonly PartState[],
@@ -194,7 +194,7 @@ const renderNode = <TKey extends `group-${string}`>(
194
194
  );
195
195
  }
196
196
 
197
- const status = parts[node.indices.at(-1)!]?.status ?? COMPLETE_STATUS;
197
+ const status = getGroupStatus(parts, node.indices);
198
198
  const groupPart: MessagePrimitiveGroupedParts.GroupPart<TKey> = {
199
199
  type: node.key as TKey,
200
200
  status,