conversion.go
package assist
import (
"fmt"
"strings"
"github.com/readysite/readysite/pkg/assistant"
"github.com/readysite/readysite/website/models"
)
// BuildMessages converts conversation history to AI message format.
// Uses summaries for older messages to reduce token usage.
func BuildMessages(conv *models.Conversation) []assistant.Message {
msgs, _ := conv.Messages()
result := make([]assistant.Message, 0, len(msgs))
// Check for existing summaries
summaries := GetAllSummaries(conv.ID)
// Build a set of message IDs that are covered by summaries
summarizedIDs := make(map[string]bool)
for _, summary := range summaries {
// Mark all messages between start and end as summarized
inRange := false
for _, msg := range msgs {
if msg.ID == summary.StartMessageID {
inRange = true
}
if inRange {
summarizedIDs[msg.ID] = true
}
if msg.ID == summary.EndMessageID {
break
}
}
}
// Add summaries as system context if we have any
if len(summaries) > 0 {
var summaryBuilder strings.Builder
for _, summary := range summaries {
fmt.Fprintf(&summaryBuilder, "Previous conversation summary (%d messages): %s\n", summary.MessageCount, summary.Summary)
}
// Add summary as a user message to provide context
// Using user message because some providers don't support system messages mid-conversation
result = append(result, assistant.NewUserMessage("[CONVERSATION CONTEXT]\n"+summaryBuilder.String()+"\nPlease continue our conversation with this context in mind."))
result = append(result, assistant.NewAssistantMessage("I understand the context from our previous discussion. How can I help you now?"))
}
for _, msg := range msgs {
// Skip pending or streaming messages
if msg.Status == "pending" || msg.Status == "streaming" {
continue
}
// Skip messages that have been summarized
if summarizedIDs[msg.ID] {
continue
}
switch msg.Role {
case RoleUser:
content := msg.Content
// Append attached file content
if files, _ := msg.Files(); len(files) > 0 {
var fileContent strings.Builder
for _, file := range files {
if text, err := GetFileContent(file.ID); err == nil {
fileContent.WriteString(fmt.Sprintf("\n\n--- Attached File: %s ---\n%s", file.Name, text))
} else if file.IsImage() {
fileContent.WriteString(fmt.Sprintf("\n\n[Attached Image: %s (%s)]", file.Name, file.MimeType))
} else {
// For non-text, non-image files, note that they're attached but can't be read
fileContent.WriteString(fmt.Sprintf("\n\n[Attached File: %s (%s) - content not readable]", file.Name, file.MimeType))
}
}
if fileContent.Len() > 0 {
content += fileContent.String()
}
}
result = append(result, assistant.NewUserMessage(content))
case RoleAssistant:
toolCalls, _ := GetToolCalls(msg)
if len(toolCalls) > 0 {
// Convert tool calls
aiToolCalls := make([]assistant.ToolCall, len(toolCalls))
for i, tc := range toolCalls {
aiToolCalls[i] = assistant.ToolCall{
ID: tc.ID,
Name: tc.Name,
Arguments: tc.Arguments,
}
}
result = append(result, assistant.NewAssistantToolCallMessage(msg.Content, aiToolCalls))
// Add tool results
for _, tc := range toolCalls {
if tc.Result != "" {
result = append(result, assistant.NewToolResultMessage(tc.ID, tc.Result))
} else if tc.Error != "" {
result = append(result, assistant.NewToolResultMessage(tc.ID, "Error: "+tc.Error))
}
}
} else if msg.Content != "" {
result = append(result, assistant.NewAssistantMessage(msg.Content))
}
}
}
return result
}