Files
gnoma/internal/context/truncate.go
vikingowl cb2d63d06f feat: Ollama/gemma4 compat — /init flow, stream filter, safety fixes
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
2026-04-05 19:24:51 +02:00

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
}