@assistant-ui/react 0.14.27 → 0.14.29
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/client/ExternalThread.d.ts +11 -1
- package/dist/client/ExternalThread.d.ts.map +1 -1
- package/dist/client/ExternalThread.js +758 -513
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/context/react/utils/createContextStoreHook.d.ts.map +1 -1
- package/dist/context/react/utils/createStateHookForRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/AssistantRuntimeProvider.js.map +1 -1
- package/dist/legacy-runtime/cloud/auiV0.d.ts +12 -1
- package/dist/legacy-runtime/cloud/auiV0.d.ts.map +1 -1
- package/dist/legacy-runtime/cloud/auiV0.js +2 -1
- package/dist/legacy-runtime/cloud/auiV0.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +7 -6
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +30 -15
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +8 -4
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +1 -0
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/model-context/makeAssistantVisible.d.ts.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +7 -1
- package/dist/model-context/makeAssistantVisible.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarCopy.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarEdit.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarReload.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarRoot.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarRoot.js +3 -3
- package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarSpeak.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.map +1 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts +7 -6
- package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts.map +1 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.js +10 -11
- package/dist/primitives/actionBar/useActionBarFloatStatus.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreContent.d.ts.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreContent.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreItem.d.ts.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreSeparator.d.ts.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreTrigger.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalAnchor.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalContent.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalContent.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalTrigger.d.ts.map +1 -1
- package/dist/primitives/assistantModal/popoverRenderPrimitives.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentRemove.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentRemove.js +1 -0
- package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
- package/dist/primitives/attachment/AttachmentRoot.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentThumb.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentThumb.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNext.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -0
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js +8 -2
- package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -3
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerDictate.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerDictationTranscript.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +6 -4
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQuote.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerQuote.js.map +1 -1
- package/dist/primitives/composer/ComposerRoot.d.ts +5 -0
- package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerRoot.js +86 -40
- package/dist/primitives/composer/ComposerRoot.js.map +1 -1
- package/dist/primitives/composer/ComposerSend.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerSend.js +1 -0
- package/dist/primitives/composer/ComposerSend.js.map +1 -1
- package/dist/primitives/composer/ComposerStopDictation.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerStopDictation.js +1 -0
- package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverBack.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
- package/dist/primitives/composer/trigger/detectTrigger.d.ts +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/dropdownMenuRenderPrimitives.d.ts.map +1 -1
- package/dist/primitives/error/ErrorMessage.d.ts.map +1 -1
- package/dist/primitives/error/ErrorMessage.js.map +1 -1
- package/dist/primitives/error/ErrorRoot.d.ts.map +1 -1
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/message/MessageRoot.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartImage.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartData.d.ts +3 -2
- package/dist/primitives/messagePart/useMessagePartData.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartData.js +3 -2
- package/dist/primitives/messagePart/useMessagePartData.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartFile.d.ts +5 -4
- package/dist/primitives/messagePart/useMessagePartFile.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartFile.js +11 -3
- package/dist/primitives/messagePart/useMessagePartFile.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartImage.d.ts +5 -4
- package/dist/primitives/messagePart/useMessagePartImage.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartImage.js +10 -3
- package/dist/primitives/messagePart/useMessagePartImage.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartReasoning.d.ts +5 -4
- package/dist/primitives/messagePart/useMessagePartReasoning.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartReasoning.js +10 -3
- package/dist/primitives/messagePart/useMessagePartReasoning.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartSource.d.ts +6 -4
- package/dist/primitives/messagePart/useMessagePartSource.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartSource.js +12 -3
- package/dist/primitives/messagePart/useMessagePartSource.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartText.d.ts +6 -5
- package/dist/primitives/messagePart/useMessagePartText.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartText.js +10 -3
- package/dist/primitives/messagePart/useMessagePartText.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.js +1 -0
- package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.js +1 -0
- package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemText.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.d.ts.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.d.ts.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionDescription.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionDescription.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionTitle.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionTitle.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js +1 -0
- package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
- package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadScrollToBottom.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadScrollToBottom.js +1 -0
- package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
- package/dist/primitives/thread/ThreadSuggestion.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +53 -19
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/ThreadViewportFooter.d.ts.map +1 -1
- package/dist/primitives/thread/topAnchor/createReserveObservers.d.ts.map +1 -1
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.d.ts +4 -0
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.d.ts.map +1 -1
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.js +14 -1
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.js.map +1 -1
- package/dist/primitives/thread/topAnchor/topAnchorTurn.d.ts +13 -1
- package/dist/primitives/thread/topAnchor/topAnchorTurn.d.ts.map +1 -1
- package/dist/primitives/thread/topAnchor/topAnchorTurn.js +14 -1
- package/dist/primitives/thread/topAnchor/topAnchorTurn.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +2 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/primitives/threadList/ThreadListLoadMore.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListRoot.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListRoot.js.map +1 -1
- package/dist/primitives/threadListFocusGroup.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.d.ts.map +1 -1
- package/dist/tests/setup.d.ts +1 -1
- package/dist/utils/Primitive.d.ts.map +1 -1
- package/dist/utils/Primitive.js.map +1 -1
- package/dist/utils/createActionButton.d.ts.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.js +1 -0
- package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
- package/dist/utils/hooks/useSizeHandle.js +1 -0
- package/dist/utils/hooks/useSizeHandle.js.map +1 -1
- package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +2 -2
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/package.json +20 -20
- package/src/client/ExternalThread.ts +341 -53
- package/src/legacy-runtime/cloud/auiV0.ts +14 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +4 -1
- package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +37 -10
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-delivery.test.tsx +79 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-parentid.test.tsx +97 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +113 -2
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +10 -2
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +189 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +20 -4
- package/src/model-context/makeAssistantVisible.test.tsx +146 -0
- package/src/model-context/makeAssistantVisible.tsx +18 -1
- package/src/primitives/actionBar/useActionBarFloatStatus.ts +7 -5
- package/src/primitives/composer/ComposerAddAttachment.test.tsx +108 -0
- package/src/primitives/composer/ComposerAddAttachment.ts +10 -4
- package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +3 -2
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +3 -2
- package/src/primitives/composer/ComposerInput.test.tsx +98 -1
- package/src/primitives/composer/ComposerInput.tsx +10 -8
- package/src/primitives/composer/ComposerRoot.test.tsx +226 -5
- package/src/primitives/composer/ComposerRoot.tsx +50 -1
- package/src/primitives/messagePart/useMessagePartData.ts +3 -2
- package/src/primitives/messagePart/useMessagePartFile.ts +19 -7
- package/src/primitives/messagePart/useMessagePartImage.ts +18 -7
- package/src/primitives/messagePart/useMessagePartReasoning.ts +15 -6
- package/src/primitives/messagePart/useMessagePartSource.ts +20 -7
- package/src/primitives/messagePart/useMessagePartText.ts +15 -5
- package/src/primitives/thread/ThreadViewport.tsx +15 -0
- package/src/primitives/thread/topAnchor/mountTopAnchorReserve.test.ts +243 -0
- package/src/primitives/thread/topAnchor/mountTopAnchorReserve.ts +28 -7
- package/src/primitives/thread/topAnchor/topAnchorTurn.test.ts +69 -0
- package/src/primitives/thread/topAnchor/topAnchorTurn.ts +29 -0
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +86 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +6 -1
- package/src/tests/DataRenderers.test.tsx +212 -0
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +1 -11
- package/src/tests/external-thread-attachments.test.tsx +372 -0
- package/src/tests/external-thread-parity.test.tsx +232 -0
- package/src/tests/generative-ui.test.tsx +1 -46
- package/src/tests/messagePartSwitchRace.test.tsx +172 -0
- package/src/tests/messagePartTypeMismatch.test.tsx +146 -0
- package/src/tests/threadMessageById.test.tsx +0 -7
- package/src/utils/smooth/useSmooth.ts +7 -4
- package/src/tests/BaseComposerRuntimeCore.test.ts +0 -612
- package/src/tests/BaseSubscribable.test.ts +0 -54
- package/src/tests/DataRenderers.test.ts +0 -355
- package/src/tests/ExternalStoreThreadRuntimeCore.test.ts +0 -685
- package/src/tests/LocalThreadRuntimeCore.test.ts +0 -324
- package/src/tests/auiV0Encode.test.ts +0 -188
- package/src/tests/external-message-converter.test.ts +0 -346
- package/src/tests/shouldContinue.test.ts +0 -122
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useMemo,
|
|
4
|
+
useEffect,
|
|
5
|
+
useEffectEvent,
|
|
6
|
+
useCallback,
|
|
7
|
+
useRef,
|
|
8
|
+
} from "react";
|
|
2
9
|
import { resource, withKey } from "@assistant-ui/tap";
|
|
3
10
|
import {
|
|
4
11
|
type ClientElement,
|
|
@@ -10,20 +17,30 @@ import {
|
|
|
10
17
|
} from "@assistant-ui/store";
|
|
11
18
|
|
|
12
19
|
import type {
|
|
20
|
+
AddToolResultOptions,
|
|
13
21
|
AppendMessage,
|
|
14
22
|
Attachment,
|
|
23
|
+
AttachmentAdapter,
|
|
15
24
|
CreateAttachment,
|
|
25
|
+
PendingAttachment,
|
|
16
26
|
RespondToToolApprovalOptions,
|
|
27
|
+
ResumeToolCallOptions,
|
|
28
|
+
MessagePartStatus,
|
|
17
29
|
ThreadAssistantMessagePart,
|
|
18
30
|
ThreadUserMessagePart,
|
|
19
31
|
ThreadMessage,
|
|
32
|
+
ToolCallMessagePartStatus,
|
|
20
33
|
ExternalThreadQueueAdapter,
|
|
21
34
|
ExternalThreadBranchAdapter,
|
|
22
35
|
} from "@assistant-ui/core";
|
|
36
|
+
import { ToolResponse } from "assistant-stream";
|
|
37
|
+
import type { ReadonlyJSONValue } from "assistant-stream/utils";
|
|
23
38
|
import type { QueueItemState } from "@assistant-ui/core/store";
|
|
24
39
|
import type { ComposerSendOptions } from "@assistant-ui/core/store";
|
|
25
40
|
import {
|
|
41
|
+
fileMatchesAccept,
|
|
26
42
|
getThreadMessageText,
|
|
43
|
+
isCreateAttachment,
|
|
27
44
|
resolveToolApprovalResponse,
|
|
28
45
|
} from "@assistant-ui/core/internal";
|
|
29
46
|
import { ModelContext, Suggestions } from "@assistant-ui/core/store";
|
|
@@ -37,9 +54,35 @@ export type ExternalThreadMessage = ThreadMessage & {
|
|
|
37
54
|
id: string;
|
|
38
55
|
};
|
|
39
56
|
|
|
57
|
+
const COMPLETE_STATUS: MessagePartStatus = Object.freeze({
|
|
58
|
+
type: "complete",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Legacy runtime parity (toMessagePartStatus): a tool call without a result
|
|
62
|
+
// carries its message's status; the last part of a streaming message streams.
|
|
63
|
+
const derivePartStatus = (
|
|
64
|
+
message: ExternalThreadMessage,
|
|
65
|
+
partIndex: number,
|
|
66
|
+
part: ThreadAssistantMessagePart | ThreadUserMessagePart,
|
|
67
|
+
): ToolCallMessagePartStatus => {
|
|
68
|
+
if (message.role !== "assistant" || !message.status) return COMPLETE_STATUS;
|
|
69
|
+
|
|
70
|
+
if (part.type === "tool-call") {
|
|
71
|
+
if (!part.result) return message.status as ToolCallMessagePartStatus;
|
|
72
|
+
return COMPLETE_STATUS;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const isLastPart = partIndex === Math.max(0, message.content.length - 1);
|
|
76
|
+
if (message.status.type === "requires-action") return COMPLETE_STATUS;
|
|
77
|
+
return isLastPart ? (message.status as MessagePartStatus) : COMPLETE_STATUS;
|
|
78
|
+
};
|
|
79
|
+
|
|
40
80
|
export type ExternalThreadProps = {
|
|
41
81
|
messages: readonly ExternalThreadMessage[];
|
|
42
82
|
isRunning?: boolean;
|
|
83
|
+
isLoading?: boolean | undefined;
|
|
84
|
+
state?: ReadonlyJSONValue | undefined;
|
|
85
|
+
extras?: unknown;
|
|
43
86
|
/**
|
|
44
87
|
* Whether sending new messages is currently disabled. When `true`, the
|
|
45
88
|
* thread composer's input remains usable but `send()` is a no-op and
|
|
@@ -56,6 +99,12 @@ export type ExternalThreadProps = {
|
|
|
56
99
|
onReload?: (parentId: string | null) => void;
|
|
57
100
|
onStartRun?: () => void;
|
|
58
101
|
onCancel?: () => void;
|
|
102
|
+
onResume?: (() => void) | undefined;
|
|
103
|
+
onAddToolResult?: ((options: AddToolResultOptions) => void) | undefined;
|
|
104
|
+
/** Callback for resuming a tool call that is waiting for human input. */
|
|
105
|
+
onResumeToolCall?: ((options: ResumeToolCallOptions) => void) | undefined;
|
|
106
|
+
onLoadExternalState?: ((state: unknown) => void) | undefined;
|
|
107
|
+
attachmentAdapter?: AttachmentAdapter | undefined;
|
|
59
108
|
/** Queue adapter for runtimes that support message queuing and steering. */
|
|
60
109
|
queue?: ExternalThreadQueueAdapter;
|
|
61
110
|
/** Branch adapter for runtimes that track sibling variants of messages. */
|
|
@@ -67,6 +116,7 @@ export type ExternalThreadProps = {
|
|
|
67
116
|
type MessageClientProps = {
|
|
68
117
|
message: ExternalThreadMessage;
|
|
69
118
|
index: number;
|
|
119
|
+
parentId: string | null;
|
|
70
120
|
onEdit?: (message: AppendMessage) => void;
|
|
71
121
|
onReload?: () => void;
|
|
72
122
|
queue?: ExternalThreadQueueAdapter | undefined;
|
|
@@ -74,17 +124,24 @@ type MessageClientProps = {
|
|
|
74
124
|
onRespondToToolApproval?:
|
|
75
125
|
| ((options: RespondToToolApprovalOptions) => void)
|
|
76
126
|
| undefined;
|
|
127
|
+
onAddToolResult?: ((options: AddToolResultOptions) => void) | undefined;
|
|
128
|
+
onResumeToolCall?: ((options: ResumeToolCallOptions) => void) | undefined;
|
|
129
|
+
attachmentAdapter?: AttachmentAdapter | undefined;
|
|
77
130
|
};
|
|
78
131
|
|
|
79
132
|
// Message Client - minimal implementation
|
|
80
133
|
const useMessageClient = ({
|
|
81
134
|
message,
|
|
82
135
|
index,
|
|
136
|
+
parentId,
|
|
83
137
|
onEdit,
|
|
84
138
|
onReload,
|
|
85
139
|
queue,
|
|
86
140
|
branches,
|
|
87
141
|
onRespondToToolApproval,
|
|
142
|
+
onAddToolResult,
|
|
143
|
+
onResumeToolCall,
|
|
144
|
+
attachmentAdapter,
|
|
88
145
|
}: MessageClientProps): ClientOutput<"message"> => {
|
|
89
146
|
const [isCopied, setIsCopied] = useState(false);
|
|
90
147
|
const [isHovering, setIsHovering] = useState(false);
|
|
@@ -92,7 +149,17 @@ const useMessageClient = ({
|
|
|
92
149
|
|
|
93
150
|
const partClients = useClientLookup(
|
|
94
151
|
message.content.map((part, idx) =>
|
|
95
|
-
withKey(
|
|
152
|
+
withKey(
|
|
153
|
+
idx,
|
|
154
|
+
PartResource({
|
|
155
|
+
part,
|
|
156
|
+
status: derivePartStatus(message, idx, part),
|
|
157
|
+
messageId: message.id,
|
|
158
|
+
onRespondToToolApproval,
|
|
159
|
+
onAddToolResult,
|
|
160
|
+
onResumeToolCall,
|
|
161
|
+
}),
|
|
162
|
+
),
|
|
96
163
|
),
|
|
97
164
|
);
|
|
98
165
|
|
|
@@ -120,7 +187,7 @@ const useMessageClient = ({
|
|
|
120
187
|
queue?.clear("edit");
|
|
121
188
|
onEdit?.({
|
|
122
189
|
...msg,
|
|
123
|
-
parentId
|
|
190
|
+
parentId,
|
|
124
191
|
sourceId: message.id,
|
|
125
192
|
});
|
|
126
193
|
setIsEditing(false);
|
|
@@ -136,6 +203,7 @@ const useMessageClient = ({
|
|
|
136
203
|
onSend: handleSendEdit,
|
|
137
204
|
message,
|
|
138
205
|
queue,
|
|
206
|
+
attachmentAdapter,
|
|
139
207
|
}),
|
|
140
208
|
);
|
|
141
209
|
|
|
@@ -148,7 +216,7 @@ const useMessageClient = ({
|
|
|
148
216
|
return {
|
|
149
217
|
...message,
|
|
150
218
|
attachments: message.attachments ?? [],
|
|
151
|
-
parentId
|
|
219
|
+
parentId,
|
|
152
220
|
isLast: false, // Will be set by thread
|
|
153
221
|
branchNumber,
|
|
154
222
|
branchCount,
|
|
@@ -161,6 +229,7 @@ const useMessageClient = ({
|
|
|
161
229
|
};
|
|
162
230
|
}, [
|
|
163
231
|
message,
|
|
232
|
+
parentId,
|
|
164
233
|
isCopied,
|
|
165
234
|
isHovering,
|
|
166
235
|
index,
|
|
@@ -219,28 +288,65 @@ const MessageClient = resource(useMessageClient);
|
|
|
219
288
|
|
|
220
289
|
type PartResourceProps = {
|
|
221
290
|
part: ThreadAssistantMessagePart | ThreadUserMessagePart;
|
|
291
|
+
status: ToolCallMessagePartStatus;
|
|
292
|
+
messageId: string;
|
|
222
293
|
onRespondToToolApproval?:
|
|
223
294
|
| ((options: RespondToToolApprovalOptions) => void)
|
|
224
295
|
| undefined;
|
|
296
|
+
onAddToolResult?: ((options: AddToolResultOptions) => void) | undefined;
|
|
297
|
+
onResumeToolCall?: ((options: ResumeToolCallOptions) => void) | undefined;
|
|
225
298
|
};
|
|
226
299
|
|
|
227
300
|
// Part Client - minimal implementation
|
|
228
301
|
const usePartResource = ({
|
|
229
302
|
part,
|
|
303
|
+
status,
|
|
304
|
+
messageId,
|
|
230
305
|
onRespondToToolApproval,
|
|
306
|
+
onAddToolResult,
|
|
307
|
+
onResumeToolCall,
|
|
231
308
|
}: PartResourceProps): ClientOutput<"part"> => {
|
|
232
309
|
const state = useMemo(
|
|
233
310
|
() => ({
|
|
234
311
|
...part,
|
|
235
|
-
status
|
|
312
|
+
status,
|
|
236
313
|
}),
|
|
237
|
-
[part],
|
|
314
|
+
[part, status],
|
|
238
315
|
);
|
|
239
316
|
|
|
240
317
|
return {
|
|
241
318
|
getState: () => state,
|
|
242
|
-
addToolResult: () => {
|
|
243
|
-
|
|
319
|
+
addToolResult: (result) => {
|
|
320
|
+
if (!onAddToolResult)
|
|
321
|
+
throw new Error(
|
|
322
|
+
"Runtime does not support tool results (onAddToolResult is not set).",
|
|
323
|
+
);
|
|
324
|
+
if (part.type !== "tool-call")
|
|
325
|
+
throw new Error("Tried to add tool result on non-tool message part");
|
|
326
|
+
|
|
327
|
+
const response = ToolResponse.toResponse(result);
|
|
328
|
+
onAddToolResult({
|
|
329
|
+
messageId,
|
|
330
|
+
toolName: part.toolName,
|
|
331
|
+
toolCallId: part.toolCallId,
|
|
332
|
+
result: response.result as ReadonlyJSONValue,
|
|
333
|
+
isError: response.isError,
|
|
334
|
+
...(response.artifact !== undefined && { artifact: response.artifact }),
|
|
335
|
+
...(response.modelContent !== undefined && {
|
|
336
|
+
modelContent: response.modelContent,
|
|
337
|
+
}),
|
|
338
|
+
});
|
|
339
|
+
},
|
|
340
|
+
resumeToolCall: (payload) => {
|
|
341
|
+
if (!onResumeToolCall)
|
|
342
|
+
throw new Error(
|
|
343
|
+
"Runtime does not support resuming tool calls (onResumeToolCall is not set).",
|
|
344
|
+
);
|
|
345
|
+
if (part.type !== "tool-call")
|
|
346
|
+
throw new Error("Tried to resume tool call on non-tool message part");
|
|
347
|
+
|
|
348
|
+
onResumeToolCall({ toolCallId: part.toolCallId, payload });
|
|
349
|
+
},
|
|
244
350
|
respondToToolApproval: (response) => {
|
|
245
351
|
if (!onRespondToToolApproval)
|
|
246
352
|
throw new Error("Runtime does not support tool approvals.");
|
|
@@ -268,7 +374,7 @@ const PartResource = resource(usePartResource);
|
|
|
268
374
|
|
|
269
375
|
type AttachmentResourceProps = {
|
|
270
376
|
attachment: Attachment;
|
|
271
|
-
onRemove?: () => void
|
|
377
|
+
onRemove?: () => void | Promise<void>;
|
|
272
378
|
};
|
|
273
379
|
|
|
274
380
|
// Attachment Client - minimal implementation
|
|
@@ -279,7 +385,7 @@ const useAttachmentResource = ({
|
|
|
279
385
|
return {
|
|
280
386
|
getState: () => attachment,
|
|
281
387
|
remove: async () => {
|
|
282
|
-
onRemove?.();
|
|
388
|
+
await onRemove?.();
|
|
283
389
|
},
|
|
284
390
|
};
|
|
285
391
|
};
|
|
@@ -296,6 +402,7 @@ type ComposerClientResourceProps = {
|
|
|
296
402
|
onSend?: (message: AppendMessage) => void;
|
|
297
403
|
message?: ExternalThreadMessage;
|
|
298
404
|
queue?: ExternalThreadQueueAdapter | undefined;
|
|
405
|
+
attachmentAdapter?: AttachmentAdapter | undefined;
|
|
299
406
|
};
|
|
300
407
|
|
|
301
408
|
const useQueueItemClient = ({
|
|
@@ -316,6 +423,33 @@ const useQueueItemClient = ({
|
|
|
316
423
|
|
|
317
424
|
const QueueItemClient = resource(useQueueItemClient);
|
|
318
425
|
|
|
426
|
+
const drainAdapterAdd = async (
|
|
427
|
+
result: ReturnType<AttachmentAdapter["add"]>,
|
|
428
|
+
upsert: (attachment: Attachment) => void,
|
|
429
|
+
) => {
|
|
430
|
+
if (Symbol.asyncIterator in result) {
|
|
431
|
+
for await (const attachment of result) {
|
|
432
|
+
upsert(attachment);
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
upsert(await result);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// State whose setter tracks the latest value in a ref, so imperative
|
|
440
|
+
// call sequences (setText immediately followed by send) observe the write
|
|
441
|
+
// before React re-renders — legacy composer parity.
|
|
442
|
+
const useLiveState = <T>(initial: T) => {
|
|
443
|
+
const [state, setState] = useState(initial);
|
|
444
|
+
const ref = useRef(state);
|
|
445
|
+
const set = useCallback((next: T | ((prev: T) => T)) => {
|
|
446
|
+
ref.current =
|
|
447
|
+
typeof next === "function" ? (next as (prev: T) => T)(ref.current) : next;
|
|
448
|
+
setState(ref.current);
|
|
449
|
+
}, []);
|
|
450
|
+
return [state, set, ref] as const;
|
|
451
|
+
};
|
|
452
|
+
|
|
319
453
|
// Composer Client - minimal implementation
|
|
320
454
|
const useComposerClientResource = ({
|
|
321
455
|
type,
|
|
@@ -327,12 +461,19 @@ const useComposerClientResource = ({
|
|
|
327
461
|
onSend,
|
|
328
462
|
message,
|
|
329
463
|
queue,
|
|
464
|
+
attachmentAdapter,
|
|
330
465
|
}: ComposerClientResourceProps): ClientOutput<"composer"> => {
|
|
331
|
-
const [text, setText] =
|
|
332
|
-
const [role, setRole] =
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const [
|
|
466
|
+
const [text, setText, textRef] = useLiveState("");
|
|
467
|
+
const [role, setRole, roleRef] = useLiveState<
|
|
468
|
+
"user" | "assistant" | "system"
|
|
469
|
+
>("user");
|
|
470
|
+
const [runConfig, setRunConfig, runConfigRef] = useLiveState<
|
|
471
|
+
Record<string, unknown>
|
|
472
|
+
>({});
|
|
473
|
+
const [attachments, setAttachments, attachmentsRef] = useLiveState<
|
|
474
|
+
readonly Attachment[]
|
|
475
|
+
>([]);
|
|
476
|
+
const [quote, setQuote, quoteRef] = useLiveState<
|
|
336
477
|
{ readonly text: string; readonly messageId: string } | undefined
|
|
337
478
|
>(undefined);
|
|
338
479
|
|
|
@@ -358,19 +499,41 @@ const useComposerClientResource = ({
|
|
|
358
499
|
}, [isEditing]);
|
|
359
500
|
|
|
360
501
|
const attachmentClients = useClientLookup(
|
|
361
|
-
attachments.map((attachment
|
|
502
|
+
attachments.map((attachment) =>
|
|
362
503
|
withKey(
|
|
363
504
|
attachment.id,
|
|
364
505
|
AttachmentResource({
|
|
365
506
|
attachment,
|
|
366
|
-
onRemove: () => {
|
|
367
|
-
|
|
507
|
+
onRemove: async () => {
|
|
508
|
+
await attachmentAdapter?.remove(attachment);
|
|
509
|
+
setAttachments((prev) =>
|
|
510
|
+
prev.filter((a) => a.id !== attachment.id),
|
|
511
|
+
);
|
|
368
512
|
},
|
|
369
513
|
}),
|
|
370
514
|
),
|
|
371
515
|
),
|
|
372
516
|
);
|
|
373
517
|
|
|
518
|
+
const removePendingAttachments = async (removed: readonly Attachment[]) => {
|
|
519
|
+
if (!attachmentAdapter) return;
|
|
520
|
+
await Promise.all(
|
|
521
|
+
removed
|
|
522
|
+
.filter((a) => a.status.type !== "complete")
|
|
523
|
+
.map((a) => attachmentAdapter.remove(a)),
|
|
524
|
+
);
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const upsertAttachment = (attachment: Attachment) => {
|
|
528
|
+
setAttachments((prev) => {
|
|
529
|
+
const idx = prev.findIndex((a) => a.id === attachment.id);
|
|
530
|
+
if (idx === -1) return [...prev, attachment];
|
|
531
|
+
const next = [...prev];
|
|
532
|
+
next[idx] = attachment;
|
|
533
|
+
return next;
|
|
534
|
+
});
|
|
535
|
+
};
|
|
536
|
+
|
|
374
537
|
const queueItems = queue?.items ?? EMPTY_QUEUE_ITEMS;
|
|
375
538
|
const queueItemClients = useClientLookup(
|
|
376
539
|
queueItems.map((item) =>
|
|
@@ -395,7 +558,7 @@ const useComposerClientResource = ({
|
|
|
395
558
|
isEditing,
|
|
396
559
|
canCancel,
|
|
397
560
|
canSend: isEditing && !isEmpty && !isSendDisabled,
|
|
398
|
-
attachmentAccept: "*",
|
|
561
|
+
attachmentAccept: attachmentAdapter?.accept ?? "*",
|
|
399
562
|
isEmpty,
|
|
400
563
|
type,
|
|
401
564
|
dictation: undefined,
|
|
@@ -414,6 +577,7 @@ const useComposerClientResource = ({
|
|
|
414
577
|
attachments.length,
|
|
415
578
|
quote,
|
|
416
579
|
queueItems,
|
|
580
|
+
attachmentAdapter?.accept,
|
|
417
581
|
]);
|
|
418
582
|
|
|
419
583
|
return {
|
|
@@ -422,7 +586,24 @@ const useComposerClientResource = ({
|
|
|
422
586
|
setRole,
|
|
423
587
|
setRunConfig,
|
|
424
588
|
addAttachment: async (fileOrAttachment: File | CreateAttachment) => {
|
|
425
|
-
if (
|
|
589
|
+
if (attachmentAdapter) {
|
|
590
|
+
const file = isCreateAttachment(fileOrAttachment)
|
|
591
|
+
? {
|
|
592
|
+
name: fileOrAttachment.name,
|
|
593
|
+
type: fileOrAttachment.contentType ?? "",
|
|
594
|
+
}
|
|
595
|
+
: { name: fileOrAttachment.name, type: fileOrAttachment.type };
|
|
596
|
+
if (!fileMatchesAccept(file, attachmentAdapter.accept))
|
|
597
|
+
throw new Error(
|
|
598
|
+
`File type ${file.type || "unknown"} is not accepted. Accepted types: ${attachmentAdapter.accept}`,
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
if (!isCreateAttachment(fileOrAttachment) && attachmentAdapter) {
|
|
602
|
+
await drainAdapterAdd(
|
|
603
|
+
attachmentAdapter.add({ file: fileOrAttachment }),
|
|
604
|
+
upsertAttachment,
|
|
605
|
+
);
|
|
606
|
+
} else if (!isCreateAttachment(fileOrAttachment)) {
|
|
426
607
|
const newAttachment: Attachment = {
|
|
427
608
|
id: Math.random().toString(36).substring(7),
|
|
428
609
|
type: "file",
|
|
@@ -432,7 +613,7 @@ const useComposerClientResource = ({
|
|
|
432
613
|
status: { type: "complete" },
|
|
433
614
|
content: [],
|
|
434
615
|
};
|
|
435
|
-
setAttachments([...
|
|
616
|
+
setAttachments((prev) => [...prev, newAttachment]);
|
|
436
617
|
} else {
|
|
437
618
|
const newAttachment: Attachment = {
|
|
438
619
|
id: fileOrAttachment.id ?? Math.random().toString(36).substring(7),
|
|
@@ -442,11 +623,13 @@ const useComposerClientResource = ({
|
|
|
442
623
|
content: fileOrAttachment.content,
|
|
443
624
|
status: { type: "complete" },
|
|
444
625
|
};
|
|
445
|
-
setAttachments([...
|
|
626
|
+
setAttachments((prev) => [...prev, newAttachment]);
|
|
446
627
|
}
|
|
447
628
|
},
|
|
448
629
|
clearAttachments: async () => {
|
|
630
|
+
const removed = attachmentsRef.current;
|
|
449
631
|
setAttachments([]);
|
|
632
|
+
await removePendingAttachments(removed);
|
|
450
633
|
},
|
|
451
634
|
attachment: (selector) => {
|
|
452
635
|
if ("id" in selector) {
|
|
@@ -455,37 +638,69 @@ const useComposerClientResource = ({
|
|
|
455
638
|
return attachmentClients.get(selector);
|
|
456
639
|
},
|
|
457
640
|
reset: async () => {
|
|
641
|
+
const removed = attachmentsRef.current;
|
|
458
642
|
setText("");
|
|
459
643
|
setRole("user");
|
|
460
644
|
setRunConfig({});
|
|
461
645
|
setAttachments([]);
|
|
462
646
|
setQuote(undefined);
|
|
647
|
+
await removePendingAttachments(removed);
|
|
463
648
|
},
|
|
464
649
|
send: (opts?: ComposerSendOptions) => {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
attachments: attachments as any,
|
|
472
|
-
createdAt: new Date(),
|
|
473
|
-
parentId: null,
|
|
474
|
-
sourceId: null,
|
|
475
|
-
runConfig,
|
|
476
|
-
startRun: opts?.startRun,
|
|
477
|
-
metadata: {
|
|
478
|
-
custom: { ...(currentQuote ? { quote: currentQuote } : {}) },
|
|
479
|
-
},
|
|
480
|
-
};
|
|
481
|
-
if (queue) {
|
|
482
|
-
queue.enqueue(composedMessage, { steer: opts?.steer ?? false });
|
|
483
|
-
} else {
|
|
484
|
-
onSend?.(composedMessage);
|
|
485
|
-
}
|
|
650
|
+
const currentQuote = quoteRef.current;
|
|
651
|
+
const currentText = textRef.current;
|
|
652
|
+
const currentAttachments = attachmentsRef.current;
|
|
653
|
+
const isEmpty = !currentText.trim() && !currentAttachments.length;
|
|
654
|
+
if (!isEditing || isEmpty || isSendDisabled) return;
|
|
655
|
+
|
|
486
656
|
setText("");
|
|
487
657
|
setAttachments([]);
|
|
488
658
|
setQuote(undefined);
|
|
659
|
+
|
|
660
|
+
const dispatch = (sendAttachments: readonly Attachment[]) => {
|
|
661
|
+
const composedMessage: AppendMessage = {
|
|
662
|
+
role: roleRef.current,
|
|
663
|
+
content: currentText
|
|
664
|
+
? [{ type: "text" as const, text: currentText }]
|
|
665
|
+
: [],
|
|
666
|
+
attachments: sendAttachments as any,
|
|
667
|
+
createdAt: new Date(),
|
|
668
|
+
parentId: null,
|
|
669
|
+
sourceId: null,
|
|
670
|
+
runConfig: runConfigRef.current,
|
|
671
|
+
startRun: opts?.startRun,
|
|
672
|
+
metadata: {
|
|
673
|
+
custom: { ...(currentQuote ? { quote: currentQuote } : {}) },
|
|
674
|
+
},
|
|
675
|
+
};
|
|
676
|
+
if (queue) {
|
|
677
|
+
queue.enqueue(composedMessage, { steer: opts?.steer ?? false });
|
|
678
|
+
} else {
|
|
679
|
+
onSend?.(composedMessage);
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
if (attachmentAdapter && currentAttachments.length > 0) {
|
|
684
|
+
void Promise.all(
|
|
685
|
+
currentAttachments.map((attachment) =>
|
|
686
|
+
attachment.status.type === "complete"
|
|
687
|
+
? attachment
|
|
688
|
+
: attachmentAdapter.send(attachment as PendingAttachment),
|
|
689
|
+
),
|
|
690
|
+
).then(dispatch, (error) => {
|
|
691
|
+
// Upload failed: merge the failed send back into the draft.
|
|
692
|
+
setText((prev) =>
|
|
693
|
+
currentText && prev
|
|
694
|
+
? currentText + "\n" + prev
|
|
695
|
+
: currentText || prev,
|
|
696
|
+
);
|
|
697
|
+
setQuote((prev) => prev ?? currentQuote);
|
|
698
|
+
setAttachments((prev) => [...currentAttachments, ...prev]);
|
|
699
|
+
console.error("Failed to send attachments", error);
|
|
700
|
+
});
|
|
701
|
+
} else {
|
|
702
|
+
dispatch(currentAttachments);
|
|
703
|
+
}
|
|
489
704
|
},
|
|
490
705
|
cancel: onCancel,
|
|
491
706
|
beginEdit: () => {
|
|
@@ -502,20 +717,50 @@ const useComposerClientResource = ({
|
|
|
502
717
|
|
|
503
718
|
const ComposerClientResource = resource(useComposerClientResource);
|
|
504
719
|
|
|
720
|
+
const dedupeMessagesById = (messages: readonly ExternalThreadMessage[]) => {
|
|
721
|
+
const seenIds = new Set<string>();
|
|
722
|
+
const deduped: ExternalThreadMessage[] = [];
|
|
723
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
724
|
+
const message = messages[i]!;
|
|
725
|
+
if (seenIds.has(message.id)) {
|
|
726
|
+
console.warn(
|
|
727
|
+
`ExternalThread: duplicate message id "${message.id}" in the provided messages array; keeping the last occurrence.`,
|
|
728
|
+
);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
seenIds.add(message.id);
|
|
732
|
+
deduped.push(message);
|
|
733
|
+
}
|
|
734
|
+
return deduped.length === messages.length ? messages : deduped.reverse();
|
|
735
|
+
};
|
|
736
|
+
|
|
505
737
|
// External Thread Client
|
|
506
738
|
const useExternalThread = ({
|
|
507
|
-
messages,
|
|
739
|
+
messages: messagesProp,
|
|
508
740
|
isRunning = false,
|
|
741
|
+
isLoading = false,
|
|
742
|
+
state: threadState,
|
|
743
|
+
extras,
|
|
509
744
|
isSendDisabled = false,
|
|
510
745
|
onNew,
|
|
511
746
|
onEdit,
|
|
512
747
|
onReload,
|
|
513
748
|
onStartRun,
|
|
514
749
|
onCancel,
|
|
750
|
+
onResume,
|
|
751
|
+
onAddToolResult,
|
|
752
|
+
onResumeToolCall,
|
|
753
|
+
onLoadExternalState,
|
|
754
|
+
attachmentAdapter,
|
|
515
755
|
queue,
|
|
516
756
|
branches,
|
|
517
757
|
onRespondToToolApproval,
|
|
518
758
|
}: ExternalThreadProps): ClientOutput<"thread"> => {
|
|
759
|
+
const messages = useMemo(
|
|
760
|
+
() => dedupeMessagesById(messagesProp),
|
|
761
|
+
[messagesProp],
|
|
762
|
+
);
|
|
763
|
+
|
|
519
764
|
const handleReload = (messageId: string) => {
|
|
520
765
|
const messageIndex = messages.findIndex((m) => m.id === messageId);
|
|
521
766
|
if (messageIndex === -1) return;
|
|
@@ -530,10 +775,14 @@ const useExternalThread = ({
|
|
|
530
775
|
const props: MessageClientProps = {
|
|
531
776
|
message: msg,
|
|
532
777
|
index,
|
|
778
|
+
parentId: index > 0 ? messages[index - 1]!.id : null,
|
|
533
779
|
onReload: () => handleReload(msg.id),
|
|
534
780
|
queue,
|
|
535
781
|
branches,
|
|
536
782
|
onRespondToToolApproval,
|
|
783
|
+
onAddToolResult,
|
|
784
|
+
onResumeToolCall,
|
|
785
|
+
attachmentAdapter,
|
|
537
786
|
};
|
|
538
787
|
if (onEdit) props.onEdit = onEdit;
|
|
539
788
|
return withKey(msg.id, MessageClient(props));
|
|
@@ -546,9 +795,25 @@ const useExternalThread = ({
|
|
|
546
795
|
};
|
|
547
796
|
|
|
548
797
|
const handleSendNew = (message: AppendMessage) => {
|
|
549
|
-
|
|
798
|
+
// The composer does not know the thread; stamp the current head as the
|
|
799
|
+
// parent (legacy composer parity).
|
|
800
|
+
onNew?.({ ...message, parentId: messages.at(-1)?.id ?? null });
|
|
550
801
|
};
|
|
551
802
|
|
|
803
|
+
const headId = messages.at(-1)?.id ?? null;
|
|
804
|
+
const composerQueue = useMemo(
|
|
805
|
+
(): ExternalThreadQueueAdapter | undefined =>
|
|
806
|
+
queue && {
|
|
807
|
+
...queue,
|
|
808
|
+
enqueue: (message, options) =>
|
|
809
|
+
queue.enqueue(
|
|
810
|
+
{ ...message, parentId: message.parentId ?? headId },
|
|
811
|
+
options,
|
|
812
|
+
),
|
|
813
|
+
},
|
|
814
|
+
[queue, headId],
|
|
815
|
+
);
|
|
816
|
+
|
|
552
817
|
const composerClient = useClientResource(
|
|
553
818
|
ComposerClientResource({
|
|
554
819
|
type: "thread",
|
|
@@ -557,12 +822,16 @@ const useExternalThread = ({
|
|
|
557
822
|
isSendDisabled,
|
|
558
823
|
onCancel: handleCancelRun,
|
|
559
824
|
onSend: handleSendNew,
|
|
560
|
-
queue,
|
|
825
|
+
queue: composerQueue,
|
|
826
|
+
attachmentAdapter,
|
|
561
827
|
}),
|
|
562
828
|
);
|
|
563
829
|
|
|
564
830
|
const hasQueue = !!queue;
|
|
565
831
|
const hasBranches = !!branches;
|
|
832
|
+
const hasEdit = !!onEdit;
|
|
833
|
+
const hasReload = !!onReload;
|
|
834
|
+
const hasAttachments = !!attachmentAdapter;
|
|
566
835
|
const state = useMemo(() => {
|
|
567
836
|
const messageStates = messageClients.state.map((s, idx, arr) => ({
|
|
568
837
|
...s,
|
|
@@ -572,15 +841,15 @@ const useExternalThread = ({
|
|
|
572
841
|
return {
|
|
573
842
|
isEmpty: messages.length === 0,
|
|
574
843
|
isDisabled: false,
|
|
575
|
-
isLoading
|
|
844
|
+
isLoading,
|
|
576
845
|
isRunning,
|
|
577
846
|
capabilities: {
|
|
578
|
-
edit:
|
|
847
|
+
edit: hasEdit,
|
|
579
848
|
delete: false,
|
|
580
|
-
reload:
|
|
849
|
+
reload: hasReload,
|
|
581
850
|
cancel: isRunning,
|
|
582
851
|
speech: false,
|
|
583
|
-
attachments:
|
|
852
|
+
attachments: hasAttachments,
|
|
584
853
|
feedback: false,
|
|
585
854
|
voice: false,
|
|
586
855
|
switchToBranch: hasBranches,
|
|
@@ -590,9 +859,9 @@ const useExternalThread = ({
|
|
|
590
859
|
queue: hasQueue,
|
|
591
860
|
},
|
|
592
861
|
messages: messageStates,
|
|
593
|
-
state: {},
|
|
862
|
+
state: threadState ?? {},
|
|
594
863
|
suggestions: [],
|
|
595
|
-
extras
|
|
864
|
+
extras,
|
|
596
865
|
speech: undefined,
|
|
597
866
|
voice: undefined,
|
|
598
867
|
composer: composerClient.state,
|
|
@@ -600,8 +869,14 @@ const useExternalThread = ({
|
|
|
600
869
|
}, [
|
|
601
870
|
messages,
|
|
602
871
|
isRunning,
|
|
872
|
+
isLoading,
|
|
873
|
+
threadState,
|
|
874
|
+
extras,
|
|
603
875
|
hasQueue,
|
|
604
876
|
hasBranches,
|
|
877
|
+
hasEdit,
|
|
878
|
+
hasReload,
|
|
879
|
+
hasAttachments,
|
|
605
880
|
messageClients.state,
|
|
606
881
|
composerClient.state,
|
|
607
882
|
]);
|
|
@@ -643,8 +918,21 @@ const useExternalThread = ({
|
|
|
643
918
|
startRun: () => {
|
|
644
919
|
onStartRun?.();
|
|
645
920
|
},
|
|
646
|
-
resumeRun: () => {
|
|
921
|
+
resumeRun: () => {
|
|
922
|
+
if (!onResume)
|
|
923
|
+
throw new Error(
|
|
924
|
+
"Runtime does not support resuming runs (onResume is not set).",
|
|
925
|
+
);
|
|
926
|
+
onResume();
|
|
927
|
+
},
|
|
647
928
|
cancelRun: handleCancelRun,
|
|
929
|
+
importExternalState: (state: unknown) => {
|
|
930
|
+
if (!onLoadExternalState)
|
|
931
|
+
throw new Error(
|
|
932
|
+
"Runtime does not support importing external states (onLoadExternalState is not set).",
|
|
933
|
+
);
|
|
934
|
+
onLoadExternalState(state);
|
|
935
|
+
},
|
|
648
936
|
getModelContext: () => ({ tools: {}, config: {} }),
|
|
649
937
|
export: () => ({ messages: [] }),
|
|
650
938
|
import: () => {},
|