1import { PROVIDER_SEND_TURN_MAX_ATTACHMENTS } from "@t3tools/contracts";
2import { scopedThreadKey } from "../lib/scopedEntities";
3import { appAtomRegistry } from "./atom-registry";
4import {
5 confirmThreadOutboxMessageQueued,
6 threadOutboxRevision,
7 type QueuedThreadMessage,
8} from "./thread-outbox";
9import { removeThreadOutboxMessage } from "./thread-outbox-removal";
10import {
11 flushComposerDrafts,
12 getComposerDraftSnapshot,
13 mergeComposerDraftContent,
14 undoComposerDraftMerge,
15 updateComposerDraftSettings,
16 waitForComposerDraftsLoaded,
17} from "./use-composer-drafts";
18import {
19 dispatchingQueuedMessageIdAtom,
20 editingQueuedMessageIdsAtom,
21 holdEditingQueuedMessage,
22 releaseEditingQueuedMessage,
23} from "./use-thread-outbox";
24
25/** Take delivery ownership before any await; the durable draft then takes ownership of the files. */
26export async function editPendingThreadMessage(message: QueuedThreadMessage): Promise<boolean> {
27 if (
28 message.creation ||
29 appAtomRegistry.get(dispatchingQueuedMessageIdAtom) === message.messageId ||
30 appAtomRegistry.get(editingQueuedMessageIdsAtom)[message.messageId]
31 ) {
32 return false;
33 }
34 holdEditingQueuedMessage(message.messageId);
35 const draftKey = scopedThreadKey(message.environmentId, message.threadId);
36 let rollback: {
37 snapshot: ReturnType<typeof getComposerDraftSnapshot>;
38 merged: ReturnType<typeof getComposerDraftSnapshot>;
39 } | null = null;
40 try {
41 if (!(await confirmThreadOutboxMessageQueued(message))) return false;
42 const revision = threadOutboxRevision(message.messageId);
43 await waitForComposerDraftsLoaded();
44 const snapshot = getComposerDraftSnapshot(draftKey);
45 const attachmentIds = new Set(snapshot.attachments.map((attachment) => attachment.id));
46 for (const attachment of message.attachments) attachmentIds.add(attachment.id);
47 if (attachmentIds.size > PROVIDER_SEND_TURN_MAX_ATTACHMENTS) {
48 throw new Error("Remove attachments from the composer before editing this message.");
49 }
50 try {
51 await mergeComposerDraftContent(draftKey, message);
52 } finally {
53 rollback = { snapshot, merged: getComposerDraftSnapshot(draftKey) };
54 }
55 updateComposerDraftSettings(draftKey, {
56 ...(message.modelSelection ? { modelSelection: message.modelSelection } : {}),
57 ...(message.runtimeMode ? { runtimeMode: message.runtimeMode } : {}),
58 ...(message.interactionMode ? { interactionMode: message.interactionMode } : {}),
59 });
60 rollback = { snapshot, merged: getComposerDraftSnapshot(draftKey) };
61 await flushComposerDrafts();
62 if (!(await removeThreadOutboxMessage(message, revision))) return false;
63 rollback = null;
64 return true;
65 } finally {
66 try {
67 if (rollback) await undoComposerDraftMerge(draftKey, rollback.snapshot, rollback.merged);
68 } finally {
69 releaseEditingQueuedMessage(message.messageId);
70 }
71 }
72}