@assistant-ui/core 0.2.12 → 0.2.14

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 (212) hide show
  1. package/dist/adapters/thread-history.d.ts +3 -1
  2. package/dist/adapters/thread-history.d.ts.map +1 -1
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/react/AssistantProvider.js +6 -1
  6. package/dist/react/AssistantProvider.js.map +1 -1
  7. package/dist/react/RuntimeAdapter.d.ts +1 -1
  8. package/dist/react/RuntimeAdapter.d.ts.map +1 -1
  9. package/dist/react/RuntimeAdapter.js +16 -6
  10. package/dist/react/RuntimeAdapter.js.map +1 -1
  11. package/dist/react/client/DataRenderers.d.ts +1 -8
  12. package/dist/react/client/DataRenderers.d.ts.map +1 -1
  13. package/dist/react/client/DataRenderers.js +3 -2
  14. package/dist/react/client/DataRenderers.js.map +1 -1
  15. package/dist/react/client/Interactables.d.ts +1 -1
  16. package/dist/react/client/Interactables.d.ts.map +1 -1
  17. package/dist/react/client/Interactables.js +4 -3
  18. package/dist/react/client/Interactables.js.map +1 -1
  19. package/dist/react/client/Tools.d.ts +2 -13
  20. package/dist/react/client/Tools.d.ts.map +1 -1
  21. package/dist/react/client/Tools.js +4 -3
  22. package/dist/react/client/Tools.js.map +1 -1
  23. package/dist/react/primitives/message/MessageGroupedParts.d.ts +3 -2
  24. package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
  25. package/dist/react/primitives/message/MessageGroupedParts.js +4 -4
  26. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  27. package/dist/react/primitives/message/MessageParts.d.ts +28 -1
  28. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  29. package/dist/react/primitives/message/MessageParts.js +43 -9
  30. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  31. package/dist/react/providers/TextMessagePartProvider.d.ts.map +1 -1
  32. package/dist/react/providers/TextMessagePartProvider.js +3 -2
  33. package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
  34. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +2 -0
  35. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  36. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +2 -0
  37. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  38. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +1 -0
  39. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  40. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  41. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +6 -0
  42. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  43. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  44. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +2 -0
  45. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  46. package/dist/react/utils/groupParts.d.ts +13 -1
  47. package/dist/react/utils/groupParts.d.ts.map +1 -1
  48. package/dist/react/utils/groupParts.js +17 -5
  49. package/dist/react/utils/groupParts.js.map +1 -1
  50. package/dist/runtime/api/bindings.d.ts +1 -0
  51. package/dist/runtime/api/bindings.d.ts.map +1 -1
  52. package/dist/runtime/api/message-runtime.d.ts +2 -0
  53. package/dist/runtime/api/message-runtime.d.ts.map +1 -1
  54. package/dist/runtime/api/message-runtime.js +5 -0
  55. package/dist/runtime/api/message-runtime.js.map +1 -1
  56. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  57. package/dist/runtime/api/thread-list-runtime.js +1 -0
  58. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  59. package/dist/runtime/api/thread-runtime.d.ts +3 -0
  60. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  61. package/dist/runtime/api/thread-runtime.js +4 -0
  62. package/dist/runtime/api/thread-runtime.js.map +1 -1
  63. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
  64. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  65. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  66. package/dist/runtime/branch/external-thread-branch-adapter.d.ts +30 -0
  67. package/dist/runtime/branch/external-thread-branch-adapter.d.ts.map +1 -0
  68. package/dist/runtime/branch/external-thread-branch-adapter.js +0 -0
  69. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +1 -0
  70. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  71. package/dist/runtime/interfaces/thread-runtime-core.d.ts +2 -0
  72. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  73. package/dist/runtimes/external-store/external-store-adapter.d.ts +1 -0
  74. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  75. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +1 -0
  76. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  77. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +13 -0
  78. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  79. package/dist/runtimes/local/local-runtime-options.d.ts +1 -1
  80. package/dist/runtimes/local/local-thread-runtime-core.d.ts +8 -1
  81. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  82. package/dist/runtimes/local/local-thread-runtime-core.js +63 -5
  83. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  84. package/dist/runtimes/local/should-continue.js +4 -2
  85. package/dist/runtimes/local/should-continue.js.map +1 -1
  86. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +2 -0
  87. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  88. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +4 -0
  89. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  90. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
  91. package/dist/runtimes/remote-thread-list/empty-thread-core.js +4 -0
  92. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  93. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +1 -0
  94. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  95. package/dist/runtimes/remote-thread-list/remote-thread-state.js +1 -0
  96. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  97. package/dist/runtimes/remote-thread-list/types.d.ts +1 -0
  98. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  99. package/dist/store/clients/chain-of-thought-client.d.ts +2 -7
  100. package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
  101. package/dist/store/clients/chain-of-thought-client.js +3 -2
  102. package/dist/store/clients/chain-of-thought-client.js.map +1 -1
  103. package/dist/store/clients/model-context-client.d.ts +1 -1
  104. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  105. package/dist/store/clients/model-context-client.js +3 -2
  106. package/dist/store/clients/model-context-client.js.map +1 -1
  107. package/dist/store/clients/no-op-composer-client.d.ts +2 -4
  108. package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
  109. package/dist/store/clients/no-op-composer-client.js +3 -2
  110. package/dist/store/clients/no-op-composer-client.js.map +1 -1
  111. package/dist/store/clients/runtime-adapter.d.ts +1 -3
  112. package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
  113. package/dist/store/clients/runtime-adapter.js +2 -15
  114. package/dist/store/clients/runtime-adapter.js.map +1 -1
  115. package/dist/store/clients/suggestions.d.ts +1 -4
  116. package/dist/store/clients/suggestions.d.ts.map +1 -1
  117. package/dist/store/clients/suggestions.js +6 -4
  118. package/dist/store/clients/suggestions.js.map +1 -1
  119. package/dist/store/clients/thread-message-client.d.ts +1 -1
  120. package/dist/store/clients/thread-message-client.d.ts.map +1 -1
  121. package/dist/store/clients/thread-message-client.js +14 -10
  122. package/dist/store/clients/thread-message-client.js.map +1 -1
  123. package/dist/store/internal.d.ts +2 -2
  124. package/dist/store/internal.js +2 -2
  125. package/dist/store/runtime-clients/attachment-runtime-client.d.ts +2 -4
  126. package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
  127. package/dist/store/runtime-clients/attachment-runtime-client.js +3 -2
  128. package/dist/store/runtime-clients/attachment-runtime-client.js.map +1 -1
  129. package/dist/store/runtime-clients/composer-runtime-client.d.ts +2 -10
  130. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  131. package/dist/store/runtime-clients/composer-runtime-client.js +9 -6
  132. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  133. package/dist/store/runtime-clients/message-part-runtime-client.d.ts +2 -4
  134. package/dist/store/runtime-clients/message-part-runtime-client.d.ts.map +1 -1
  135. package/dist/store/runtime-clients/message-part-runtime-client.js +3 -2
  136. package/dist/store/runtime-clients/message-part-runtime-client.js.map +1 -1
  137. package/dist/store/runtime-clients/message-runtime-client.d.ts +2 -7
  138. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  139. package/dist/store/runtime-clients/message-runtime-client.js +10 -6
  140. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  141. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts +2 -4
  142. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  143. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +3 -2
  144. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  145. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts +2 -5
  146. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  147. package/dist/store/runtime-clients/thread-list-runtime-client.js +6 -4
  148. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  149. package/dist/store/runtime-clients/thread-runtime-client.d.ts +2 -4
  150. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  151. package/dist/store/runtime-clients/thread-runtime-client.js +7 -4
  152. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  153. package/dist/store/scopes/message.d.ts +1 -0
  154. package/dist/store/scopes/message.d.ts.map +1 -1
  155. package/dist/store/scopes/thread-list-item.d.ts +1 -0
  156. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  157. package/dist/store/scopes/thread.d.ts +1 -0
  158. package/dist/store/scopes/thread.d.ts.map +1 -1
  159. package/package.json +4 -4
  160. package/src/adapters/thread-history.ts +2 -0
  161. package/src/index.ts +1 -0
  162. package/src/react/AssistantProvider.tsx +3 -1
  163. package/src/react/RuntimeAdapter.ts +25 -8
  164. package/src/react/client/DataRenderers.ts +42 -45
  165. package/src/react/client/Interactables.ts +261 -261
  166. package/src/react/client/Tools.ts +6 -4
  167. package/src/react/primitives/message/MessageGroupedParts.tsx +19 -7
  168. package/src/react/primitives/message/MessageParts.tsx +64 -13
  169. package/src/react/providers/TextMessagePartProvider.tsx +5 -3
  170. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +1 -0
  171. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +11 -0
  172. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +6 -0
  173. package/src/react/utils/groupParts.ts +27 -0
  174. package/src/runtime/api/bindings.ts +1 -0
  175. package/src/runtime/api/message-runtime.ts +7 -0
  176. package/src/runtime/api/thread-list-runtime.ts +1 -0
  177. package/src/runtime/api/thread-runtime.ts +7 -0
  178. package/src/runtime/base/base-thread-runtime-core.ts +1 -0
  179. package/src/runtime/branch/external-thread-branch-adapter.ts +26 -0
  180. package/src/runtime/interfaces/thread-list-runtime-core.ts +1 -0
  181. package/src/runtime/interfaces/thread-runtime-core.ts +2 -0
  182. package/src/runtimes/external-store/external-store-adapter.ts +1 -0
  183. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +24 -0
  184. package/src/runtimes/local/local-runtime-options.ts +1 -1
  185. package/src/runtimes/local/local-thread-runtime-core.test.ts +311 -0
  186. package/src/runtimes/local/local-thread-runtime-core.ts +104 -7
  187. package/src/runtimes/local/should-continue.ts +23 -13
  188. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +5 -0
  189. package/src/runtimes/remote-thread-list/empty-thread-core.ts +5 -0
  190. package/src/runtimes/remote-thread-list/remote-thread-state.ts +2 -0
  191. package/src/runtimes/remote-thread-list/types.ts +1 -0
  192. package/src/store/clients/chain-of-thought-client.ts +5 -3
  193. package/src/store/clients/model-context-client.test.ts +5 -4
  194. package/src/store/clients/model-context-client.ts +21 -21
  195. package/src/store/clients/no-op-composer-client.ts +5 -3
  196. package/src/store/clients/runtime-adapter.ts +0 -24
  197. package/src/store/clients/suggestions.ts +9 -18
  198. package/src/store/clients/thread-message-client.ts +29 -26
  199. package/src/store/internal.ts +1 -4
  200. package/src/store/runtime-clients/attachment-runtime-client.ts +14 -14
  201. package/src/store/runtime-clients/composer-runtime-client.ts +30 -24
  202. package/src/store/runtime-clients/message-part-runtime-client.ts +5 -3
  203. package/src/store/runtime-clients/message-runtime-client.ts +26 -19
  204. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +5 -3
  205. package/src/store/runtime-clients/thread-list-runtime-client.ts +10 -6
  206. package/src/store/runtime-clients/thread-runtime-client.ts +11 -6
  207. package/src/store/scopes/message.ts +1 -0
  208. package/src/store/scopes/thread-list-item.ts +1 -0
  209. package/src/store/scopes/thread.ts +1 -0
  210. package/src/tests/external-store-thread-runtime-core.test.ts +57 -0
  211. package/src/tests/groupMessageParts.test.ts +84 -0
  212. package/src/tests/groupParts.test.ts +55 -0
@@ -44,9 +44,24 @@ import { useShallow } from "zustand/shallow";
44
44
 
45
45
  type MessagePartRange =
46
46
  | { type: "single"; index: number }
47
- | { type: "toolGroup"; startIndex: number; endIndex: number }
48
- | { type: "reasoningGroup"; startIndex: number; endIndex: number }
49
- | { type: "chainOfThoughtGroup"; startIndex: number; endIndex: number };
47
+ | {
48
+ type: "toolGroup";
49
+ startIndex: number;
50
+ endIndex: number;
51
+ idKey?: string | undefined;
52
+ }
53
+ | {
54
+ type: "reasoningGroup";
55
+ startIndex: number;
56
+ endIndex: number;
57
+ idKey?: string | undefined;
58
+ }
59
+ | {
60
+ type: "chainOfThoughtGroup";
61
+ startIndex: number;
62
+ endIndex: number;
63
+ idKey?: string | undefined;
64
+ };
50
65
 
51
66
  /**
52
67
  * Creates a group state manager for a specific part type.
@@ -91,10 +106,13 @@ const createGroupState = <
91
106
  * Groups consecutive tool-call and reasoning message parts into ranges.
92
107
  * Always groups tool calls and reasoning parts, even if there's only one.
93
108
  * When useChainOfThought is true, groups tool-call and reasoning parts together.
109
+ * `partIds[i]` optionally carries a stable identity for part `i`; group
110
+ * ranges derive an `idKey` from their first part's id (first claim wins).
94
111
  */
95
- const groupMessageParts = (
112
+ export const groupMessageParts = (
96
113
  messageTypes: readonly string[],
97
114
  useChainOfThought: boolean,
115
+ partIds?: readonly (string | undefined)[],
98
116
  ): MessagePartRange[] => {
99
117
  const ranges: MessagePartRange[] = [];
100
118
 
@@ -137,22 +155,44 @@ const groupMessageParts = (
137
155
  reasoningGroup.finalize(messageTypes.length - 1, ranges);
138
156
  }
139
157
 
158
+ if (partIds) {
159
+ const claimed = new Set<string>();
160
+ for (const range of ranges) {
161
+ if (range.type === "single") continue;
162
+ const id = partIds[range.startIndex];
163
+ if (id !== undefined && !claimed.has(id)) {
164
+ claimed.add(id);
165
+ range.idKey = `id:${id}`;
166
+ }
167
+ }
168
+ }
169
+
140
170
  return ranges;
141
171
  };
142
172
 
143
173
  const useMessagePartsGroups = (
144
174
  useChainOfThought: boolean,
145
- ): MessagePartRange[] => {
175
+ ): { ranges: MessagePartRange[]; partIds: (string | undefined)[] } => {
146
176
  const messageTypes = useAuiState(
147
177
  useShallow((s) => s.message.parts.map((c: any) => c.type)),
148
178
  );
179
+ const partIds = useAuiState(
180
+ useShallow((s) =>
181
+ s.message.parts.map((c: any) =>
182
+ c.type === "tool-call" ? c.toolCallId : undefined,
183
+ ),
184
+ ),
185
+ );
149
186
 
150
187
  return useMemo(() => {
151
188
  if (messageTypes.length === 0) {
152
- return [];
189
+ return { ranges: [], partIds };
153
190
  }
154
- return groupMessageParts(messageTypes, useChainOfThought);
155
- }, [messageTypes, useChainOfThought]);
191
+ return {
192
+ ranges: groupMessageParts(messageTypes, useChainOfThought, partIds),
193
+ partIds,
194
+ };
195
+ }, [messageTypes, partIds, useChainOfThought]);
156
196
  };
157
197
 
158
198
  export namespace MessagePrimitiveParts {
@@ -795,13 +835,24 @@ const MessagePrimitivePartsCompat: FC<{
795
835
  }> = ({ components, unstable_showEmptyOnNonTextEnd }) => {
796
836
  const contentLength = useAuiState((s) => s.message.parts.length);
797
837
  const useChainOfThought = !!components?.ChainOfThought;
798
- const messageRanges = useMessagePartsGroups(useChainOfThought);
838
+ const { ranges: messageRanges, partIds } =
839
+ useMessagePartsGroups(useChainOfThought);
799
840
 
800
841
  const partsElements = useMemo(() => {
801
842
  if (contentLength === 0) {
802
843
  return <EmptyParts components={components} />;
803
844
  }
804
845
 
846
+ const claimed = new Set<string>();
847
+ const toolLeafKey = (partIndex: number) => {
848
+ const id = partIds[partIndex];
849
+ if (id !== undefined && !claimed.has(id)) {
850
+ claimed.add(id);
851
+ return `part-id:${id}`;
852
+ }
853
+ return `part-${partIndex}`;
854
+ };
855
+
805
856
  return messageRanges.map((range) => {
806
857
  if (range.type === "single") {
807
858
  return (
@@ -816,7 +867,7 @@ const MessagePrimitivePartsCompat: FC<{
816
867
  if (!ChainOfThoughtComponent) return null;
817
868
  return (
818
869
  <ChainOfThoughtByIndicesProvider
819
- key={`chainOfThought-${range.startIndex}`}
870
+ key={`chainOfThought-${range.idKey ?? range.startIndex}`}
820
871
  startIndex={range.startIndex}
821
872
  endIndex={range.endIndex}
822
873
  >
@@ -828,7 +879,7 @@ const MessagePrimitivePartsCompat: FC<{
828
879
  components?.ToolGroup ?? defaultComponents.ToolGroup;
829
880
  return (
830
881
  <ToolGroupComponent
831
- key={`tool-${range.startIndex}`}
882
+ key={`tool-${range.idKey ?? range.startIndex}`}
832
883
  startIndex={range.startIndex}
833
884
  endIndex={range.endIndex}
834
885
  >
@@ -838,7 +889,7 @@ const MessagePrimitivePartsCompat: FC<{
838
889
  const partIndex = range.startIndex + i;
839
890
  return (
840
891
  <MessagePrimitivePartByIndex
841
- key={`part-${partIndex}`}
892
+ key={toolLeafKey(partIndex)}
842
893
  index={partIndex}
843
894
  components={components}
844
895
  />
@@ -874,7 +925,7 @@ const MessagePrimitivePartsCompat: FC<{
874
925
  );
875
926
  }
876
927
  });
877
- }, [messageRanges, components, contentLength]);
928
+ }, [messageRanges, partIds, components, contentLength]);
878
929
 
879
930
  return (
880
931
  <>
@@ -4,13 +4,13 @@ import type { PartState } from "../../store/scopes/part";
4
4
 
5
5
  import { resource } from "@assistant-ui/tap";
6
6
 
7
- const TextMessagePartClient = resource(function TextMessagePartClient({
7
+ const useTextMessagePartClient = ({
8
8
  text,
9
9
  isRunning,
10
10
  }: {
11
11
  text: string;
12
12
  isRunning: boolean;
13
- }): ClientOutput<"part"> {
13
+ }): ClientOutput<"part"> => {
14
14
  const state = useMemo<PartState>(
15
15
  () => ({
16
16
  type: "text",
@@ -32,7 +32,9 @@ const TextMessagePartClient = resource(function TextMessagePartClient({
32
32
  throw new Error("Not supported");
33
33
  },
34
34
  };
35
- });
35
+ };
36
+
37
+ const TextMessagePartClient = resource(useTextMessagePartClient);
36
38
 
37
39
  export const TextMessagePartProvider: FC<
38
40
  PropsWithChildren<{
@@ -296,6 +296,7 @@ export class RemoteThreadListThreadListRuntimeCore
296
296
  externalId: remoteMetadata.externalId,
297
297
  status: remoteMetadata.status,
298
298
  title: remoteMetadata.title,
299
+ lastMessageAt: remoteMetadata.lastMessageAt,
299
300
  custom: remoteMetadata.custom,
300
301
  } as RemoteThreadData,
301
302
  };
@@ -57,6 +57,11 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
57
57
  if (!remoteId) return;
58
58
  await formatted.update?.(remoteId, item, localMessageId);
59
59
  },
60
+ async delete() {
61
+ throw new Error(
62
+ "Assistant Cloud does not support deleting thread messages yet.",
63
+ );
64
+ },
60
65
  reportTelemetry(
61
66
  items: MessageFormatItem<TMessage>[],
62
67
  options?: {
@@ -97,6 +102,12 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
97
102
  }
98
103
  }
99
104
 
105
+ async delete() {
106
+ throw new Error(
107
+ "Assistant Cloud does not support deleting thread messages yet.",
108
+ );
109
+ }
110
+
100
111
  async load() {
101
112
  const remoteId = this.aui.threadListItem().getState().remoteId;
102
113
  if (!remoteId) return { messages: [] };
@@ -94,6 +94,9 @@ export const useCloudThreadListAdapter = (
94
94
  status: t.is_archived ? "archived" : "regular",
95
95
  remoteId: t.id,
96
96
  title: t.title,
97
+ lastMessageAt: t.last_message_at
98
+ ? new Date(t.last_message_at)
99
+ : undefined,
97
100
  externalId: t.external_id ?? undefined,
98
101
  custom: toCustom(t.metadata),
99
102
  })),
@@ -153,6 +156,9 @@ export const useCloudThreadListAdapter = (
153
156
  status: thread.is_archived ? "archived" : "regular",
154
157
  remoteId: thread.id,
155
158
  title: thread.title,
159
+ lastMessageAt: thread.last_message_at
160
+ ? new Date(thread.last_message_at)
161
+ : undefined,
156
162
  externalId: thread.external_id ?? undefined,
157
163
  custom: toCustom(thread.metadata),
158
164
  };
@@ -119,6 +119,11 @@ export interface GroupNodeGroup {
119
119
  readonly key: string;
120
120
  /** Structural React key: sibling-index path, e.g. `"0.1.0"`. */
121
121
  readonly nodeKey: string;
122
+ /**
123
+ * Identity key (`"id:<partId>"`) from the group's first part; undefined
124
+ * when absent or already claimed by an earlier sibling.
125
+ */
126
+ readonly idKey: string | undefined;
122
127
  /** Indices of parts in this subtree, in order. */
123
128
  readonly indices: readonly number[];
124
129
  readonly children: readonly GroupNode[];
@@ -130,6 +135,11 @@ export interface GroupNodePart {
130
135
  readonly index: number;
131
136
  /** Structural React key: sibling-index path within parent. */
132
137
  readonly nodeKey: string;
138
+ /**
139
+ * Identity key (`"id:<partId>"`); undefined when absent or already
140
+ * claimed by an earlier sibling.
141
+ */
142
+ readonly idKey: string | undefined;
133
143
  }
134
144
 
135
145
  interface BuildFrame {
@@ -138,6 +148,7 @@ interface BuildFrame {
138
148
  indices: number[];
139
149
  children: GroupNode[];
140
150
  nextChildIdx: number;
151
+ claimed: Set<string>;
141
152
  }
142
153
 
143
154
  const makeChildNodeKey = (parent: BuildFrame): string => {
@@ -145,13 +156,25 @@ const makeChildNodeKey = (parent: BuildFrame): string => {
145
156
  return parent.nodeKey === "" ? String(idx) : `${parent.nodeKey}.${idx}`;
146
157
  };
147
158
 
159
+ const claimIdKey = (
160
+ frame: BuildFrame,
161
+ id: string | undefined,
162
+ ): string | undefined => {
163
+ if (id === undefined || frame.claimed.has(id)) return undefined;
164
+ frame.claimed.add(id);
165
+ return `id:${id}`;
166
+ };
167
+
148
168
  /**
149
169
  * Build the group tree from an array of normalized group paths.
150
170
  * `paths[i]` is the path for part `i`. The output tree contains one
151
171
  * `part` node per part and one `group` node per coalesced run.
172
+ * `partIds[i]` optionally carries a stable identity for part `i` (e.g. a
173
+ * tool call id), from which nodes derive an `idKey`.
152
174
  */
153
175
  export const buildGroupTree = (
154
176
  paths: readonly (readonly string[])[],
177
+ partIds?: readonly (string | undefined)[],
155
178
  ): readonly GroupNode[] => {
156
179
  const root: BuildFrame = {
157
180
  key: "",
@@ -159,6 +182,7 @@ export const buildGroupTree = (
159
182
  indices: [],
160
183
  children: [],
161
184
  nextChildIdx: 0,
185
+ claimed: new Set(),
162
186
  };
163
187
  const stack: BuildFrame[] = [root];
164
188
 
@@ -169,6 +193,7 @@ export const buildGroupTree = (
169
193
  type: "group",
170
194
  key: closing.key,
171
195
  nodeKey: closing.nodeKey,
196
+ idKey: claimIdKey(parent, partIds?.[closing.indices[0]!]),
172
197
  indices: closing.indices,
173
198
  children: closing.children,
174
199
  });
@@ -202,6 +227,7 @@ export const buildGroupTree = (
202
227
  indices: [],
203
228
  children: [],
204
229
  nextChildIdx: 0,
230
+ claimed: new Set(),
205
231
  });
206
232
  }
207
233
 
@@ -211,6 +237,7 @@ export const buildGroupTree = (
211
237
  type: "part",
212
238
  index: i,
213
239
  nodeKey: makeChildNodeKey(top),
240
+ idKey: claimIdKey(top, partIds?.[i]),
214
241
  });
215
242
 
216
243
  // Record the part index in every open ancestor group.
@@ -42,5 +42,6 @@ export type ThreadListItemState = {
42
42
  readonly externalId: string | undefined;
43
43
  readonly status: import("../interfaces/thread-list-runtime-core").ThreadListItemStatus;
44
44
  readonly title?: string | undefined;
45
+ readonly lastMessageAt?: Date | undefined;
45
46
  readonly custom?: Record<string, unknown> | undefined;
46
47
  };
@@ -101,6 +101,7 @@ export type MessageRuntime = {
101
101
  readonly composer: EditComposerRuntime;
102
102
 
103
103
  getState(): MessageState;
104
+ delete(): void | Promise<void>;
104
105
  reload(config?: ReloadConfig): void;
105
106
  /**
106
107
  * @deprecated This API is still under active development and might change without notice.
@@ -155,6 +156,7 @@ export class MessageRuntimeImpl implements MessageRuntime {
155
156
 
156
157
  protected __internal_bindMethods() {
157
158
  this.reload = this.reload.bind(this);
159
+ this.delete = this.delete.bind(this);
158
160
  this.getState = this.getState.bind(this);
159
161
  this.subscribe = this.subscribe.bind(this);
160
162
  this.getMessagePartByIndex = this.getMessagePartByIndex.bind(this);
@@ -180,6 +182,11 @@ export class MessageRuntimeImpl implements MessageRuntime {
180
182
  return this._core.getState();
181
183
  }
182
184
 
185
+ public delete() {
186
+ const state = this._core.getState();
187
+ return this._threadBinding.getState().deleteMessage(state.id);
188
+ }
189
+
183
190
  public reload(reloadConfig: ReloadConfig = {}) {
184
191
  const editComposerRuntimeCore = this._getEditComposerRuntimeCore();
185
192
  const composerRuntimeCore =
@@ -88,6 +88,7 @@ const getThreadListItemState = (
88
88
  externalId: threadData.externalId,
89
89
  title: threadData.title,
90
90
  status: threadData.status,
91
+ lastMessageAt: threadData.lastMessageAt,
91
92
  custom: threadData.custom,
92
93
  isMain: threadData.id === threadList.mainThreadId,
93
94
  };
@@ -252,6 +252,8 @@ export type ThreadRuntime = {
252
252
  */
253
253
  append(message: CreateAppendMessage): void;
254
254
 
255
+ deleteMessage(messageId: string): void | Promise<void>;
256
+
255
257
  /**
256
258
  * Start a new run with the given configuration.
257
259
  * @param config The configuration for starting the run
@@ -374,6 +376,7 @@ export class ThreadRuntimeImpl implements ThreadRuntime {
374
376
 
375
377
  protected __internal_bindMethods() {
376
378
  this.append = this.append.bind(this);
379
+ this.deleteMessage = this.deleteMessage.bind(this);
377
380
  this.resumeRun = this.resumeRun.bind(this);
378
381
  this.importExternalState = this.importExternalState.bind(this);
379
382
  this.exportExternalState = this.exportExternalState.bind(this);
@@ -411,6 +414,10 @@ export class ThreadRuntimeImpl implements ThreadRuntime {
411
414
  );
412
415
  }
413
416
 
417
+ public deleteMessage(messageId: string) {
418
+ return this._threadBinding.getState().deleteMessage(messageId);
419
+ }
420
+
414
421
  public subscribe(callback: () => void) {
415
422
  return this._threadBinding.subscribe(callback);
416
423
  }
@@ -56,6 +56,7 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
56
56
 
57
57
  public abstract get capabilities(): RuntimeCapabilities;
58
58
  public abstract append(message: AppendMessage): void;
59
+ public abstract deleteMessage(messageId: string): void | Promise<void>;
59
60
  public abstract startRun(config: StartRunConfig): void;
60
61
  public abstract resumeRun(config: ResumeRunConfig): void;
61
62
  public abstract addToolResult(options: AddToolResultOptions): void;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The branch surface a runtime exposes so the branch picker can navigate
3
+ * between sibling variants of a message. Read during render; recreate the
4
+ * adapter object when branch data changes.
5
+ *
6
+ * Editing a message creates a branch only if the `onEdit` handler creates a
7
+ * sibling and `getBranches` reports it. Note that `ExternalThread`'s edit
8
+ * payload carries the edited message's own id as both `parentId` and
9
+ * `sourceId`.
10
+ */
11
+ export type ExternalThreadBranchAdapter = {
12
+ /**
13
+ * Returns the sibling branch ids for a message in display order, including
14
+ * the message's own id. Return an empty array for messages without
15
+ * alternative branches.
16
+ */
17
+ getBranches: (messageId: string) => readonly string[];
18
+ /**
19
+ * Makes the given branch the visible one. The runtime is expected to swap
20
+ * the `messages` array to the selected branch. May be invoked
21
+ * programmatically while a run is in progress; pending queue items are not
22
+ * cleared, and reconciling them with the new branch is the runtime's
23
+ * responsibility.
24
+ */
25
+ switchToBranch: (branchId: string) => void;
26
+ };
@@ -10,6 +10,7 @@ export type ThreadListItemCoreState = {
10
10
 
11
11
  readonly status: ThreadListItemStatus;
12
12
  readonly title?: string | undefined;
13
+ readonly lastMessageAt?: Date | undefined;
13
14
  readonly custom?: Record<string, unknown> | undefined;
14
15
 
15
16
  readonly runtime?: ThreadRuntimeCore | undefined;
@@ -23,6 +23,7 @@ export type RuntimeCapabilities = {
23
23
  readonly switchBranchDuringRun: boolean;
24
24
  readonly edit: boolean;
25
25
  readonly reload: boolean;
26
+ readonly delete: boolean;
26
27
  readonly cancel: boolean;
27
28
  readonly unstable_copy: boolean;
28
29
  readonly speech: boolean;
@@ -146,6 +147,7 @@ export type ThreadRuntimeCore = Readonly<{
146
147
  switchToBranch: (branchId: string) => void;
147
148
 
148
149
  append: (message: AppendMessage) => void;
150
+ deleteMessage: (messageId: string) => void | Promise<void>;
149
151
  startRun: (config: StartRunConfig) => void;
150
152
  resumeRun: (config: ResumeRunConfig) => void;
151
153
  cancelRun: () => void;
@@ -109,6 +109,7 @@ type ExternalStoreAdapterBase<T> = {
109
109
  /** Opt in to message queuing. Typically produced by `createMessageQueue`. */
110
110
  queue?: ExternalThreadQueueAdapter | undefined;
111
111
  onEdit?: ((message: AppendMessage) => Promise<void>) | undefined;
112
+ onDelete?: ((messageId: string) => Promise<void> | void) | undefined;
112
113
  onReload?: // TODO: remove parentId in 0.12.0
113
114
  | ((parentId: string | null, config: StartRunConfig) => Promise<void>)
114
115
  | undefined;
@@ -60,6 +60,7 @@ export class ExternalStoreThreadRuntimeCore
60
60
  switchToBranch: false,
61
61
  switchBranchDuringRun: false,
62
62
  edit: false,
63
+ delete: false,
63
64
  reload: false,
64
65
  cancel: false,
65
66
  unstable_copy: false,
@@ -148,6 +149,9 @@ export class ExternalStoreThreadRuntimeCore
148
149
  switchToBranch: this._store.setMessages !== undefined,
149
150
  switchBranchDuringRun: false,
150
151
  edit: this._store.onEdit !== undefined,
152
+ delete:
153
+ this._store.onDelete !== undefined ||
154
+ this._store.setMessages !== undefined,
151
155
  reload: this._store.onReload !== undefined,
152
156
  cancel: this._store.onCancel !== undefined,
153
157
  speech: this._store.adapters?.speech !== undefined,
@@ -419,6 +423,26 @@ export class ExternalStoreThreadRuntimeCore
419
423
  }
420
424
  }
421
425
 
426
+ public async deleteMessage(messageId: string): Promise<void> {
427
+ if (this._store.onDelete) {
428
+ await this._store.onDelete(messageId);
429
+ return;
430
+ }
431
+
432
+ if (!this._store.setMessages)
433
+ throw new Error("Runtime does not support deleting messages.");
434
+
435
+ if (this._store.isRunning) {
436
+ await this._toolInvocations?.abort();
437
+ }
438
+
439
+ const messages = this.repository.getMessages();
440
+ const messageIndex = messages.findIndex((m) => m.id === messageId);
441
+ if (messageIndex === -1) throw new Error("Message not found.");
442
+
443
+ this.updateMessages(messages.filter((message) => message.id !== messageId));
444
+ }
445
+
422
446
  public getQueueItems() {
423
447
  // The composer reads this during base-thread construction, before the
424
448
  // constructor assigns `_store`, so guard against the unset field.
@@ -23,7 +23,7 @@ export type LocalRuntimeOptionsBase = {
23
23
  };
24
24
 
25
25
  /**
26
- * Names of tools that are allowed to interrupt the run in order to wait for human/external approval.
26
+ * Names of tools that pause the run until a result is supplied via `addToolResult`.
27
27
  */
28
28
  unstable_humanToolNames?: string[] | undefined;
29
29