@assistant-ui/core 0.2.18 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -1
- package/dist/internal.js +2 -1
- package/dist/model-context/interactable-composer-metadata.d.ts +105 -0
- package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -0
- package/dist/model-context/interactable-composer-metadata.js +259 -0
- package/dist/model-context/interactable-composer-metadata.js.map +1 -0
- package/dist/model-context/registry.d.ts.map +1 -1
- package/dist/model-context/registry.js +1 -0
- package/dist/model-context/registry.js.map +1 -1
- package/dist/model-context/types.d.ts +6 -0
- package/dist/model-context/types.d.ts.map +1 -1
- package/dist/model-context/types.js +4 -0
- package/dist/model-context/types.js.map +1 -1
- package/dist/react/AssistantProvider.d.ts +1 -1
- package/dist/react/AssistantProvider.js +1 -1
- package/dist/react/AssistantRuntimeProvider.d.ts +1 -1
- package/dist/react/RuntimeAdapter.js +1 -1
- package/dist/react/client/Interactables.d.ts +8 -2
- package/dist/react/client/Interactables.d.ts.map +1 -1
- package/dist/react/client/Interactables.js +190 -94
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/Tools.js +1 -1
- package/dist/react/client/interactable-model-context.d.ts +9 -6
- package/dist/react/client/interactable-model-context.d.ts.map +1 -1
- package/dist/react/client/interactable-model-context.js +154 -32
- package/dist/react/client/interactable-model-context.js.map +1 -1
- package/dist/react/index.d.ts +15 -6
- package/dist/react/index.js +12 -5
- package/dist/react/interactables-legacy/Interactables.d.ts +12 -0
- package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -0
- package/dist/react/interactables-legacy/Interactables.js +264 -0
- package/dist/react/interactables-legacy/Interactables.js.map +1 -0
- package/dist/react/interactables-legacy/interactable-model-context.d.ts +12 -0
- package/dist/react/interactables-legacy/interactable-model-context.d.ts.map +1 -0
- package/dist/react/interactables-legacy/interactable-model-context.js +54 -0
- package/dist/react/interactables-legacy/interactable-model-context.js.map +1 -0
- package/dist/react/interactables-legacy/scopes.d.ts +58 -0
- package/dist/react/interactables-legacy/scopes.d.ts.map +1 -0
- package/dist/react/interactables-legacy/scopes.js +0 -0
- package/dist/react/interactables-legacy/useAssistantInteractable.d.ts +31 -0
- package/dist/react/interactables-legacy/useAssistantInteractable.d.ts.map +1 -0
- package/dist/react/{model-context → interactables-legacy}/useAssistantInteractable.js +6 -2
- package/dist/react/interactables-legacy/useAssistantInteractable.js.map +1 -0
- package/dist/react/interactables-legacy/useInteractableState.d.ts +21 -0
- package/dist/react/interactables-legacy/useInteractableState.d.ts.map +1 -0
- package/dist/react/interactables-legacy/useInteractableState.js +84 -0
- package/dist/react/interactables-legacy/useInteractableState.js.map +1 -0
- package/dist/react/model-context/interactableTool.d.ts +53 -0
- package/dist/react/model-context/interactableTool.d.ts.map +1 -0
- package/dist/react/model-context/interactableTool.js +188 -0
- package/dist/react/model-context/interactableTool.js.map +1 -0
- package/dist/react/model-context/useAssistantContext.js +1 -1
- package/dist/react/model-context/useAssistantDataUI.js +1 -1
- package/dist/react/model-context/useAssistantInstructions.js +1 -1
- package/dist/react/model-context/useAssistantTool.js +1 -1
- package/dist/react/model-context/useAssistantToolUI.js +1 -1
- package/dist/react/model-context/useAuiToolOverrides.js +1 -1
- package/dist/react/model-context/useInteractable.d.ts +73 -0
- package/dist/react/model-context/useInteractable.d.ts.map +1 -0
- package/dist/react/model-context/useInteractable.js +100 -0
- package/dist/react/model-context/useInteractable.js.map +1 -0
- package/dist/react/model-context/useInteractableState.d.ts +8 -5
- package/dist/react/model-context/useInteractableState.d.ts.map +1 -1
- package/dist/react/model-context/useInteractableState.js +48 -48
- package/dist/react/model-context/useInteractableState.js.map +1 -1
- package/dist/react/model-context/useInteractableVersions.d.ts +19 -0
- package/dist/react/model-context/useInteractableVersions.d.ts.map +1 -0
- package/dist/react/model-context/useInteractableVersions.js +51 -0
- package/dist/react/model-context/useInteractableVersions.js.map +1 -0
- package/dist/react/primitive-hooks/useThreadMessageIds.d.ts +15 -0
- package/dist/react/primitive-hooks/useThreadMessageIds.d.ts.map +1 -0
- package/dist/react/primitive-hooks/useThreadMessageIds.js +26 -0
- package/dist/react/primitive-hooks/useThreadMessageIds.js.map +1 -0
- package/dist/react/primitive-hooks/useVoice.js +1 -1
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +1 -1
- package/dist/react/primitives/composer/ComposerAttachments.js +1 -1
- package/dist/react/primitives/composer/ComposerQueue.js +1 -1
- package/dist/react/primitives/generativeUI/GenerativeUI.d.ts.map +1 -1
- package/dist/react/primitives/generativeUI/GenerativeUI.js +2 -2
- package/dist/react/primitives/generativeUI/GenerativeUI.js.map +1 -1
- package/dist/react/primitives/message/MessageAttachments.js +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js +1 -1
- package/dist/react/primitives/message/MessageParts.js +1 -1
- package/dist/react/primitives/message/MessageQuote.js +1 -1
- package/dist/react/primitives/part/PartMessages.d.ts +5 -2
- package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
- package/dist/react/primitives/part/PartMessages.js +8 -8
- package/dist/react/primitives/part/PartMessages.js.map +1 -1
- package/dist/react/primitives/thread/ThreadMessages.d.ts +31 -1
- package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/react/primitives/thread/ThreadMessages.js +64 -2
- package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/react/primitives/thread/ThreadSuggestions.js +1 -1
- package/dist/react/primitives/threadList/ThreadListItems.js +1 -1
- package/dist/react/providers/MessageByIdProvider.d.ts +9 -0
- package/dist/react/providers/MessageByIdProvider.d.ts.map +1 -0
- package/dist/react/providers/MessageByIdProvider.js +57 -0
- package/dist/react/providers/MessageByIdProvider.js.map +1 -0
- package/dist/react/providers/ReadonlyThreadProvider.js +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +3 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +16 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +1 -1
- package/dist/react/runtimes/cloud/auiV0.js +1 -1
- package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +1 -1
- package/dist/react/runtimes/createRuntimeExtras.d.ts +20 -0
- package/dist/react/runtimes/createRuntimeExtras.d.ts.map +1 -0
- package/dist/react/runtimes/createRuntimeExtras.js +43 -0
- package/dist/react/runtimes/createRuntimeExtras.js.map +1 -0
- package/dist/react/runtimes/useLocalRuntime.js +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useRemoteThreadListRuntime.js +6 -2
- package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
- package/dist/react/runtimes/useStreamingTiming.d.ts +26 -0
- package/dist/react/runtimes/useStreamingTiming.d.ts.map +1 -0
- package/dist/react/runtimes/useStreamingTiming.js +43 -0
- package/dist/react/runtimes/useStreamingTiming.js.map +1 -0
- package/dist/react/types/scopes/interactables.d.ts +40 -24
- package/dist/react/types/scopes/interactables.d.ts.map +1 -1
- package/dist/react/types/store-augmentation.d.ts +3 -1
- package/dist/react/types/store-augmentation.d.ts.map +1 -1
- package/dist/react/utils/useJSONEqual.d.ts +11 -0
- package/dist/react/utils/useJSONEqual.d.ts.map +1 -0
- package/dist/react/utils/useJSONEqual.js +30 -0
- package/dist/react/utils/useJSONEqual.js.map +1 -0
- package/dist/runtime/base/base-composer-runtime-core.d.ts +5 -0
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +13 -0
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js +6 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js +4 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/utils/message-repository.d.ts +1 -0
- package/dist/runtime/utils/message-repository.d.ts.map +1 -1
- package/dist/runtime/utils/message-repository.js +6 -3
- package/dist/runtime/utils/message-repository.js.map +1 -1
- package/dist/runtime/utils/streaming-timing.d.ts +59 -0
- package/dist/runtime/utils/streaming-timing.d.ts.map +1 -0
- package/dist/runtime/utils/streaming-timing.js +76 -0
- package/dist/runtime/utils/streaming-timing.js.map +1 -0
- package/dist/runtime/utils/thread-message-like.js +2 -1
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts +26 -1
- package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +9 -0
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js +19 -0
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +12 -0
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/clients/suggestions.js +1 -1
- package/dist/store/clients/thread-message-client.js +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +1 -1
- package/dist/store/runtime-clients/message-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
- package/dist/tests/remote-thread-list-test-helpers.d.ts +1 -1
- package/dist/tests/remote-thread-list-test-helpers.d.ts.map +1 -1
- package/dist/tests/remote-thread-list-test-helpers.js +3 -2
- package/dist/tests/remote-thread-list-test-helpers.js.map +1 -1
- package/package.json +9 -9
- package/src/index.ts +17 -0
- package/src/internal.ts +7 -0
- package/src/model-context/interactable-composer-metadata.test.ts +550 -0
- package/src/model-context/interactable-composer-metadata.ts +395 -0
- package/src/model-context/registry.ts +5 -0
- package/src/model-context/types.ts +12 -0
- package/src/react/client/Interactables.test.ts +358 -0
- package/src/react/client/Interactables.ts +339 -86
- package/src/react/client/interactable-model-context.test.ts +418 -0
- package/src/react/client/interactable-model-context.ts +252 -58
- package/src/react/index.ts +72 -3
- package/src/react/interactables-legacy/Interactables.ts +332 -0
- package/src/react/interactables-legacy/interactable-model-context.test.ts +54 -0
- package/src/react/interactables-legacy/interactable-model-context.ts +80 -0
- package/src/react/interactables-legacy/scopes.ts +66 -0
- package/src/react/{model-context → interactables-legacy}/useAssistantInteractable.ts +10 -1
- package/src/react/interactables-legacy/useInteractableState.ts +67 -0
- package/src/react/model-context/interactableTool.tsx +175 -0
- package/src/react/model-context/useInteractable.ts +233 -0
- package/src/react/model-context/useInteractableState.ts +31 -23
- package/src/react/model-context/useInteractableVersions.ts +48 -0
- package/src/react/primitive-hooks/useThreadMessageIds.ts +27 -0
- package/src/react/primitives/generativeUI/GenerativeUI.tsx +5 -1
- package/src/react/primitives/part/PartMessages.test.tsx +17 -0
- package/src/react/primitives/part/PartMessages.tsx +13 -8
- package/src/react/primitives/thread/ThreadMessages.tsx +67 -0
- package/src/react/providers/MessageByIdProvider.tsx +23 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +21 -1
- package/src/react/runtimes/createRuntimeExtras.test.ts +58 -0
- package/src/react/runtimes/createRuntimeExtras.ts +68 -0
- package/src/react/runtimes/createRuntimeExtras.use.test.ts +50 -0
- package/src/react/runtimes/useRemoteThreadListRuntime.ts +13 -1
- package/src/react/runtimes/useStreamingTiming.ts +68 -0
- package/src/react/types/scopes/interactables.ts +45 -23
- package/src/react/types/store-augmentation.ts +4 -2
- package/src/react/utils/useJSONEqual.ts +20 -0
- package/src/runtime/base/base-composer-runtime-core.ts +13 -0
- package/src/runtime/base/default-edit-composer-runtime-core.ts +18 -1
- package/src/runtime/base/default-thread-composer-runtime-core.ts +11 -1
- package/src/runtime/utils/message-repository.ts +11 -7
- package/src/runtime/utils/streaming-timing.test.ts +301 -0
- package/src/runtime/utils/streaming-timing.ts +161 -0
- package/src/runtime/utils/thread-message-like.ts +2 -1
- package/src/runtimes/external-store/external-store-adapter.ts +28 -0
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +33 -1
- package/src/runtimes/local/local-thread-runtime-core.ts +4 -1
- package/src/runtimes/remote-thread-list/types.ts +13 -0
- package/src/tests/MessageRepository.test.ts +2 -0
- package/src/tests/composer-can-send.test.ts +1 -0
- package/src/tests/default-edit-composer-runtime-core.test.ts +78 -1
- package/src/tests/default-thread-composer-runtime-core.test.ts +82 -0
- package/src/tests/external-store-thread-runtime-core.test.ts +337 -0
- package/src/tests/groupParts.test.ts +32 -0
- package/src/tests/remote-thread-list-reactive-threadId.test.ts +90 -0
- package/src/tests/remote-thread-list-test-helpers.ts +2 -1
- package/src/tests/thread-message-like.test.ts +50 -0
- package/dist/react/model-context/useAssistantInteractable.d.ts +0 -22
- package/dist/react/model-context/useAssistantInteractable.d.ts.map +0 -1
- package/dist/react/model-context/useAssistantInteractable.js.map +0 -1
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "react";
|
|
8
8
|
import { RenderChildrenWithAccessor, useAuiState } from "@assistant-ui/store";
|
|
9
9
|
import { MessageByIndexProvider } from "../../providers/MessageByIndexProvider";
|
|
10
|
+
import { MessageByIdProvider } from "../../providers/MessageByIdProvider";
|
|
10
11
|
import type { MessageState } from "../../../store";
|
|
11
12
|
|
|
12
13
|
type MessagesComponentConfig =
|
|
@@ -79,6 +80,20 @@ const isComponentsSame = (
|
|
|
79
80
|
|
|
80
81
|
const DEFAULT_SYSTEM_MESSAGE = () => null;
|
|
81
82
|
|
|
83
|
+
const messageIdSetCache = new WeakMap<
|
|
84
|
+
readonly MessageState[],
|
|
85
|
+
ReadonlySet<string>
|
|
86
|
+
>();
|
|
87
|
+
|
|
88
|
+
const hasMessageId = (messages: readonly MessageState[], messageId: string) => {
|
|
89
|
+
let ids = messageIdSetCache.get(messages);
|
|
90
|
+
if (!ids) {
|
|
91
|
+
ids = new Set(messages.map((m) => m.id));
|
|
92
|
+
messageIdSetCache.set(messages, ids);
|
|
93
|
+
}
|
|
94
|
+
return ids.has(messageId);
|
|
95
|
+
};
|
|
96
|
+
|
|
82
97
|
const getComponent = (
|
|
83
98
|
components: MessagesComponentConfig,
|
|
84
99
|
role: MessageState["role"],
|
|
@@ -170,6 +185,58 @@ export const ThreadPrimitiveMessageByIndex: FC<ThreadPrimitiveMessageByIndex.Pro
|
|
|
170
185
|
|
|
171
186
|
ThreadPrimitiveMessageByIndex.displayName = "ThreadPrimitive.MessageByIndex";
|
|
172
187
|
|
|
188
|
+
export namespace ThreadPrimitiveUnstable_MessageById {
|
|
189
|
+
export type Props = {
|
|
190
|
+
messageId: string;
|
|
191
|
+
components: MessagesComponentConfig;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Renders the message with the given id in the current thread.
|
|
197
|
+
*
|
|
198
|
+
* Unlike {@link ThreadPrimitiveMessageByIndex}, this keys off the message id,
|
|
199
|
+
* so it stays attached to the same message across reordering and windowing -
|
|
200
|
+
* the shape needed to drive a virtualized or custom message list together with
|
|
201
|
+
* `unstable_useThreadMessageIds`. A missing or removed id renders `null` rather
|
|
202
|
+
* than throwing.
|
|
203
|
+
*
|
|
204
|
+
* @deprecated Unstable / Experimental - may change in any release.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```tsx
|
|
208
|
+
* const messageIds = unstable_useThreadMessageIds();
|
|
209
|
+
* return messageIds.map((messageId) => (
|
|
210
|
+
* <ThreadPrimitive.Unstable_MessageById
|
|
211
|
+
* key={messageId}
|
|
212
|
+
* messageId={messageId}
|
|
213
|
+
* components={MESSAGE_COMPONENTS}
|
|
214
|
+
* />
|
|
215
|
+
* ));
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
export const ThreadPrimitiveUnstable_MessageById: FC<ThreadPrimitiveUnstable_MessageById.Props> =
|
|
219
|
+
memo(
|
|
220
|
+
({ messageId, components }) => {
|
|
221
|
+
const exists = useAuiState((s) =>
|
|
222
|
+
hasMessageId(s.thread.messages, messageId),
|
|
223
|
+
);
|
|
224
|
+
if (!exists) return null;
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<MessageByIdProvider id={messageId}>
|
|
228
|
+
<ThreadMessageComponent components={components} />
|
|
229
|
+
</MessageByIdProvider>
|
|
230
|
+
);
|
|
231
|
+
},
|
|
232
|
+
(prev, next) =>
|
|
233
|
+
prev.messageId === next.messageId &&
|
|
234
|
+
isComponentsSame(prev.components, next.components),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
ThreadPrimitiveUnstable_MessageById.displayName =
|
|
238
|
+
"ThreadPrimitive.Unstable_MessageById";
|
|
239
|
+
|
|
173
240
|
const ThreadPrimitiveMessagesInner: FC<{
|
|
174
241
|
children: (value: { message: MessageState }) => ReactNode;
|
|
175
242
|
}> = ({ children }) => {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FC, PropsWithChildren } from "react";
|
|
2
|
+
import { useAui, AuiProvider, Derived } from "@assistant-ui/store";
|
|
3
|
+
|
|
4
|
+
export const MessageByIdProvider: FC<
|
|
5
|
+
PropsWithChildren<{
|
|
6
|
+
id: string;
|
|
7
|
+
}>
|
|
8
|
+
> = ({ id, children }) => {
|
|
9
|
+
const aui = useAui({
|
|
10
|
+
message: Derived({
|
|
11
|
+
source: "thread",
|
|
12
|
+
query: { type: "id", id },
|
|
13
|
+
get: (aui) => aui.thread().message({ id }),
|
|
14
|
+
}),
|
|
15
|
+
composer: Derived({
|
|
16
|
+
source: "message",
|
|
17
|
+
query: {},
|
|
18
|
+
get: (aui) => aui.thread().message({ id }).composer(),
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return <AuiProvider value={aui}>{children}</AuiProvider>;
|
|
23
|
+
};
|
|
@@ -163,7 +163,10 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
163
163
|
super();
|
|
164
164
|
this.contextProvider = contextProvider;
|
|
165
165
|
|
|
166
|
-
this._state.subscribe(() =>
|
|
166
|
+
this._state.subscribe(() => {
|
|
167
|
+
this._notifySubscribers();
|
|
168
|
+
this._notifyThreadIdChange();
|
|
169
|
+
});
|
|
167
170
|
this._hookManager = new RemoteThreadListHookInstanceManager(
|
|
168
171
|
options.runtimeHook,
|
|
169
172
|
this,
|
|
@@ -253,6 +256,22 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
253
256
|
return this._mainThreadId;
|
|
254
257
|
}
|
|
255
258
|
|
|
259
|
+
// The settled remote ID of the active thread, or undefined while it is still
|
|
260
|
+
// a new/optimistic thread. This is the value surfaced to `onThreadIdChange`.
|
|
261
|
+
private get _mainThreadRemoteId(): string | undefined {
|
|
262
|
+
if (this._mainThreadId === undefined) return undefined;
|
|
263
|
+
return getThreadData(this._state.value, this._mainThreadId)?.remoteId;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private _lastNotifiedThreadId: string | undefined = undefined;
|
|
267
|
+
|
|
268
|
+
private _notifyThreadIdChange() {
|
|
269
|
+
const threadId = this._mainThreadRemoteId;
|
|
270
|
+
if (this._lastNotifiedThreadId === threadId) return;
|
|
271
|
+
this._lastNotifiedThreadId = threadId;
|
|
272
|
+
this._options.onThreadIdChange?.(threadId);
|
|
273
|
+
}
|
|
274
|
+
|
|
256
275
|
public getMainThreadRuntimeCore() {
|
|
257
276
|
const result = this._hookManager.getThreadRuntimeCore(this._mainThreadId);
|
|
258
277
|
if (!result) return EMPTY_THREAD_CORE;
|
|
@@ -351,6 +370,7 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
351
370
|
this._mainThreadId = data.id;
|
|
352
371
|
|
|
353
372
|
this._notifySubscribers();
|
|
373
|
+
this._notifyThreadIdChange();
|
|
354
374
|
}
|
|
355
375
|
|
|
356
376
|
public async switchToNewThread(): Promise<void> {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import type { AssistantClient } from "@assistant-ui/store";
|
|
3
|
+
import { createRuntimeExtras } from "./createRuntimeExtras";
|
|
4
|
+
|
|
5
|
+
type Extras = { value: number; greet: () => string };
|
|
6
|
+
|
|
7
|
+
const clientWith = (extras: unknown) =>
|
|
8
|
+
({
|
|
9
|
+
thread: () => ({ getState: () => ({ extras }) }),
|
|
10
|
+
}) as unknown as AssistantClient;
|
|
11
|
+
|
|
12
|
+
describe("createRuntimeExtras", () => {
|
|
13
|
+
it("brands a value so its own guards recognize it", () => {
|
|
14
|
+
const channel = createRuntimeExtras<Extras>("useTestRuntime");
|
|
15
|
+
const branded = channel.provide({ value: 1, greet: () => "hi" });
|
|
16
|
+
|
|
17
|
+
expect(channel.is(branded)).toBe(true);
|
|
18
|
+
expect(channel.tryGet(branded)).toBe(branded);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns the same object reference from provide (stable identity)", () => {
|
|
22
|
+
const channel = createRuntimeExtras<Extras>("useTestRuntime");
|
|
23
|
+
const value = { value: 1, greet: () => "hi" };
|
|
24
|
+
expect(channel.provide(value)).toBe(value);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("keeps the brand non-enumerable so it stays out of serialization", () => {
|
|
28
|
+
const channel = createRuntimeExtras<Extras>("useTestRuntime");
|
|
29
|
+
const branded = channel.provide({ value: 1, greet: () => "hi" });
|
|
30
|
+
|
|
31
|
+
expect(Object.keys(branded)).toEqual(["value", "greet"]);
|
|
32
|
+
expect(JSON.parse(JSON.stringify(branded))).toEqual({ value: 1 });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("rejects unbranded and foreign values", () => {
|
|
36
|
+
const channel = createRuntimeExtras<Extras>("useTestRuntime");
|
|
37
|
+
const other = createRuntimeExtras<Extras>("useOtherRuntime");
|
|
38
|
+
const foreign = other.provide({ value: 2, greet: () => "yo" });
|
|
39
|
+
|
|
40
|
+
expect(channel.is(undefined)).toBe(false);
|
|
41
|
+
expect(channel.is({ value: 1 })).toBe(false);
|
|
42
|
+
expect(channel.is(foreign)).toBe(false);
|
|
43
|
+
expect(channel.tryGet(foreign)).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("get reads the current snapshot off an AssistantClient", () => {
|
|
47
|
+
const channel = createRuntimeExtras<Extras>("useTestRuntime");
|
|
48
|
+
const branded = channel.provide({ value: 7, greet: () => "hi" });
|
|
49
|
+
|
|
50
|
+
expect(channel.get(clientWith(branded)).value).toBe(7);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("get throws with the runtime name when the thread is not backed by it", () => {
|
|
54
|
+
const channel = createRuntimeExtras<Extras>("useTestRuntime");
|
|
55
|
+
|
|
56
|
+
expect(() => channel.get(clientWith(undefined))).toThrow("useTestRuntime");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
4
|
+
import type { AssistantClient } from "@assistant-ui/store";
|
|
5
|
+
|
|
6
|
+
/** @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice. */
|
|
7
|
+
export type RuntimeExtras<T extends object> = {
|
|
8
|
+
provide: (value: T) => T;
|
|
9
|
+
is: (extras: unknown) => extras is T;
|
|
10
|
+
tryGet: (extras: unknown) => T | undefined;
|
|
11
|
+
get: (client: AssistantClient) => T;
|
|
12
|
+
use: {
|
|
13
|
+
(): T;
|
|
14
|
+
<S>(select: (extras: T) => S): S;
|
|
15
|
+
<S>(select: (extras: T) => S, fallback: S): S;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** @deprecated Internal API for external-store adapter authors. Not part of the public API; may change or be removed without notice. */
|
|
20
|
+
export const createRuntimeExtras = <T extends object>(
|
|
21
|
+
runtimeName: string,
|
|
22
|
+
): RuntimeExtras<T> => {
|
|
23
|
+
const brand = Symbol(`${runtimeName} extras`);
|
|
24
|
+
|
|
25
|
+
const is = (extras: unknown): extras is T =>
|
|
26
|
+
typeof extras === "object" && extras !== null && brand in extras;
|
|
27
|
+
|
|
28
|
+
const assert = (extras: unknown): T => {
|
|
29
|
+
if (!is(extras))
|
|
30
|
+
throw new Error(
|
|
31
|
+
`The current thread is not backed by the ${runtimeName} runtime.`,
|
|
32
|
+
);
|
|
33
|
+
return extras;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const provide = (value: T): T => {
|
|
37
|
+
Object.defineProperty(value, brand, {
|
|
38
|
+
value: true,
|
|
39
|
+
enumerable: false,
|
|
40
|
+
configurable: true,
|
|
41
|
+
});
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const tryGet = (extras: unknown): T | undefined =>
|
|
46
|
+
is(extras) ? extras : undefined;
|
|
47
|
+
|
|
48
|
+
const get = (client: AssistantClient): T =>
|
|
49
|
+
assert(client.thread().getState().extras);
|
|
50
|
+
|
|
51
|
+
function use<S>(
|
|
52
|
+
select?: (extras: T) => S,
|
|
53
|
+
...rest: [fallback: S] | []
|
|
54
|
+
): S | T {
|
|
55
|
+
// Detect a provided fallback by arity, not value: callers pass an explicit
|
|
56
|
+
// `undefined` fallback, which must return `undefined` rather than throw.
|
|
57
|
+
const hasFallback = rest.length > 0;
|
|
58
|
+
const fallback = rest[0] as S;
|
|
59
|
+
return useAuiState((s) => {
|
|
60
|
+
const extras = s.thread.extras;
|
|
61
|
+
if (is(extras)) return select ? select(extras) : extras;
|
|
62
|
+
if (hasFallback) return fallback;
|
|
63
|
+
return assert(extras);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { provide, is, tryGet, get, use: use as RuntimeExtras<T>["use"] };
|
|
68
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const { mockUseAuiState } = vi.hoisted(() => ({ mockUseAuiState: vi.fn() }));
|
|
4
|
+
|
|
5
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
6
|
+
...(await importOriginal<typeof import("@assistant-ui/store")>()),
|
|
7
|
+
useAuiState: ((selector: (s: unknown) => unknown) =>
|
|
8
|
+
mockUseAuiState(
|
|
9
|
+
selector,
|
|
10
|
+
)) as typeof import("@assistant-ui/store").useAuiState,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import { createRuntimeExtras } from "./createRuntimeExtras";
|
|
14
|
+
|
|
15
|
+
type Extras = { value: number };
|
|
16
|
+
|
|
17
|
+
const against = (extras: unknown) =>
|
|
18
|
+
mockUseAuiState.mockImplementationOnce((selector: (s: unknown) => unknown) =>
|
|
19
|
+
selector({ thread: { extras } }),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
describe("createRuntimeExtras.use", () => {
|
|
23
|
+
const channel = createRuntimeExtras<Extras>("useTestRuntime");
|
|
24
|
+
|
|
25
|
+
it("projects the extras when the thread is backed by the runtime", () => {
|
|
26
|
+
against(channel.provide({ value: 5 }));
|
|
27
|
+
expect(channel.use((e) => e.value, 0)).toBe(5);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("returns the whole extras when called without a selector", () => {
|
|
31
|
+
const extras = channel.provide({ value: 9 });
|
|
32
|
+
against(extras);
|
|
33
|
+
expect(channel.use()).toBe(extras);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("returns the fallback when the thread is not backed by the runtime", () => {
|
|
37
|
+
against(undefined);
|
|
38
|
+
expect(channel.use((e) => e.value, 0)).toBe(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("returns an explicit undefined fallback instead of throwing", () => {
|
|
42
|
+
against(undefined);
|
|
43
|
+
expect(channel.use((e) => e.value, undefined)).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("throws when no fallback is given and the runtime is absent", () => {
|
|
47
|
+
against(undefined);
|
|
48
|
+
expect(() => channel.use((e) => e.value)).toThrow("useTestRuntime");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffectEvent,
|
|
8
|
+
} from "react";
|
|
2
9
|
import { BaseAssistantRuntimeCore } from "../../runtime/base/base-assistant-runtime-core";
|
|
3
10
|
import { AssistantRuntimeImpl } from "../../runtime/api/assistant-runtime";
|
|
4
11
|
import type { RemoteThreadListOptions } from "../../runtimes/remote-thread-list/types";
|
|
@@ -51,12 +58,17 @@ export const useRemoteThreadListRuntime = (
|
|
|
51
58
|
return runtimeHookRef.current();
|
|
52
59
|
}, []);
|
|
53
60
|
|
|
61
|
+
const onThreadIdChange = useEffectEvent((threadId: string | undefined) => {
|
|
62
|
+
options.onThreadIdChange?.(threadId);
|
|
63
|
+
});
|
|
64
|
+
|
|
54
65
|
const stableOptions = useMemo<RemoteThreadListOptions>(
|
|
55
66
|
() => ({
|
|
56
67
|
adapter: options.adapter,
|
|
57
68
|
allowNesting: options.allowNesting,
|
|
58
69
|
initialThreadId: startThreadIdRef.current,
|
|
59
70
|
runtimeHook: stableRuntimeHook,
|
|
71
|
+
onThreadIdChange,
|
|
60
72
|
}),
|
|
61
73
|
[options.adapter, options.allowNesting, stableRuntimeHook],
|
|
62
74
|
);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import type { MessageTiming } from "../../types/message";
|
|
5
|
+
import {
|
|
6
|
+
stepStreamingTiming,
|
|
7
|
+
type StreamingTimingAccessors,
|
|
8
|
+
type StreamingTimingOptions,
|
|
9
|
+
type StreamingTimingState,
|
|
10
|
+
} from "../../runtime/utils/streaming-timing";
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
StreamingTimingAccessors,
|
|
14
|
+
StreamingTimingOptions,
|
|
15
|
+
StreamingTimingState,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Tracks per-message streaming timing client-side and returns finalized
|
|
20
|
+
* `MessageTiming` keyed by message id.
|
|
21
|
+
*
|
|
22
|
+
* Observes `isRunning` transitions and content growth through the provided
|
|
23
|
+
* `accessors`, which adapt the hook to a runtime's message shape. Timing is
|
|
24
|
+
* finalized when streaming ends; adapters thread the result into
|
|
25
|
+
* `useExternalMessageConverter` metadata as `messageTiming`.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const messageTiming = useStreamingTiming(messages, isRunning, {
|
|
30
|
+
* getAssistantMessageId: (msgs) => msgs.findLast((m) => m.role === "assistant")?.id,
|
|
31
|
+
* getTextLength: (msgs, id) => msgs.find((m) => m.id === id)?.content?.length ?? 0,
|
|
32
|
+
* getToolCallCount: (msgs, id) => msgs.find((m) => m.id === id)?.tool_calls?.length ?? 0,
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const useStreamingTiming = <TMessage>(
|
|
37
|
+
messages: readonly TMessage[],
|
|
38
|
+
isRunning: boolean,
|
|
39
|
+
accessors: StreamingTimingAccessors<TMessage>,
|
|
40
|
+
options?: StreamingTimingOptions,
|
|
41
|
+
): Record<string, MessageTiming> => {
|
|
42
|
+
const [timings, setTimings] = useState<Record<string, MessageTiming>>({});
|
|
43
|
+
const stateRef = useRef<StreamingTimingState | null>(null);
|
|
44
|
+
// Read the latest accessors/options from refs so the effect keeps the
|
|
45
|
+
// original `[messages, isRunning]` reactivity of the adapter hooks this
|
|
46
|
+
// replaces (a stable timings object while streaming, a single new reference
|
|
47
|
+
// at finalize).
|
|
48
|
+
const accessorsRef = useRef(accessors);
|
|
49
|
+
accessorsRef.current = accessors;
|
|
50
|
+
const optionsRef = useRef(options);
|
|
51
|
+
optionsRef.current = options;
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const result = stepStreamingTiming(
|
|
55
|
+
stateRef.current,
|
|
56
|
+
messages,
|
|
57
|
+
isRunning,
|
|
58
|
+
accessorsRef.current,
|
|
59
|
+
optionsRef.current,
|
|
60
|
+
);
|
|
61
|
+
stateRef.current = result.state;
|
|
62
|
+
if (Object.keys(result.timings).length > 0) {
|
|
63
|
+
setTimings((prev) => ({ ...prev, ...result.timings }));
|
|
64
|
+
}
|
|
65
|
+
}, [messages, isRunning]);
|
|
66
|
+
|
|
67
|
+
return timings;
|
|
68
|
+
};
|
|
@@ -1,66 +1,88 @@
|
|
|
1
1
|
import type { Tool } from "assistant-stream";
|
|
2
2
|
import type { Unsubscribe } from "../../..";
|
|
3
|
+
import type { ToolCallMessagePartComponent } from "../MessagePartComponentTypes";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Schema type matching Tool["parameters"] from assistant-stream.
|
|
6
7
|
* Accepts both StandardSchemaV1 and JSONSchema7.
|
|
7
8
|
*/
|
|
8
|
-
export type
|
|
9
|
+
export type Unstable_InteractableStateSchema = NonNullable<
|
|
9
10
|
Extract<Tool, { parameters: unknown }>["parameters"]
|
|
10
11
|
>;
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
type InteractableScope = "app" | "thread";
|
|
14
|
+
|
|
15
|
+
export type Unstable_InteractableDefinition = {
|
|
13
16
|
id: string;
|
|
14
17
|
name: string;
|
|
15
18
|
description: string;
|
|
16
|
-
stateSchema:
|
|
19
|
+
stateSchema: Unstable_InteractableStateSchema;
|
|
17
20
|
state: unknown;
|
|
18
|
-
|
|
21
|
+
initialState: unknown;
|
|
22
|
+
scope?: InteractableScope | undefined;
|
|
19
23
|
};
|
|
20
24
|
|
|
21
|
-
export type
|
|
25
|
+
export type Unstable_InteractableRegistration = {
|
|
22
26
|
id: string;
|
|
23
27
|
name: string;
|
|
24
28
|
description: string;
|
|
25
|
-
stateSchema:
|
|
29
|
+
stateSchema: Unstable_InteractableStateSchema;
|
|
26
30
|
initialState: unknown;
|
|
27
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Component installed as the tool UI for this interactable's `update_{name}`
|
|
33
|
+
* tool calls, so a model edit re-renders the interactable at the message
|
|
34
|
+
* that made it. Registered once per name; requires the `tools` scope.
|
|
35
|
+
*/
|
|
36
|
+
updateRender?: ToolCallMessagePartComponent | undefined;
|
|
28
37
|
};
|
|
29
38
|
|
|
30
|
-
export type
|
|
39
|
+
export type Unstable_InteractablePersistenceStatus = {
|
|
31
40
|
isPending: boolean;
|
|
32
41
|
error: unknown;
|
|
33
42
|
};
|
|
34
43
|
|
|
35
|
-
export type
|
|
44
|
+
export type Unstable_InteractablesState = {
|
|
36
45
|
/** Keyed by instance id */
|
|
37
|
-
definitions: Record<string,
|
|
46
|
+
definitions: Record<string, Unstable_InteractableDefinition>;
|
|
38
47
|
/** Per-id persistence sync status */
|
|
39
|
-
persistence: Record<string,
|
|
48
|
+
persistence: Record<string, Unstable_InteractablePersistenceStatus>;
|
|
40
49
|
};
|
|
41
50
|
|
|
42
|
-
export type
|
|
51
|
+
export type Unstable_InteractablePersistedState = Record<
|
|
43
52
|
string,
|
|
44
53
|
{ name: string; state: unknown }
|
|
45
54
|
>;
|
|
46
55
|
|
|
47
|
-
export type
|
|
48
|
-
save(state:
|
|
56
|
+
export type Unstable_InteractablePersistenceAdapter = {
|
|
57
|
+
save(state: Unstable_InteractablePersistedState): void | Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Restores previously saved state. Called when the adapter is attached;
|
|
60
|
+
* loaded state seeds app-scoped interactables that have not been locally
|
|
61
|
+
* edited yet (a local edit always wins over a slow load).
|
|
62
|
+
*/
|
|
63
|
+
load?():
|
|
64
|
+
| Unstable_InteractablePersistedState
|
|
65
|
+
| null
|
|
66
|
+
| undefined
|
|
67
|
+
| Promise<Unstable_InteractablePersistedState | null | undefined>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type Unstable_InteractablesConfig = {
|
|
71
|
+
persistence?: Unstable_InteractablePersistenceAdapter | undefined;
|
|
49
72
|
};
|
|
50
73
|
|
|
51
|
-
export type
|
|
52
|
-
getState():
|
|
53
|
-
register(def:
|
|
74
|
+
export type Unstable_InteractablesMethods = {
|
|
75
|
+
getState(): Unstable_InteractablesState;
|
|
76
|
+
register(def: Unstable_InteractableRegistration): Unsubscribe;
|
|
54
77
|
setState(id: string, updater: (prev: unknown) => unknown): void;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
importState(saved: InteractablePersistedState): void;
|
|
78
|
+
exportState(): Unstable_InteractablePersistedState;
|
|
79
|
+
importState(saved: Unstable_InteractablePersistedState): void;
|
|
58
80
|
setPersistenceAdapter(
|
|
59
|
-
adapter:
|
|
81
|
+
adapter: Unstable_InteractablePersistenceAdapter | undefined,
|
|
60
82
|
): void;
|
|
61
83
|
flush(): Promise<void>;
|
|
62
84
|
};
|
|
63
85
|
|
|
64
|
-
export type
|
|
65
|
-
methods:
|
|
86
|
+
export type Unstable_InteractablesClientSchema = {
|
|
87
|
+
methods: Unstable_InteractablesMethods;
|
|
66
88
|
};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { ToolsClientSchema } from "./scopes/tools";
|
|
2
2
|
import type { DataRenderersClientSchema } from "./scopes/dataRenderers";
|
|
3
|
-
import type { InteractablesClientSchema } from "
|
|
3
|
+
import type { InteractablesClientSchema as LegacyInteractablesClientSchema } from "../interactables-legacy/scopes";
|
|
4
|
+
import type { Unstable_InteractablesClientSchema } from "./scopes/interactables";
|
|
4
5
|
|
|
5
6
|
declare module "@assistant-ui/store" {
|
|
6
7
|
interface ScopeRegistry {
|
|
7
8
|
tools: ToolsClientSchema;
|
|
8
9
|
dataRenderers: DataRenderersClientSchema;
|
|
9
|
-
interactables:
|
|
10
|
+
interactables: LegacyInteractablesClientSchema;
|
|
11
|
+
unstable_interactables: Unstable_InteractablesClientSchema;
|
|
10
12
|
}
|
|
11
13
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
import { isJSONValueEqual } from "../../utils/json/is-json-equal";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Like `useShallow`, but with JSON deep-equality. Use when a selector derives an
|
|
6
|
+
* equal-but-fresh value on every store update — e.g. folding over
|
|
7
|
+
* `thread.messages`, whose identity changes on every streaming token — where a
|
|
8
|
+
* shallow compare would re-render regardless.
|
|
9
|
+
*/
|
|
10
|
+
export function useJSONEqual<S, U>(selector: (state: S) => U): (state: S) => U {
|
|
11
|
+
const prev = useRef<U | undefined>(undefined);
|
|
12
|
+
return (state) => {
|
|
13
|
+
const next = selector(state);
|
|
14
|
+
if (prev.current !== undefined && isJSONValueEqual(prev.current, next)) {
|
|
15
|
+
return prev.current;
|
|
16
|
+
}
|
|
17
|
+
prev.current = next;
|
|
18
|
+
return next;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -41,6 +41,19 @@ export abstract class BaseComposerRuntimeCore
|
|
|
41
41
|
protected abstract getAttachmentAdapter(): AttachmentAdapter | undefined;
|
|
42
42
|
protected abstract getDictationAdapter(): DictationAdapter | undefined;
|
|
43
43
|
|
|
44
|
+
protected enrichWithComposerMetadata<
|
|
45
|
+
T extends { metadata?: { custom?: Record<string, unknown> } },
|
|
46
|
+
>(message: T, composerMetadata: Record<string, unknown> | undefined): T {
|
|
47
|
+
if (!composerMetadata) return message;
|
|
48
|
+
return {
|
|
49
|
+
...message,
|
|
50
|
+
metadata: {
|
|
51
|
+
...message.metadata,
|
|
52
|
+
custom: { ...message.metadata?.custom, ...composerMetadata },
|
|
53
|
+
},
|
|
54
|
+
} as T;
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
public get attachmentAccept(): string {
|
|
45
58
|
return this.getAttachmentAdapter()?.accept ?? "*";
|
|
46
59
|
}
|
|
@@ -7,6 +7,7 @@ import type { DictationAdapter } from "../../adapters/speech";
|
|
|
7
7
|
import type { SendOptions } from "../interfaces/composer-runtime-core";
|
|
8
8
|
import type { ThreadRuntimeCore } from "../interfaces/thread-runtime-core";
|
|
9
9
|
import { BaseComposerRuntimeCore } from "./base-composer-runtime-core";
|
|
10
|
+
import { gateInteractableComposerMetadata } from "../../model-context/interactable-composer-metadata";
|
|
10
11
|
|
|
11
12
|
export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
|
|
12
13
|
public get canCancel() {
|
|
@@ -97,8 +98,24 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
|
|
|
97
98
|
...this._nonTextPassthrough,
|
|
98
99
|
] as AppendMessage["content"])
|
|
99
100
|
: message.content;
|
|
101
|
+
// Gate live state against the new branch's prefix (messages up to the
|
|
102
|
+
// parent): an unchanged interactable re-stamps the prior baseline, an
|
|
103
|
+
// interactable edited since the original message stamps its newest state.
|
|
104
|
+
const messages = this.runtime.messages;
|
|
105
|
+
const parentIndex =
|
|
106
|
+
this._parentId === null
|
|
107
|
+
? -1
|
|
108
|
+
: messages.findIndex((m) => m.id === this._parentId);
|
|
109
|
+
const composerMetadata = gateInteractableComposerMetadata(
|
|
110
|
+
this.runtime.getModelContext().unstable_composerMetadata,
|
|
111
|
+
messages.slice(0, parentIndex + 1),
|
|
112
|
+
);
|
|
113
|
+
const enriched = this.enrichWithComposerMetadata(
|
|
114
|
+
message,
|
|
115
|
+
composerMetadata,
|
|
116
|
+
);
|
|
100
117
|
this.runtime.append({
|
|
101
|
-
...
|
|
118
|
+
...enriched,
|
|
102
119
|
content,
|
|
103
120
|
parentId: this._parentId,
|
|
104
121
|
sourceId: this._sourceId,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type QueueItemState,
|
|
12
12
|
} from "../../store/scopes/queue-item";
|
|
13
13
|
import { BaseComposerRuntimeCore } from "./base-composer-runtime-core";
|
|
14
|
+
import { gateInteractableComposerMetadata } from "../../model-context/interactable-composer-metadata";
|
|
14
15
|
|
|
15
16
|
export class DefaultThreadComposerRuntimeCore
|
|
16
17
|
extends BaseComposerRuntimeCore
|
|
@@ -84,8 +85,17 @@ export class DefaultThreadComposerRuntimeCore
|
|
|
84
85
|
message: Omit<AppendMessage, "parentId" | "sourceId">,
|
|
85
86
|
options?: SendOptions,
|
|
86
87
|
) {
|
|
88
|
+
// Merge provider-contributed metadata onto the outgoing user message
|
|
89
|
+
// (same metadata.custom append path quotes ride). The interactables gate
|
|
90
|
+
// runs here because it needs thread history, unavailable to the provider.
|
|
91
|
+
const composerMetadata = gateInteractableComposerMetadata(
|
|
92
|
+
this.runtime.getModelContext().unstable_composerMetadata,
|
|
93
|
+
this.runtime.messages,
|
|
94
|
+
);
|
|
95
|
+
const enriched = this.enrichWithComposerMetadata(message, composerMetadata);
|
|
96
|
+
|
|
87
97
|
this.runtime.append({
|
|
88
|
-
...(
|
|
98
|
+
...(enriched as AppendMessage),
|
|
89
99
|
parentId: this.runtime.messages.at(-1)?.id ?? null,
|
|
90
100
|
sourceId: null,
|
|
91
101
|
startRun: options?.startRun,
|