Problem
The backend ChatMessage Prisma model already has a stable UUID id field, but the Pydantic ChatMessage model strips it in from_db(). This means:
- Hydrated messages (from REST API) get synthetic IDs like
${sessionId}-${index}
- Streamed messages (from AI SDK via SSE) get their own auto-generated UUIDs
- Same message, different ID depending on the source
The frontend currently works around this with a content fingerprint deduplication function (deduplicateMessages in helpers.ts) that compares consecutive assistant messages by text content. This is fragile and adds complexity.
Root Cause
The backend Pydantic model (backend/copilot/model.py:ChatMessage) does not include the id field, even though the Prisma schema already has it as a UUID primary key.
Proposed Solution
Backend
- Add
id: str | None = None to the Pydantic ChatMessage model
- Thread it through
from_db(): id=prisma_message.id
- Include
id in REST responses (session history endpoint)
- Include
id in SSE stream events (when saving messages during streaming)
Frontend
- Update
convertChatSessionMessagesToUiMessages to use msg.id from the API response instead of ${sessionId}-${index}
- Align AI SDK streaming to use the same IDs when available
- Simplify or remove the
deduplicateMessages fingerprint logic once IDs are stable
Files to Change
Backend:
backend/copilot/model.py - Add id field to ChatMessage, update from_db()
- Chat REST endpoints - Ensure
id is serialized in responses
- SSE streaming - Include message
id in stream events
Frontend:
copilot/helpers/convertChatSessionToUiMessages.ts - Use backend id instead of synthetic
copilot/helpers.ts - Simplify deduplicateMessages (may become unnecessary)
copilot/useCopilotStream.ts - Align hydrated + streamed message IDs
Context
Discussion: https://discord.com/channels/1126875755960336515/1126875756925046928/1478360192495128708
Originated from Zamil's review of Ubbe's SSE proxy removal PR (#12254) - the dedup logic raised questions about why IDs aren't aligned between backend and frontend.
Requested by
@majdyz
Problem
The backend
ChatMessagePrisma model already has a stable UUIDidfield, but the PydanticChatMessagemodel strips it infrom_db(). This means:${sessionId}-${index}The frontend currently works around this with a content fingerprint deduplication function (
deduplicateMessagesinhelpers.ts) that compares consecutive assistant messages by text content. This is fragile and adds complexity.Root Cause
The backend Pydantic model (
backend/copilot/model.py:ChatMessage) does not include theidfield, even though the Prisma schema already has it as a UUID primary key.Proposed Solution
Backend
id: str | None = Noneto the PydanticChatMessagemodelfrom_db():id=prisma_message.ididin REST responses (session history endpoint)idin SSE stream events (when saving messages during streaming)Frontend
convertChatSessionMessagesToUiMessagesto usemsg.idfrom the API response instead of${sessionId}-${index}deduplicateMessagesfingerprint logic once IDs are stableFiles to Change
Backend:
backend/copilot/model.py- Addidfield toChatMessage, updatefrom_db()idis serialized in responsesidin stream eventsFrontend:
copilot/helpers/convertChatSessionToUiMessages.ts- Use backendidinstead of syntheticcopilot/helpers.ts- SimplifydeduplicateMessages(may become unnecessary)copilot/useCopilotStream.ts- Align hydrated + streamed message IDsContext
Discussion: https://discord.com/channels/1126875755960336515/1126875756925046928/1478360192495128708
Originated from Zamil's review of Ubbe's SSE proxy removal PR (#12254) - the dedup logic raised questions about why IDs aren't aligned between backend and frontend.
Requested by
@majdyz