@assistant-ui/react 0.14.14 → 0.14.16

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 (207) hide show
  1. package/README.md +5 -1
  2. package/dist/client/ExternalThread.d.ts +2 -12
  3. package/dist/client/ExternalThread.d.ts.map +1 -1
  4. package/dist/client/ExternalThread.js +30 -29
  5. package/dist/client/ExternalThread.js.map +1 -1
  6. package/dist/client/InMemoryThreadList.d.ts.map +1 -1
  7. package/dist/client/InMemoryThreadList.js +11 -10
  8. package/dist/client/InMemoryThreadList.js.map +1 -1
  9. package/dist/client/SingleThreadList.d.ts.map +1 -1
  10. package/dist/client/SingleThreadList.js +9 -8
  11. package/dist/client/SingleThreadList.js.map +1 -1
  12. package/dist/context/providers/ThreadViewportProvider.js +1 -1
  13. package/dist/context/providers/ThreadViewportProvider.js.map +1 -1
  14. package/dist/context/react/ThreadViewportContext.js +1 -1
  15. package/dist/context/react/utils/createContextHook.js +1 -1
  16. package/dist/context/react/utils/ensureBinding.js.map +1 -1
  17. package/dist/context/react/utils/useRuntimeState.js +1 -1
  18. package/dist/context/stores/ThreadViewport.js.map +1 -1
  19. package/dist/devtools/DevToolsHooks.js.map +1 -1
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.js +3 -3
  22. package/dist/legacy-runtime/AssistantRuntimeProvider.js +1 -1
  23. package/dist/legacy-runtime/cloud/auiV0.js +1 -1
  24. package/dist/legacy-runtime/hooks/AssistantContext.js.map +1 -1
  25. package/dist/legacy-runtime/hooks/AttachmentContext.js.map +1 -1
  26. package/dist/legacy-runtime/hooks/ComposerContext.js.map +1 -1
  27. package/dist/legacy-runtime/hooks/MessageContext.js.map +1 -1
  28. package/dist/legacy-runtime/hooks/MessagePartContext.js.map +1 -1
  29. package/dist/legacy-runtime/hooks/ThreadContext.js +1 -1
  30. package/dist/legacy-runtime/hooks/ThreadContext.js.map +1 -1
  31. package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +1 -1
  32. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +1 -1
  33. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +1 -1
  34. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +1 -1
  35. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +1 -1
  36. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  37. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +1 -1
  38. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +1 -1
  39. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  40. package/dist/mcp-apps/McpAppRenderer.js +7 -7
  41. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  42. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  43. package/dist/mcp-apps/McpAppsRemoteHost.js +5 -4
  44. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  45. package/dist/mcp-apps/app-frame.d.ts +1 -1
  46. package/dist/mcp-apps/app-frame.d.ts.map +1 -1
  47. package/dist/mcp-apps/app-frame.js +82 -104
  48. package/dist/mcp-apps/app-frame.js.map +1 -1
  49. package/dist/mcp-apps/bridge.d.ts +3 -3
  50. package/dist/mcp-apps/bridge.d.ts.map +1 -1
  51. package/dist/mcp-apps/bridge.js +35 -10
  52. package/dist/mcp-apps/bridge.js.map +1 -1
  53. package/dist/mcp-apps/types.d.ts +2 -12
  54. package/dist/mcp-apps/types.d.ts.map +1 -1
  55. package/dist/mcp-apps/types.js.map +1 -1
  56. package/dist/model-context/frame/useAssistantFrameHost.js +1 -1
  57. package/dist/model-context/makeAssistantVisible.js +1 -1
  58. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  59. package/dist/primitives/actionBar/ActionBarCopy.js +1 -1
  60. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +1 -1
  61. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  62. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -1
  63. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -1
  64. package/dist/primitives/actionBar/ActionBarInteractionContext.js +1 -1
  65. package/dist/primitives/actionBar/ActionBarRoot.js +1 -1
  66. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +1 -1
  67. package/dist/primitives/actionBarMore/ActionBarMoreContent.js +1 -1
  68. package/dist/primitives/actionBarMore/ActionBarMoreItem.js +1 -1
  69. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js +1 -1
  70. package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js +1 -1
  71. package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js +1 -1
  72. package/dist/primitives/assistantModal/AssistantModalAnchor.js +1 -1
  73. package/dist/primitives/assistantModal/AssistantModalContent.js +1 -1
  74. package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -1
  75. package/dist/primitives/assistantModal/AssistantModalTrigger.js +1 -1
  76. package/dist/primitives/attachment/AttachmentRemove.js +1 -1
  77. package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
  78. package/dist/primitives/attachment/AttachmentRoot.js +1 -1
  79. package/dist/primitives/attachment/AttachmentThumb.js +1 -1
  80. package/dist/primitives/branchPicker/BranchPickerRoot.js +1 -1
  81. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -1
  82. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
  83. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js +1 -1
  84. package/dist/primitives/composer/ComposerAddAttachment.js +1 -1
  85. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  86. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  87. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  88. package/dist/primitives/composer/ComposerDictationTranscript.js +1 -1
  89. package/dist/primitives/composer/ComposerInput.js +1 -1
  90. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  91. package/dist/primitives/composer/ComposerInputPluginContext.js +1 -1
  92. package/dist/primitives/composer/ComposerQuote.js +1 -1
  93. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  94. package/dist/primitives/composer/ComposerRoot.js +1 -1
  95. package/dist/primitives/composer/ComposerSend.js +1 -1
  96. package/dist/primitives/composer/ComposerStopDictation.js +1 -1
  97. package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
  98. package/dist/primitives/composer/trigger/TriggerPopover.js +2 -2
  99. package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
  100. package/dist/primitives/composer/trigger/TriggerPopoverAction.js +1 -1
  101. package/dist/primitives/composer/trigger/TriggerPopoverBack.js +1 -1
  102. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +1 -1
  103. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +1 -1
  104. package/dist/primitives/composer/trigger/TriggerPopoverItems.js +1 -1
  105. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
  106. package/dist/primitives/composer/trigger/TriggerPopoverResource.js +8 -7
  107. package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
  108. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js +1 -1
  109. package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
  110. package/dist/primitives/composer/trigger/triggerDetectionResource.js +5 -4
  111. package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
  112. package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
  113. package/dist/primitives/composer/trigger/triggerKeyboardResource.js +8 -7
  114. package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
  115. package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
  116. package/dist/primitives/composer/trigger/triggerNavigationResource.js +13 -12
  117. package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
  118. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  119. package/dist/primitives/composer/trigger/triggerSelectionResource.js +7 -6
  120. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  121. package/dist/primitives/error/ErrorMessage.js +1 -1
  122. package/dist/primitives/error/ErrorRoot.js +1 -1
  123. package/dist/primitives/message/MessagePartsGrouped.js +1 -1
  124. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  125. package/dist/primitives/message/MessageRoot.js +1 -1
  126. package/dist/primitives/message/MessageRoot.js.map +1 -1
  127. package/dist/primitives/messagePart/MessagePartImage.js +1 -1
  128. package/dist/primitives/messagePart/MessagePartText.js +1 -1
  129. package/dist/primitives/queueItem/QueueItemRemove.js +1 -1
  130. package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
  131. package/dist/primitives/queueItem/QueueItemSteer.js +1 -1
  132. package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
  133. package/dist/primitives/queueItem/QueueItemText.js +1 -1
  134. package/dist/primitives/reasoning/useScrollLock.js +1 -1
  135. package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
  136. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
  137. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  138. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +1 -1
  139. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  140. package/dist/primitives/suggestion/SuggestionDescription.js +1 -1
  141. package/dist/primitives/suggestion/SuggestionTitle.js +1 -1
  142. package/dist/primitives/suggestion/SuggestionTrigger.js +1 -1
  143. package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
  144. package/dist/primitives/thread/ThreadRoot.js +1 -1
  145. package/dist/primitives/thread/ThreadScrollToBottom.js +1 -1
  146. package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
  147. package/dist/primitives/thread/ThreadViewport.js +1 -1
  148. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  149. package/dist/primitives/thread/ThreadViewportFooter.js +1 -1
  150. package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
  151. package/dist/primitives/thread/topAnchor/topAnchorTurn.js.map +1 -1
  152. package/dist/primitives/thread/topAnchor/topAnchorUtils.js.map +1 -1
  153. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +1 -1
  154. package/dist/primitives/thread/useThreadViewportAutoScroll.js +1 -1
  155. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  156. package/dist/primitives/threadList/ThreadListNew.js +1 -1
  157. package/dist/primitives/threadList/ThreadListRoot.js +1 -1
  158. package/dist/primitives/threadListItem/ThreadListItemRoot.js +1 -1
  159. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +1 -1
  160. package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js +1 -1
  161. package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js +1 -1
  162. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +1 -1
  163. package/dist/sandbox-host/SandboxHost.d.ts +50 -0
  164. package/dist/sandbox-host/SandboxHost.d.ts.map +1 -0
  165. package/dist/sandbox-host/SandboxHost.js +85 -0
  166. package/dist/sandbox-host/SandboxHost.js.map +1 -0
  167. package/dist/unstable/useMentionAdapter.js +1 -1
  168. package/dist/unstable/useMentionAdapter.js.map +1 -1
  169. package/dist/unstable/useSlashCommandAdapter.js +1 -1
  170. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  171. package/dist/utils/Primitive.js +1 -1
  172. package/dist/utils/createActionButton.js +1 -1
  173. package/dist/utils/createActionButton.js.map +1 -1
  174. package/dist/utils/hooks/useManagedRef.js +1 -1
  175. package/dist/utils/hooks/useMediaQuery.js +1 -1
  176. package/dist/utils/hooks/useMediaQuery.js.map +1 -1
  177. package/dist/utils/hooks/useOnResizeContent.js +1 -1
  178. package/dist/utils/hooks/useOnScrollToBottom.js +1 -1
  179. package/dist/utils/hooks/useSizeHandle.js +1 -1
  180. package/dist/utils/json/is-json.js.map +1 -1
  181. package/dist/utils/smooth/SmoothContext.js +1 -1
  182. package/dist/utils/smooth/SmoothContext.js.map +1 -1
  183. package/dist/utils/smooth/useSmooth.js +1 -1
  184. package/dist/utils/smooth/useSmooth.js.map +1 -1
  185. package/dist/utils/useToolArgsFieldStatus.d.ts +2 -2
  186. package/dist/utils/useToolArgsFieldStatus.d.ts.map +1 -1
  187. package/package.json +48 -40
  188. package/src/client/ExternalThread.ts +484 -515
  189. package/src/client/InMemoryThreadList.ts +153 -162
  190. package/src/client/SingleThreadList.ts +87 -84
  191. package/src/context/providers/ThreadViewportProvider.tsx +2 -2
  192. package/src/index.ts +8 -1
  193. package/src/mcp-apps/McpAppRenderer.tsx +28 -35
  194. package/src/mcp-apps/McpAppsRemoteHost.ts +25 -24
  195. package/src/mcp-apps/app-frame.tsx +100 -141
  196. package/src/mcp-apps/bridge.test.ts +100 -60
  197. package/src/mcp-apps/bridge.ts +43 -21
  198. package/src/mcp-apps/types.ts +2 -12
  199. package/src/primitives/composer/trigger/TriggerPopover.tsx +1 -1
  200. package/src/primitives/composer/trigger/TriggerPopoverResource.ts +75 -76
  201. package/src/primitives/composer/trigger/triggerDetectionResource.ts +6 -5
  202. package/src/primitives/composer/trigger/triggerKeyboardResource.ts +9 -13
  203. package/src/primitives/composer/trigger/triggerNavigationResource.ts +14 -19
  204. package/src/primitives/composer/trigger/triggerSelectionResource.ts +8 -7
  205. package/src/sandbox-host/SandboxHost.test.tsx +231 -0
  206. package/src/sandbox-host/SandboxHost.tsx +185 -0
  207. package/src/tests/local-runtime-queue.test.tsx +305 -0
@@ -1,19 +1,14 @@
1
- import {
2
- resource,
3
- tapState,
4
- tapMemo,
5
- tapEffect,
6
- tapEffectEvent,
7
- } from "@assistant-ui/tap";
1
+ import { useState, useMemo, useEffect, useEffectEvent } from "react";
2
+ import { resource, withKey } from "@assistant-ui/tap";
8
3
  import {
9
4
  type ClientElement,
10
5
  type ClientOutput,
11
- tapClientLookup,
6
+ useClientLookup,
12
7
  attachTransformScopes,
13
- tapClientResource,
8
+ useClientResource,
14
9
  Derived,
15
10
  } from "@assistant-ui/store";
16
- import { withKey } from "@assistant-ui/tap";
11
+
17
12
  import type {
18
13
  AppendMessage,
19
14
  Attachment,
@@ -21,6 +16,7 @@ import type {
21
16
  ThreadAssistantMessagePart,
22
17
  ThreadUserMessagePart,
23
18
  ThreadMessage,
19
+ ExternalThreadQueueAdapter,
24
20
  } from "@assistant-ui/core";
25
21
  import type { QueueItemState } from "@assistant-ui/core/store";
26
22
  import type { ComposerSendOptions } from "@assistant-ui/core/store";
@@ -34,19 +30,6 @@ export type ExternalThreadMessage = ThreadMessage & {
34
30
  id: string;
35
31
  };
36
32
 
37
- export type ExternalThreadQueueAdapter = {
38
- /** The current queue items. */
39
- items: readonly QueueItemState[];
40
- /** Called when a message is submitted via the composer. Receives the steer preference. */
41
- enqueue: (message: AppendMessage, opts: { steer: boolean }) => void;
42
- /** Called to promote an existing queue item (cancel current run, run this immediately). */
43
- steer: (queueItemId: string) => void;
44
- /** Called to remove an item from the queue. */
45
- remove: (queueItemId: string) => void;
46
- /** Called to clear all pending queue items, with the reason for clearing. */
47
- clear: (reason: "edit" | "reload" | "cancel-run") => void;
48
- };
49
-
50
33
  export type ExternalThreadProps = {
51
34
  messages: readonly ExternalThreadMessage[];
52
35
  isRunning?: boolean;
@@ -79,151 +62,147 @@ type MessageClientProps = {
79
62
  };
80
63
 
81
64
  // Message Client - minimal implementation
82
- const MessageClient = resource(
83
- ({
84
- message,
85
- index,
86
- onEdit,
87
- onReload,
88
- queue,
89
- }: MessageClientProps): ClientOutput<"message"> => {
90
- const [isCopied, setIsCopied] = tapState(false);
91
- const [isHovering, setIsHovering] = tapState(false);
92
- const [isEditing, setIsEditing] = tapState(false);
93
-
94
- const partClients = tapClientLookup(
95
- () =>
96
- message.content.map((part, idx) =>
97
- withKey(idx, PartResource({ part })),
98
- ),
99
- [message.content],
100
- );
101
-
102
- const attachmentClients = tapClientLookup(
103
- () =>
104
- (message.attachments ?? []).map((attachment) =>
105
- withKey(
106
- attachment.id,
107
- AttachmentResource({
108
- attachment,
109
- onRemove: () => {},
110
- }),
111
- ),
65
+ const MessageClient = resource(function MessageClient({
66
+ message,
67
+ index,
68
+ onEdit,
69
+ onReload,
70
+ queue,
71
+ }: MessageClientProps): ClientOutput<"message"> {
72
+ const [isCopied, setIsCopied] = useState(false);
73
+ const [isHovering, setIsHovering] = useState(false);
74
+ const [isEditing, setIsEditing] = useState(false);
75
+
76
+ const partClients = useClientLookup(
77
+ () =>
78
+ message.content.map((part, idx) => withKey(idx, PartResource({ part }))),
79
+ [message.content],
80
+ );
81
+
82
+ const attachmentClients = useClientLookup(
83
+ () =>
84
+ (message.attachments ?? []).map((attachment) =>
85
+ withKey(
86
+ attachment.id,
87
+ AttachmentResource({
88
+ attachment,
89
+ onRemove: () => {},
90
+ }),
112
91
  ),
113
- [message.attachments],
114
- );
115
-
116
- const handleBeginEdit = () => {
117
- setIsEditing(true);
118
- };
119
-
120
- const handleCancelEdit = () => {
121
- setIsEditing(false);
122
- };
123
-
124
- const handleSendEdit = (msg: AppendMessage) => {
125
- queue?.clear("edit");
126
- onEdit?.({
127
- ...msg,
128
- parentId: message.id,
129
- sourceId: message.id,
130
- });
131
- setIsEditing(false);
132
- };
133
-
134
- const composerClient = tapClientResource(
135
- ComposerClientResource({
136
- type: "edit",
137
- isEditing,
138
- canCancel: true,
139
- onCancel: handleCancelEdit,
140
- onBeginEdit: handleBeginEdit,
141
- onSend: handleSendEdit,
142
- message,
143
- queue,
144
- }),
145
- );
92
+ ),
93
+ [message.attachments],
94
+ );
95
+
96
+ const handleBeginEdit = () => {
97
+ setIsEditing(true);
98
+ };
99
+
100
+ const handleCancelEdit = () => {
101
+ setIsEditing(false);
102
+ };
103
+
104
+ const handleSendEdit = (msg: AppendMessage) => {
105
+ queue?.clear("edit");
106
+ onEdit?.({
107
+ ...msg,
108
+ parentId: message.id,
109
+ sourceId: message.id,
110
+ });
111
+ setIsEditing(false);
112
+ };
146
113
 
147
- const state = tapMemo(() => {
148
- return {
149
- ...message,
150
- attachments: message.attachments ?? [],
151
- parentId: null,
152
- isLast: false, // Will be set by thread
153
- branchNumber: 1,
154
- branchCount: 1,
155
- speech: undefined,
156
- parts: partClients.state,
157
- isCopied,
158
- isHovering,
159
- index,
160
- composer: composerClient.state,
161
- };
162
- }, [
114
+ const composerClient = useClientResource(
115
+ ComposerClientResource({
116
+ type: "edit",
117
+ isEditing,
118
+ canCancel: true,
119
+ onCancel: handleCancelEdit,
120
+ onBeginEdit: handleBeginEdit,
121
+ onSend: handleSendEdit,
163
122
  message,
123
+ queue,
124
+ }),
125
+ );
126
+
127
+ const state = useMemo(() => {
128
+ return {
129
+ ...message,
130
+ attachments: message.attachments ?? [],
131
+ parentId: null,
132
+ isLast: false, // Will be set by thread
133
+ branchNumber: 1,
134
+ branchCount: 1,
135
+ speech: undefined,
136
+ parts: partClients.state,
164
137
  isCopied,
165
138
  isHovering,
166
139
  index,
167
- composerClient.state,
168
- partClients.state,
169
- ]);
170
-
171
- return {
172
- getState: () => state,
173
- composer: () => composerClient.methods,
174
- reload: () => {
175
- onReload?.();
176
- },
177
- speak: () => {},
178
- stopSpeaking: () => {},
179
- submitFeedback: () => {},
180
- switchToBranch: () => {},
181
- getCopyText: () =>
182
- message.content.map((c) => ("text" in c ? c.text : "")).join(""),
183
- part: (selector) => {
184
- if ("index" in selector) {
185
- return partClients.get(selector);
186
- }
187
- const partIndex = state.parts.findIndex(
188
- (p) => p.type === "tool-call" && p.toolCallId === selector.toolCallId,
189
- );
190
- return partClients.get({ index: partIndex });
191
- },
192
- attachment: (selector) => {
193
- if ("id" in selector) {
194
- return attachmentClients.get({ key: selector.id });
195
- }
196
- return attachmentClients.get(selector);
197
- },
198
- setIsCopied,
199
- setIsHovering,
140
+ composer: composerClient.state,
200
141
  };
201
- },
202
- );
142
+ }, [
143
+ message,
144
+ isCopied,
145
+ isHovering,
146
+ index,
147
+ composerClient.state,
148
+ partClients.state,
149
+ ]);
150
+
151
+ return {
152
+ getState: () => state,
153
+ composer: () => composerClient.methods,
154
+ reload: () => {
155
+ onReload?.();
156
+ },
157
+ speak: () => {},
158
+ stopSpeaking: () => {},
159
+ submitFeedback: () => {},
160
+ switchToBranch: () => {},
161
+ getCopyText: () =>
162
+ message.content.map((c) => ("text" in c ? c.text : "")).join(""),
163
+ part: (selector) => {
164
+ if ("index" in selector) {
165
+ return partClients.get(selector);
166
+ }
167
+ const partIndex = state.parts.findIndex(
168
+ (p) => p.type === "tool-call" && p.toolCallId === selector.toolCallId,
169
+ );
170
+ return partClients.get({ index: partIndex });
171
+ },
172
+ attachment: (selector) => {
173
+ if ("id" in selector) {
174
+ return attachmentClients.get({ key: selector.id });
175
+ }
176
+ return attachmentClients.get(selector);
177
+ },
178
+ setIsCopied,
179
+ setIsHovering,
180
+ };
181
+ });
203
182
 
204
183
  type PartResourceProps = {
205
184
  part: ThreadAssistantMessagePart | ThreadUserMessagePart;
206
185
  };
207
186
 
208
187
  // Part Client - minimal implementation
209
- const PartResource = resource(
210
- ({ part }: PartResourceProps): ClientOutput<"part"> => {
211
- const state = tapMemo(
212
- () => ({
213
- ...part,
214
- status: { type: "complete" as const },
215
- }),
216
- [part],
217
- );
218
-
219
- return {
220
- getState: () => state,
221
- addToolResult: () => {},
222
- resumeToolCall: () => {},
223
- respondToToolApproval: () => {},
224
- };
225
- },
226
- );
188
+ const PartResource = resource(function PartResource({
189
+ part,
190
+ }: PartResourceProps): ClientOutput<"part"> {
191
+ const state = useMemo(
192
+ () => ({
193
+ ...part,
194
+ status: { type: "complete" as const },
195
+ }),
196
+ [part],
197
+ );
198
+
199
+ return {
200
+ getState: () => state,
201
+ addToolResult: () => {},
202
+ resumeToolCall: () => {},
203
+ respondToToolApproval: () => {},
204
+ };
205
+ });
227
206
 
228
207
  type AttachmentResourceProps = {
229
208
  attachment: Attachment;
@@ -231,19 +210,17 @@ type AttachmentResourceProps = {
231
210
  };
232
211
 
233
212
  // Attachment Client - minimal implementation
234
- const AttachmentResource = resource(
235
- ({
236
- attachment,
237
- onRemove,
238
- }: AttachmentResourceProps): ClientOutput<"attachment"> => {
239
- return {
240
- getState: () => attachment,
241
- remove: async () => {
242
- onRemove?.();
243
- },
244
- };
245
- },
246
- );
213
+ const AttachmentResource = resource(function AttachmentResource({
214
+ attachment,
215
+ onRemove,
216
+ }: AttachmentResourceProps): ClientOutput<"attachment"> {
217
+ return {
218
+ getState: () => attachment,
219
+ remove: async () => {
220
+ onRemove?.();
221
+ },
222
+ };
223
+ });
247
224
 
248
225
  type ComposerClientResourceProps = {
249
226
  type: "thread" | "edit";
@@ -257,374 +234,366 @@ type ComposerClientResourceProps = {
257
234
  queue?: ExternalThreadQueueAdapter | undefined;
258
235
  };
259
236
 
260
- const QueueItemClient = resource(
261
- ({
262
- item,
263
- onSteer,
264
- onRemove,
265
- }: {
266
- item: QueueItemState;
267
- onSteer: () => void;
268
- onRemove: () => void;
269
- }): ClientOutput<"queueItem"> => {
270
- return {
271
- getState: () => item,
272
- steer: onSteer,
273
- remove: onRemove,
274
- };
275
- },
276
- );
237
+ const QueueItemClient = resource(function QueueItemClient({
238
+ item,
239
+ onSteer,
240
+ onRemove,
241
+ }: {
242
+ item: QueueItemState;
243
+ onSteer: () => void;
244
+ onRemove: () => void;
245
+ }): ClientOutput<"queueItem"> {
246
+ return {
247
+ getState: () => item,
248
+ steer: onSteer,
249
+ remove: onRemove,
250
+ };
251
+ });
277
252
 
278
253
  // Composer Client - minimal implementation
279
- const ComposerClientResource = resource(
280
- ({
281
- type,
282
- isEditing,
283
- canCancel,
284
- isSendDisabled = false,
285
- onCancel,
286
- onBeginEdit,
287
- onSend,
288
- message,
289
- queue,
290
- }: ComposerClientResourceProps): ClientOutput<"composer"> => {
291
- const [text, setText] = tapState("");
292
- const [role, setRole] = tapState<"user" | "assistant" | "system">("user");
293
- const [runConfig, setRunConfig] = tapState<Record<string, unknown>>({});
294
- const [attachments, setAttachments] = tapState<readonly Attachment[]>([]);
295
- const [quote, setQuote] = tapState<
296
- { readonly text: string; readonly messageId: string } | undefined
297
- >(undefined);
298
-
299
- // Update composer values when editing begins
300
- const updateFromMessage = tapEffectEvent(() => {
301
- if (message) {
302
- // Extract text from message content (text parts only)
303
- const textParts = message.content.filter(
304
- (part) => part.type === "text",
305
- );
306
- const messageText = textParts
307
- .map((part) => ("text" in part ? part.text : ""))
308
- .join("\n\n");
309
-
310
- setText(messageText);
311
- setRole(message.role);
312
- setAttachments(message.attachments ?? []);
313
- }
314
- });
254
+ const ComposerClientResource = resource(function ComposerClientResource({
255
+ type,
256
+ isEditing,
257
+ canCancel,
258
+ isSendDisabled = false,
259
+ onCancel,
260
+ onBeginEdit,
261
+ onSend,
262
+ message,
263
+ queue,
264
+ }: ComposerClientResourceProps): ClientOutput<"composer"> {
265
+ const [text, setText] = useState("");
266
+ const [role, setRole] = useState<"user" | "assistant" | "system">("user");
267
+ const [runConfig, setRunConfig] = useState<Record<string, unknown>>({});
268
+ const [attachments, setAttachments] = useState<readonly Attachment[]>([]);
269
+ const [quote, setQuote] = useState<
270
+ { readonly text: string; readonly messageId: string } | undefined
271
+ >(undefined);
272
+
273
+ // Update composer values when editing begins
274
+ const updateFromMessage = useEffectEvent(() => {
275
+ if (message) {
276
+ // Extract text from message content (text parts only)
277
+ const textParts = message.content.filter((part) => part.type === "text");
278
+ const messageText = textParts
279
+ .map((part) => ("text" in part ? part.text : ""))
280
+ .join("\n\n");
281
+
282
+ setText(messageText);
283
+ setRole(message.role);
284
+ setAttachments(message.attachments ?? []);
285
+ }
286
+ });
315
287
 
316
- tapEffect(() => {
317
- if (isEditing) {
318
- updateFromMessage();
319
- }
320
- }, [isEditing]);
321
-
322
- const attachmentClients = tapClientLookup(
323
- () =>
324
- attachments.map((attachment, idx) =>
325
- withKey(
326
- attachment.id,
327
- AttachmentResource({
328
- attachment,
329
- onRemove: () => {
330
- setAttachments(attachments.filter((_, i) => i !== idx));
331
- },
332
- }),
333
- ),
288
+ useEffect(() => {
289
+ if (isEditing) {
290
+ updateFromMessage();
291
+ }
292
+ }, [isEditing]);
293
+
294
+ const attachmentClients = useClientLookup(
295
+ () =>
296
+ attachments.map((attachment, idx) =>
297
+ withKey(
298
+ attachment.id,
299
+ AttachmentResource({
300
+ attachment,
301
+ onRemove: () => {
302
+ setAttachments(attachments.filter((_, i) => i !== idx));
303
+ },
304
+ }),
334
305
  ),
335
- [attachments],
336
- );
337
-
338
- const queueItems = queue?.items ?? EMPTY_QUEUE_ITEMS;
339
- const queueItemClients = tapClientLookup(
340
- () =>
341
- queueItems.map((item) =>
342
- withKey(
343
- item.id,
344
- QueueItemClient({
345
- item,
346
- onSteer: () => queue?.steer(item.id),
347
- onRemove: () => queue?.remove(item.id),
348
- }),
349
- ),
306
+ ),
307
+ [attachments],
308
+ );
309
+
310
+ const queueItems = queue?.items ?? EMPTY_QUEUE_ITEMS;
311
+ const queueItemClients = useClientLookup(
312
+ () =>
313
+ queueItems.map((item) =>
314
+ withKey(
315
+ item.id,
316
+ QueueItemClient({
317
+ item,
318
+ onSteer: () => queue?.steer(item.id),
319
+ onRemove: () => queue?.remove(item.id),
320
+ }),
350
321
  ),
351
- [queueItems],
352
- );
322
+ ),
323
+ [queueItems],
324
+ );
353
325
 
354
- const state = tapMemo(() => {
355
- const isEmpty = !text.trim() && !attachments.length;
356
- return {
357
- text,
358
- role,
359
- attachments: attachmentClients.state,
360
- runConfig,
361
- isEditing,
362
- canCancel,
363
- canSend: isEditing && !isEmpty && !isSendDisabled,
364
- attachmentAccept: "*",
365
- isEmpty,
366
- type,
367
- dictation: undefined,
368
- quote,
369
- queue: queueItems,
370
- };
371
- }, [
326
+ const state = useMemo(() => {
327
+ const isEmpty = !text.trim() && !attachments.length;
328
+ return {
372
329
  text,
373
330
  role,
374
- attachmentClients.state,
331
+ attachments: attachmentClients.state,
375
332
  runConfig,
376
333
  isEditing,
377
334
  canCancel,
378
- isSendDisabled,
335
+ canSend: isEditing && !isEmpty && !isSendDisabled,
336
+ attachmentAccept: "*",
337
+ isEmpty,
379
338
  type,
380
- attachments.length,
339
+ dictation: undefined,
381
340
  quote,
382
- queueItems,
383
- ]);
384
-
385
- return {
386
- getState: () => state,
387
- setText,
388
- setRole,
389
- setRunConfig,
390
- addAttachment: async (fileOrAttachment: File | CreateAttachment) => {
391
- if (fileOrAttachment instanceof File) {
392
- const newAttachment: Attachment = {
393
- id: Math.random().toString(36).substring(7),
394
- type: "file",
395
- name: fileOrAttachment.name,
396
- contentType: fileOrAttachment.type,
397
- file: fileOrAttachment,
398
- status: { type: "complete" },
399
- content: [],
400
- };
401
- setAttachments([...attachments, newAttachment]);
402
- } else {
403
- const newAttachment: Attachment = {
404
- id: fileOrAttachment.id ?? Math.random().toString(36).substring(7),
405
- type: fileOrAttachment.type ?? "document",
406
- name: fileOrAttachment.name,
407
- contentType: fileOrAttachment.contentType,
408
- content: fileOrAttachment.content,
409
- status: { type: "complete" },
410
- };
411
- setAttachments([...attachments, newAttachment]);
412
- }
413
- },
414
- clearAttachments: async () => {
415
- setAttachments([]);
416
- },
417
- attachment: (selector) => {
418
- if ("id" in selector) {
419
- return attachmentClients.get({ key: selector.id });
420
- }
421
- return attachmentClients.get(selector);
422
- },
423
- reset: async () => {
424
- setText("");
425
- setRole("user");
426
- setRunConfig({});
427
- setAttachments([]);
428
- setQuote(undefined);
429
- },
430
- send: (opts?: ComposerSendOptions) => {
431
- if (!state.canSend) return;
432
-
433
- const currentQuote = quote;
434
- const composedMessage: AppendMessage = {
435
- role,
436
- content: text ? [{ type: "text" as const, text }] : [],
437
- attachments: attachments as any,
438
- createdAt: new Date(),
439
- parentId: null,
440
- sourceId: null,
441
- runConfig,
442
- startRun: opts?.startRun,
443
- metadata: {
444
- custom: { ...(currentQuote ? { quote: currentQuote } : {}) },
445
- },
446
- };
447
- if (queue) {
448
- queue.enqueue(composedMessage, { steer: opts?.steer ?? false });
449
- } else {
450
- onSend?.(composedMessage);
451
- }
452
- setText("");
453
- setAttachments([]);
454
- setQuote(undefined);
455
- },
456
- cancel: onCancel,
457
- beginEdit: () => {
458
- onBeginEdit?.();
459
- },
460
- startDictation: () => {},
461
- stopDictation: () => {},
462
- setQuote,
463
- queueItem: (selector: { index: number }) => {
464
- return queueItemClients.get(selector);
465
- },
341
+ queue: queueItems,
466
342
  };
467
- },
468
- );
343
+ }, [
344
+ text,
345
+ role,
346
+ attachmentClients.state,
347
+ runConfig,
348
+ isEditing,
349
+ canCancel,
350
+ isSendDisabled,
351
+ type,
352
+ attachments.length,
353
+ quote,
354
+ queueItems,
355
+ ]);
356
+
357
+ return {
358
+ getState: () => state,
359
+ setText,
360
+ setRole,
361
+ setRunConfig,
362
+ addAttachment: async (fileOrAttachment: File | CreateAttachment) => {
363
+ if (fileOrAttachment instanceof File) {
364
+ const newAttachment: Attachment = {
365
+ id: Math.random().toString(36).substring(7),
366
+ type: "file",
367
+ name: fileOrAttachment.name,
368
+ contentType: fileOrAttachment.type,
369
+ file: fileOrAttachment,
370
+ status: { type: "complete" },
371
+ content: [],
372
+ };
373
+ setAttachments([...attachments, newAttachment]);
374
+ } else {
375
+ const newAttachment: Attachment = {
376
+ id: fileOrAttachment.id ?? Math.random().toString(36).substring(7),
377
+ type: fileOrAttachment.type ?? "document",
378
+ name: fileOrAttachment.name,
379
+ contentType: fileOrAttachment.contentType,
380
+ content: fileOrAttachment.content,
381
+ status: { type: "complete" },
382
+ };
383
+ setAttachments([...attachments, newAttachment]);
384
+ }
385
+ },
386
+ clearAttachments: async () => {
387
+ setAttachments([]);
388
+ },
389
+ attachment: (selector) => {
390
+ if ("id" in selector) {
391
+ return attachmentClients.get({ key: selector.id });
392
+ }
393
+ return attachmentClients.get(selector);
394
+ },
395
+ reset: async () => {
396
+ setText("");
397
+ setRole("user");
398
+ setRunConfig({});
399
+ setAttachments([]);
400
+ setQuote(undefined);
401
+ },
402
+ send: (opts?: ComposerSendOptions) => {
403
+ if (!state.canSend) return;
404
+
405
+ const currentQuote = quote;
406
+ const composedMessage: AppendMessage = {
407
+ role,
408
+ content: text ? [{ type: "text" as const, text }] : [],
409
+ attachments: attachments as any,
410
+ createdAt: new Date(),
411
+ parentId: null,
412
+ sourceId: null,
413
+ runConfig,
414
+ startRun: opts?.startRun,
415
+ metadata: {
416
+ custom: { ...(currentQuote ? { quote: currentQuote } : {}) },
417
+ },
418
+ };
419
+ if (queue) {
420
+ queue.enqueue(composedMessage, { steer: opts?.steer ?? false });
421
+ } else {
422
+ onSend?.(composedMessage);
423
+ }
424
+ setText("");
425
+ setAttachments([]);
426
+ setQuote(undefined);
427
+ },
428
+ cancel: onCancel,
429
+ beginEdit: () => {
430
+ onBeginEdit?.();
431
+ },
432
+ startDictation: () => {},
433
+ stopDictation: () => {},
434
+ setQuote,
435
+ queueItem: (selector: { index: number }) => {
436
+ return queueItemClients.get(selector);
437
+ },
438
+ };
439
+ });
469
440
 
470
441
  // External Thread Client
471
- export const ExternalThread = resource(
472
- ({
473
- messages,
474
- isRunning = false,
475
- isSendDisabled = false,
476
- onNew,
477
- onEdit,
478
- onReload,
479
- onStartRun,
480
- onCancel,
481
- queue,
482
- }: ExternalThreadProps): ClientOutput<"thread"> => {
483
- const handleReload = (messageId: string) => {
484
- const messageIndex = messages.findIndex((m) => m.id === messageId);
485
- if (messageIndex === -1) return;
486
-
487
- const parentId = messageIndex > 0 ? messages[messageIndex - 1]!.id : null;
488
- queue?.clear("reload");
489
- onReload?.(parentId);
490
- };
491
-
492
- const messageClients = tapClientLookup(
493
- () =>
494
- messages.map((msg, index) => {
495
- const props: MessageClientProps = {
496
- message: msg,
497
- index,
498
- onReload: () => handleReload(msg.id),
499
- queue,
500
- };
501
- if (onEdit) props.onEdit = onEdit;
502
- return withKey(msg.id, MessageClient(props));
503
- }),
504
- [messages, onEdit, queue],
505
- );
506
-
507
- const handleCancelRun = () => {
508
- queue?.clear("cancel-run");
509
- onCancel?.();
510
- };
511
-
512
- const handleSendNew = (message: AppendMessage) => {
513
- onNew?.(message);
514
- };
515
-
516
- const composerClient = tapClientResource(
517
- ComposerClientResource({
518
- type: "thread",
519
- isEditing: true,
520
- canCancel: isRunning,
521
- isSendDisabled,
522
- onCancel: handleCancelRun,
523
- onSend: handleSendNew,
524
- queue,
442
+ export const ExternalThread = resource(function ExternalThread({
443
+ messages,
444
+ isRunning = false,
445
+ isSendDisabled = false,
446
+ onNew,
447
+ onEdit,
448
+ onReload,
449
+ onStartRun,
450
+ onCancel,
451
+ queue,
452
+ }: ExternalThreadProps): ClientOutput<"thread"> {
453
+ const handleReload = (messageId: string) => {
454
+ const messageIndex = messages.findIndex((m) => m.id === messageId);
455
+ if (messageIndex === -1) return;
456
+
457
+ const parentId = messageIndex > 0 ? messages[messageIndex - 1]!.id : null;
458
+ queue?.clear("reload");
459
+ onReload?.(parentId);
460
+ };
461
+
462
+ const messageClients = useClientLookup(
463
+ () =>
464
+ messages.map((msg, index) => {
465
+ const props: MessageClientProps = {
466
+ message: msg,
467
+ index,
468
+ onReload: () => handleReload(msg.id),
469
+ queue,
470
+ };
471
+ if (onEdit) props.onEdit = onEdit;
472
+ return withKey(msg.id, MessageClient(props));
525
473
  }),
526
- );
527
-
528
- const hasQueue = !!queue;
529
- const state = tapMemo(() => {
530
- const messageStates = messageClients.state.map((s, idx, arr) => ({
531
- ...s,
532
- isLast: idx === arr.length - 1,
533
- }));
534
-
535
- return {
536
- isEmpty: messages.length === 0,
537
- isDisabled: false,
538
- isLoading: false,
539
- isRunning,
540
- capabilities: {
541
- edit: false,
542
- reload: false,
543
- cancel: isRunning,
544
- speech: false,
545
- attachments: false,
546
- feedback: false,
547
- voice: false,
548
- switchToBranch: false,
549
- switchBranchDuringRun: false,
550
- unstable_copy: false,
551
- dictation: false,
552
- queue: hasQueue,
553
- },
554
- messages: messageStates,
555
- state: {},
556
- suggestions: [],
557
- extras: undefined,
558
- speech: undefined,
559
- voice: undefined,
560
- composer: composerClient.state,
561
- };
562
- }, [
563
- messages,
564
- isRunning,
565
- hasQueue,
566
- messageClients.state,
567
- composerClient.state,
568
- ]);
474
+ [messages, onEdit, queue],
475
+ );
476
+
477
+ const handleCancelRun = () => {
478
+ queue?.clear("cancel-run");
479
+ onCancel?.();
480
+ };
481
+
482
+ const handleSendNew = (message: AppendMessage) => {
483
+ onNew?.(message);
484
+ };
485
+
486
+ const composerClient = useClientResource(
487
+ ComposerClientResource({
488
+ type: "thread",
489
+ isEditing: true,
490
+ canCancel: isRunning,
491
+ isSendDisabled,
492
+ onCancel: handleCancelRun,
493
+ onSend: handleSendNew,
494
+ queue,
495
+ }),
496
+ );
497
+
498
+ const hasQueue = !!queue;
499
+ const state = useMemo(() => {
500
+ const messageStates = messageClients.state.map((s, idx, arr) => ({
501
+ ...s,
502
+ isLast: idx === arr.length - 1,
503
+ }));
569
504
 
570
505
  return {
571
- getState: () => state,
572
- composer: () => composerClient.methods,
573
- append: (message) => {
574
- const appendMessage: AppendMessage =
575
- typeof message === "string"
576
- ? {
577
- createdAt: new Date(),
578
- parentId: messages.at(-1)?.id ?? null,
579
- sourceId: null,
580
- runConfig: {},
581
- role: "user",
582
- content: [{ type: "text", text: message }],
583
- attachments: [],
584
- metadata: { custom: {} },
585
- }
586
- : {
587
- createdAt: message.createdAt ?? new Date(),
588
- parentId: message.parentId ?? messages.at(-1)?.id ?? null,
589
- sourceId: message.sourceId ?? null,
590
- role: message.role ?? "user",
591
- content: message.content,
592
- attachments: message.attachments ?? [],
593
- metadata: message.metadata ?? { custom: {} },
594
- runConfig: message.runConfig ?? {},
595
- startRun: message.startRun,
596
- };
597
- if (queue) {
598
- queue.enqueue(appendMessage, { steer: false });
599
- } else {
600
- onNew?.(appendMessage);
601
- }
602
- },
603
- startRun: () => {
604
- onStartRun?.();
605
- },
606
- resumeRun: () => {},
607
- cancelRun: handleCancelRun,
608
- getModelContext: () => ({ tools: {}, config: {} }),
609
- export: () => ({ messages: [] }),
610
- import: () => {},
611
- reset: () => {},
612
- message: (selector) => {
613
- if ("id" in selector) {
614
- return messageClients.get({ key: selector.id });
615
- }
616
- return messageClients.get(selector);
506
+ isEmpty: messages.length === 0,
507
+ isDisabled: false,
508
+ isLoading: false,
509
+ isRunning,
510
+ capabilities: {
511
+ edit: false,
512
+ reload: false,
513
+ cancel: isRunning,
514
+ speech: false,
515
+ attachments: false,
516
+ feedback: false,
517
+ voice: false,
518
+ switchToBranch: false,
519
+ switchBranchDuringRun: false,
520
+ unstable_copy: false,
521
+ dictation: false,
522
+ queue: hasQueue,
617
523
  },
618
- stopSpeaking: () => {},
619
- connectVoice: () => {},
620
- disconnectVoice: () => {},
621
- getVoiceVolume: () => 0,
622
- subscribeVoiceVolume: () => () => {},
623
- muteVoice: () => {},
624
- unmuteVoice: () => {},
524
+ messages: messageStates,
525
+ state: {},
526
+ suggestions: [],
527
+ extras: undefined,
528
+ speech: undefined,
529
+ voice: undefined,
530
+ composer: composerClient.state,
625
531
  };
626
- },
627
- );
532
+ }, [
533
+ messages,
534
+ isRunning,
535
+ hasQueue,
536
+ messageClients.state,
537
+ composerClient.state,
538
+ ]);
539
+
540
+ return {
541
+ getState: () => state,
542
+ composer: () => composerClient.methods,
543
+ append: (message) => {
544
+ const appendMessage: AppendMessage =
545
+ typeof message === "string"
546
+ ? {
547
+ createdAt: new Date(),
548
+ parentId: messages.at(-1)?.id ?? null,
549
+ sourceId: null,
550
+ runConfig: {},
551
+ role: "user",
552
+ content: [{ type: "text", text: message }],
553
+ attachments: [],
554
+ metadata: { custom: {} },
555
+ }
556
+ : {
557
+ createdAt: message.createdAt ?? new Date(),
558
+ parentId: message.parentId ?? messages.at(-1)?.id ?? null,
559
+ sourceId: message.sourceId ?? null,
560
+ role: message.role ?? "user",
561
+ content: message.content,
562
+ attachments: message.attachments ?? [],
563
+ metadata: message.metadata ?? { custom: {} },
564
+ runConfig: message.runConfig ?? {},
565
+ startRun: message.startRun,
566
+ };
567
+ if (queue) {
568
+ queue.enqueue(appendMessage, { steer: false });
569
+ } else {
570
+ onNew?.(appendMessage);
571
+ }
572
+ },
573
+ startRun: () => {
574
+ onStartRun?.();
575
+ },
576
+ resumeRun: () => {},
577
+ cancelRun: handleCancelRun,
578
+ getModelContext: () => ({ tools: {}, config: {} }),
579
+ export: () => ({ messages: [] }),
580
+ import: () => {},
581
+ reset: () => {},
582
+ message: (selector) => {
583
+ if ("id" in selector) {
584
+ return messageClients.get({ key: selector.id });
585
+ }
586
+ return messageClients.get(selector);
587
+ },
588
+ stopSpeaking: () => {},
589
+ connectVoice: () => {},
590
+ disconnectVoice: () => {},
591
+ getVoiceVolume: () => 0,
592
+ subscribeVoiceVolume: () => () => {},
593
+ muteVoice: () => {},
594
+ unmuteVoice: () => {},
595
+ };
596
+ });
628
597
 
629
598
  attachTransformScopes(ExternalThread, (scopes, parent) => {
630
599
  if (!scopes.threads && parent.threads.source === null) {