diff --git a/apps/agent/entrypoints/sidepanel/index/useChatSession.ts b/apps/agent/entrypoints/sidepanel/index/useChatSession.ts index 4c4f1fb0..a31ede48 100644 --- a/apps/agent/entrypoints/sidepanel/index/useChatSession.ts +++ b/apps/agent/entrypoints/sidepanel/index/useChatSession.ts @@ -304,6 +304,15 @@ export const useChatSession = () => { }), }) + // Remove messages with empty parts (e.g. interrupted assistant responses) + // to prevent AI SDK validation errors on subsequent sends + useEffect(() => { + if (status === 'streaming') return + if (messages.some((m) => !m.parts?.length)) { + setMessages(messages.filter((m) => m.parts?.length > 0)) + } + }, [messages, status, setMessages]) + useNotifyActiveTab({ messages, status, @@ -370,15 +379,14 @@ export const useChatSession = () => { // biome-ignore lint/correctness/useExhaustiveDependencies: only need to run when messages change useEffect(() => { messagesRef.current = messages - if (messages.length > 0) { - // Local storage: save on every change (including during streaming) - // Remote: only save when not streaming to avoid partial message saves + const messagesToSave = messages.filter((m) => m.parts?.length > 0) + if (messagesToSave.length > 0) { if (isLoggedIn) { if (status !== 'streaming') { - saveRemoteConversation(conversationIdRef.current, messages) + saveRemoteConversation(conversationIdRef.current, messagesToSave) } } else { - saveLocalConversation(conversationIdRef.current, messages) + saveLocalConversation(conversationIdRef.current, messagesToSave) } } }, [messages, isLoggedIn, status]) diff --git a/apps/agent/lib/conversations/formatConversationHistory.ts b/apps/agent/lib/conversations/formatConversationHistory.ts index 50ffeb58..8988a2af 100644 --- a/apps/agent/lib/conversations/formatConversationHistory.ts +++ b/apps/agent/lib/conversations/formatConversationHistory.ts @@ -17,6 +17,7 @@ export function formatConversationHistory( return recentMessages .map((msg) => { + if (!msg.parts?.length) return null const role: 'user' | 'assistant' = msg.role === 'user' ? 'user' : 'assistant' const textContent = msg.parts