From df2333b7329de54490f44b023d35ac7e44bd0748 Mon Sep 17 00:00:00 2001 From: Trung Nguyen Date: Mon, 9 Feb 2026 13:05:26 +0100 Subject: [PATCH] fix: persist multi_content for user messages User message file attachments (multi_content) were not surviving app restarts because UserMessageEvent only carried the text content string. The multi-content data was present in memory but lost when persisted to SQLite via the event handler. --- pkg/runtime/event.go | 12 +++++++----- pkg/runtime/persistent_runtime.go | 2 +- pkg/runtime/runtime.go | 3 ++- pkg/runtime/runtime_test.go | 8 ++++---- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/pkg/runtime/event.go b/pkg/runtime/event.go index 01b26265d..98d170e5e 100644 --- a/pkg/runtime/event.go +++ b/pkg/runtime/event.go @@ -30,14 +30,15 @@ func newAgentContext(agentName string) AgentContext { // UserMessageEvent is sent when a user message is received type UserMessageEvent struct { - Type string `json:"type"` - Message string `json:"message"` - SessionID string `json:"session_id"` - SessionPosition int `json:"session_position"` // Index in session.Messages, -1 if unknown + Type string `json:"type"` + Message string `json:"message"` + MultiContent []chat.MessagePart `json:"multi_content,omitempty"` + SessionID string `json:"session_id"` + SessionPosition int `json:"session_position"` // Index in session.Messages, -1 if unknown AgentContext } -func UserMessage(message, sessionID string, sessionPos ...int) Event { +func UserMessage(message, sessionID string, multiContent []chat.MessagePart, sessionPos ...int) Event { pos := -1 if len(sessionPos) > 0 { pos = sessionPos[0] @@ -45,6 +46,7 @@ func UserMessage(message, sessionID string, sessionPos ...int) Event { return &UserMessageEvent{ Type: "user_message", Message: message, + MultiContent: multiContent, SessionID: sessionID, SessionPosition: pos, AgentContext: newAgentContext(""), diff --git a/pkg/runtime/persistent_runtime.go b/pkg/runtime/persistent_runtime.go index c15b1c092..30df8d7b2 100644 --- a/pkg/runtime/persistent_runtime.go +++ b/pkg/runtime/persistent_runtime.go @@ -93,7 +93,7 @@ func (r *PersistentRuntime) handleEvent(ctx context.Context, sess *session.Sessi streaming.agentName = "" streaming.messageID = 0 - if _, err := r.sessionStore.AddMessage(ctx, e.SessionID, session.UserMessage(e.Message)); err != nil { + if _, err := r.sessionStore.AddMessage(ctx, e.SessionID, session.UserMessage(e.Message, e.MultiContent...)); err != nil { slog.Warn("Failed to persist user message", "session_id", e.SessionID, "error", err) } diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 18ad4574f..e8610106a 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -872,7 +872,8 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c messages := sess.GetMessages(a) if sess.SendUserMessage { - events <- UserMessage(messages[len(messages)-1].Content, sess.ID, len(sess.Messages)-1) + lastMsg := messages[len(messages)-1] + events <- UserMessage(lastMsg.Content, sess.ID, lastMsg.MultiContent, len(sess.Messages)-1) } events <- StreamStarted(sess.ID, a.Name()) diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index 2920c557e..15ae30567 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -278,7 +278,7 @@ func TestSimple(t *testing.T) { AgentInfo("root", "test/mock-model", "", ""), TeamInfo([]AgentDetails{{Name: "root", Provider: "test", Model: "mock-model"}}, "root"), ToolsetInfo(0, false, "root"), - UserMessage("Hi", sess.ID, 0), + UserMessage("Hi", sess.ID, nil, 0), StreamStarted(sess.ID, "root"), AgentChoice("root", "Hello"), MessageAdded(sess.ID, msgAdded.Message, "root"), @@ -316,7 +316,7 @@ func TestMultipleContentChunks(t *testing.T) { AgentInfo("root", "test/mock-model", "", ""), TeamInfo([]AgentDetails{{Name: "root", Provider: "test", Model: "mock-model"}}, "root"), ToolsetInfo(0, false, "root"), - UserMessage("Please greet me", sess.ID, 0), + UserMessage("Please greet me", sess.ID, nil, 0), StreamStarted(sess.ID, "root"), AgentChoice("root", "Hello "), AgentChoice("root", "there, "), @@ -356,7 +356,7 @@ func TestWithReasoning(t *testing.T) { AgentInfo("root", "test/mock-model", "", ""), TeamInfo([]AgentDetails{{Name: "root", Provider: "test", Model: "mock-model"}}, "root"), ToolsetInfo(0, false, "root"), - UserMessage("Hi", sess.ID, 0), + UserMessage("Hi", sess.ID, nil, 0), StreamStarted(sess.ID, "root"), AgentChoiceReasoning("root", "Let me think about this..."), AgentChoiceReasoning("root", " I should respond politely."), @@ -395,7 +395,7 @@ func TestMixedContentAndReasoning(t *testing.T) { AgentInfo("root", "test/mock-model", "", ""), TeamInfo([]AgentDetails{{Name: "root", Provider: "test", Model: "mock-model"}}, "root"), ToolsetInfo(0, false, "root"), - UserMessage("Hi there", sess.ID, 0), + UserMessage("Hi there", sess.ID, nil, 0), StreamStarted(sess.ID, "root"), AgentChoiceReasoning("root", "The user wants a greeting"), AgentChoice("root", "Hello!"),