diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 0facbdfff452..eb6c4d1d568e 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -782,21 +782,34 @@ function createGlobalSync() { break } case "message.updated": { - const messages = store.message[event.properties.info.sessionID] + const info = event.properties.info + const messages = store.message[info.sessionID] if (!messages) { - setStore("message", event.properties.info.sessionID, [event.properties.info]) + setStore("message", info.sessionID, [info]) break } - const result = Binary.search(messages, event.properties.info.id, (m) => m.id) + + const clientID = info.role === "user" ? info.clientMessageID : undefined + if (clientID && clientID !== info.id) { + const optimistic = Binary.search(messages, clientID, (m) => m.id) + if (optimistic.found) { + setStore("message", info.sessionID, optimistic.index, reconcile(info)) + setStore("part", info.id, store.part[clientID] ?? []) + setStore("part", clientID, undefined!) + break + } + } + + const result = Binary.search(messages, info.id, (m) => m.id) if (result.found) { - setStore("message", event.properties.info.sessionID, result.index, reconcile(event.properties.info)) + setStore("message", info.sessionID, result.index, reconcile(info)) break } setStore( "message", - event.properties.info.sessionID, + info.sessionID, produce((draft) => { - draft.splice(result.index, 0, event.properties.info) + draft.splice(result.index, 0, info) }), ) break diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 6358c6c5e9b0..b40e9e4e34c0 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -324,6 +324,7 @@ export namespace MessageV2 { system: z.string().optional(), tools: z.record(z.string(), z.boolean()).optional(), variant: z.string().optional(), + clientMessageID: z.string().optional(), }).meta({ ref: "UserMessage", }) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index e0861c4df527..58092e618725 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -842,7 +842,7 @@ export namespace SessionPrompt { : undefined) const info: MessageV2.Info = { - id: input.messageID ?? Identifier.ascending("message"), + id: Identifier.ascending("message"), role: "user", sessionID: input.sessionID, time: { @@ -853,6 +853,7 @@ export namespace SessionPrompt { model, system: input.system, variant, + clientMessageID: input.messageID, } using _ = defer(() => InstructionPrompt.clear(info.id)) @@ -1192,6 +1193,7 @@ export namespace SessionPrompt { agent: input.agent, model: input.model, messageID: input.messageID, + clientMessageID: input.messageID, variant: input.variant, }, { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 0cf70241ef6f..246e47a27cc2 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -121,6 +121,7 @@ export type UserMessage = { [key: string]: boolean } variant?: string + clientMessageID?: string } export type ProviderAuthError = { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index d179ed8b8c4e..7143399448a1 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -6164,6 +6164,9 @@ }, "variant": { "type": "string" + }, + "clientMessageID": { + "type": "string" } }, "required": ["id", "sessionID", "role", "time", "agent", "model"]