| 1 | package agent |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "fmt" |
| 6 | "strings" |
| 7 | |
| 8 | "reasonix/internal/provider" |
| 9 | ) |
| 10 | |
| 11 | // applyRepeatReminders is advisory only. It never changes execution state, |
| 12 | // blocks a call, marks the turn as needing the user, or requests completion. |
| 13 | func (a *Agent) applyRepeatReminders(calls []provider.ToolCall, results []string) { |
| 14 | for i, call := range calls { |
| 15 | key := strings.ToLower(strings.TrimSpace(call.Name)) + "\x00" + canonicalToolArguments(call.Arguments) |
| 16 | if key != a.turn.repeatKey { |
| 17 | a.turn.repeatKey = key |
| 18 | a.turn.repeatCount = 1 |
| 19 | } else { |
| 20 | a.turn.repeatCount++ |
| 21 | } |
| 22 | switch a.turn.repeatCount { |
| 23 | case 3, 5, 8: |
| 24 | results[i] += fmt.Sprintf("\n\n[repeat reminder] %s was called %d consecutive times with the same arguments. Check whether another call is useful before repeating it again.", call.Name, a.turn.repeatCount) |
| 25 | } |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | func canonicalToolArguments(raw string) string { |
| 30 | var value any |
| 31 | if json.Unmarshal([]byte(raw), &value) != nil { |
| 32 | return strings.TrimSpace(raw) |
| 33 | } |
| 34 | encoded, err := json.Marshal(value) |
| 35 | if err != nil { |
| 36 | return strings.TrimSpace(raw) |
| 37 | } |
| 38 | return string(encoded) |
| 39 | } |
| 40 |