provider/openai: - Fix doubled tool call args (argsComplete flag): Ollama sends complete args in the first streaming chunk then repeats them as delta, causing doubled JSON and 400 errors in elfs - Handle fs: prefix (gemma4 uses fs:grep instead of fs.grep) - Add Reasoning field support for Ollama thinking output cmd/gnoma: - Early TTY detection so logger is created with correct destination before any component gets a reference to it (fixes slog WARN bleed into TUI textarea) permission: - Exempt spawn_elfs and agent tools from safety scanner: elf prompt text may legitimately mention .env/.ssh/credentials patterns and should not be blocked tui/app: - /init retry chain: no-tool-calls → spawn_elfs nudge → write nudge (ask for plain text output) → TUI fallback write from streamBuf - looksLikeAgentsMD + extractMarkdownDoc: validate and clean fallback content before writing (reject refusals, strip narrative preambles) - Collapse thinking output to 3 lines; ctrl+o to expand (live stream and committed messages) - Stream-level filter for model pseudo-tool-call blocks: suppresses <<tool_code>>...</tool_code>> and <<function_call>>...<tool_call|> from entering streamBuf across chunk boundaries - sanitizeAssistantText regex covers both block formats - Reset streamFilterClose at every turn start
60 lines
1.7 KiB
Go
60 lines
1.7 KiB
Go
package context
|
|
|
|
import (
|
|
"somegit.dev/Owlibou/gnoma/internal/message"
|
|
)
|
|
|
|
// TruncateStrategy drops oldest messages while preserving:
|
|
// - System prompt (first message if role=system)
|
|
// - Recent messages (last N turns)
|
|
// Fast, cheap, no LLM call. Used as default and emergency fallback.
|
|
type TruncateStrategy struct {
|
|
// KeepRecent is the number of recent messages to always preserve.
|
|
// Default: 10 (5 user + 5 assistant turns)
|
|
KeepRecent int
|
|
}
|
|
|
|
func NewTruncateStrategy() *TruncateStrategy {
|
|
return &TruncateStrategy{KeepRecent: 10}
|
|
}
|
|
|
|
func (s *TruncateStrategy) Compact(messages []message.Message, budget int64) ([]message.Message, error) {
|
|
if len(messages) <= s.KeepRecent {
|
|
return messages, nil // nothing to compact
|
|
}
|
|
|
|
keepRecent := s.KeepRecent
|
|
if keepRecent > len(messages) {
|
|
keepRecent = len(messages)
|
|
}
|
|
|
|
// Separate system prompt (if present) from history
|
|
var systemMsgs []message.Message
|
|
var history []message.Message
|
|
|
|
for i, m := range messages {
|
|
if i == 0 && m.Role == message.RoleSystem {
|
|
systemMsgs = append(systemMsgs, m)
|
|
} else {
|
|
history = append(history, m)
|
|
}
|
|
}
|
|
|
|
// Keep only the most recent messages from history
|
|
if len(history) > keepRecent {
|
|
// Add a compaction boundary marker
|
|
marker := message.NewUserText("[Earlier conversation was summarized to save context]")
|
|
ack := message.NewAssistantText("Understood, I'll continue from here.")
|
|
|
|
// Adjust split to never orphan tool results (the assistant message with
|
|
// matching tool calls must stay in the recent window with its results).
|
|
splitAt := safeSplitPoint(history, len(history)-keepRecent)
|
|
recent := history[splitAt:]
|
|
result := append(systemMsgs, marker, ack)
|
|
result = append(result, recent...)
|
|
return result, nil
|
|
}
|
|
|
|
return messages, nil
|
|
}
|