fix(discord): Implement character-aware message splitting and soften memory warnings
This commit is contained in:
parent
5ac9a46f59
commit
e60728cf96
2 changed files with 71 additions and 67 deletions
|
|
@ -740,7 +740,7 @@ func (al *AgentLoop) maybeSummarize(sessionKey, channel, chatID string) {
|
||||||
al.bus.PublishOutbound(bus.OutboundMessage{
|
al.bus.PublishOutbound(bus.OutboundMessage{
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
ChatID: chatID,
|
ChatID: chatID,
|
||||||
Content: "⚠️ Memory threshold reached. Optimizing conversation history...",
|
Content: "🔄 Context optimized. (Memory threshold reached)",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
al.summarizeSession(sessionKey)
|
al.summarizeSession(sessionKey)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
|
|
@ -120,97 +119,102 @@ func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro
|
||||||
// splitMessage splits long messages into chunks, preserving code block integrity
|
// splitMessage splits long messages into chunks, preserving code block integrity
|
||||||
// Uses natural boundaries (newlines, spaces) and extends messages slightly to avoid breaking code blocks
|
// Uses natural boundaries (newlines, spaces) and extends messages slightly to avoid breaking code blocks
|
||||||
func splitMessage(content string, limit int) []string {
|
func splitMessage(content string, limit int) []string {
|
||||||
var messages []string
|
if limit > 1900 {
|
||||||
|
limit = 1900
|
||||||
|
}
|
||||||
|
runes := []rune(content)
|
||||||
|
if len(runes) <= limit {
|
||||||
|
return []string{content}
|
||||||
|
}
|
||||||
|
|
||||||
for len(content) > 0 {
|
var chunks []string
|
||||||
if len(content) <= limit {
|
for len(runes) > 0 {
|
||||||
messages = append(messages, content)
|
if len(runes) <= limit {
|
||||||
|
chunks = append(chunks, string(runes))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
msgEnd := limit
|
splitAt := limit
|
||||||
|
|
||||||
// Find natural split point within the limit
|
// Look for natural split points (newlines) in a window
|
||||||
msgEnd = findLastNewline(content[:limit], 200)
|
window := 300
|
||||||
if msgEnd <= 0 {
|
if splitAt < window {
|
||||||
msgEnd = findLastSpace(content[:limit], 100)
|
window = splitAt
|
||||||
}
|
|
||||||
if msgEnd <= 0 {
|
|
||||||
msgEnd = limit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this would end with an incomplete code block
|
foundNatural := false
|
||||||
candidate := content[:msgEnd]
|
for i := splitAt - 1; i >= splitAt-window; i-- {
|
||||||
unclosedIdx := findLastUnclosedCodeBlock(candidate)
|
if runes[i] == '\n' {
|
||||||
|
splitAt = i + 1
|
||||||
if unclosedIdx >= 0 {
|
foundNatural = true
|
||||||
// Message would end with incomplete code block
|
break
|
||||||
// Try to extend to include the closing ``` (with some buffer)
|
|
||||||
extendedLimit := limit + 500 // Allow 500 char buffer for code blocks
|
|
||||||
if len(content) > extendedLimit {
|
|
||||||
closingIdx := findNextClosingCodeBlock(content, msgEnd)
|
|
||||||
if closingIdx > 0 && closingIdx <= extendedLimit {
|
|
||||||
// Extend to include the closing ```
|
|
||||||
msgEnd = closingIdx
|
|
||||||
} else {
|
|
||||||
// Can't find closing, split before the code block
|
|
||||||
msgEnd = findLastNewline(content[:unclosedIdx], 200)
|
|
||||||
if msgEnd <= 0 {
|
|
||||||
msgEnd = findLastSpace(content[:unclosedIdx], 100)
|
|
||||||
}
|
|
||||||
if msgEnd <= 0 {
|
|
||||||
msgEnd = unclosedIdx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Remaining content fits within extended limit
|
|
||||||
msgEnd = len(content)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if msgEnd <= 0 {
|
if !foundNatural {
|
||||||
msgEnd = limit
|
for i := splitAt - 1; i >= splitAt-window/2; i-- {
|
||||||
|
if runes[i] == ' ' || runes[i] == '\t' {
|
||||||
|
splitAt = i + 1
|
||||||
|
foundNatural = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messages = append(messages, content[:msgEnd])
|
// Check for unclosed code blocks
|
||||||
content = strings.TrimSpace(content[msgEnd:])
|
chunkCandidate := runes[:splitAt]
|
||||||
|
if isInsideCodeBlock(chunkCandidate) {
|
||||||
|
// Try to find the closing code block within extended limit
|
||||||
|
extendedLimit := limit + 300
|
||||||
|
if extendedLimit > 2000 {
|
||||||
|
extendedLimit = 2000
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages
|
foundClosing := false
|
||||||
|
for i := splitAt; i < len(runes)-2 && i < extendedLimit-3; i++ {
|
||||||
|
if runes[i] == '`' && runes[i+1] == '`' && runes[i+2] == '`' {
|
||||||
|
splitAt = i + 3
|
||||||
|
foundClosing = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundClosing {
|
||||||
|
// Can't find closing within reasonable limit, split before the block
|
||||||
|
for i := splitAt - 1; i >= 0; i-- {
|
||||||
|
if i+2 < len(runes) && runes[i] == '`' && runes[i+1] == '`' && runes[i+2] == '`' {
|
||||||
|
if i > 0 {
|
||||||
|
splitAt = i
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if splitAt <= 0 {
|
||||||
|
splitAt = limit
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks = append(chunks, string(runes[:splitAt]))
|
||||||
|
runes = runes[splitAt:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
// findLastUnclosedCodeBlock finds the last opening ``` that doesn't have a closing ```
|
func isInsideCodeBlock(runes []rune) bool {
|
||||||
// Returns the position of the opening ``` or -1 if all code blocks are complete
|
|
||||||
func findLastUnclosedCodeBlock(text string) int {
|
|
||||||
count := 0
|
count := 0
|
||||||
lastOpenIdx := -1
|
for i := 0; i < len(runes)-2; i++ {
|
||||||
|
if runes[i] == '`' && runes[i+1] == '`' && runes[i+2] == '`' {
|
||||||
for i := 0; i < len(text); i++ {
|
|
||||||
if i+2 < len(text) && text[i] == '`' && text[i+1] == '`' && text[i+2] == '`' {
|
|
||||||
if count == 0 {
|
|
||||||
lastOpenIdx = i
|
|
||||||
}
|
|
||||||
count++
|
count++
|
||||||
i += 2
|
i += 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return count%2 != 0
|
||||||
// If odd number of ``` markers, last one is unclosed
|
|
||||||
if count%2 == 1 {
|
|
||||||
return lastOpenIdx
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findNextClosingCodeBlock finds the next closing ``` starting from a position
|
func _unused_marker_() {
|
||||||
// Returns the position after the closing ``` or -1 if not found
|
|
||||||
func findNextClosingCodeBlock(text string, startIdx int) int {
|
|
||||||
for i := startIdx; i < len(text); i++ {
|
|
||||||
if i+2 < len(text) && text[i] == '`' && text[i+1] == '`' && text[i+2] == '`' {
|
|
||||||
return i + 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findLastNewline finds the last newline character within the last N characters
|
// findLastNewline finds the last newline character within the last N characters
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue