mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-12 07:16:56 +08:00
feat: integrate NOFXi agent into dev
This commit is contained in:
161
agent/preferences.go
Normal file
161
agent/preferences.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PersistentPreference is a durable user instruction shown in the UI and
|
||||
// injected into the agent context for future conversations.
|
||||
type PersistentPreference struct {
|
||||
ID string `json:"id"`
|
||||
Text string `json:"text"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
}
|
||||
|
||||
func NewPersistentPreference(text string) (PersistentPreference, error) {
|
||||
text = strings.TrimSpace(text)
|
||||
if text == "" {
|
||||
return PersistentPreference{}, fmt.Errorf("text required")
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
return PersistentPreference{
|
||||
ID: now.Format("20060102150405.000000000"),
|
||||
Text: text,
|
||||
CreatedAt: now.Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SessionUserIDFromKey maps a stable user key (for example a UUID string from
|
||||
// auth) to the int64 session id expected by the current agent implementation.
|
||||
func SessionUserIDFromKey(userKey string) int64 {
|
||||
if strings.TrimSpace(userKey) == "" {
|
||||
return 1
|
||||
}
|
||||
h := fnv.New64a()
|
||||
_, _ = h.Write([]byte(userKey))
|
||||
sum := h.Sum64() & 0x7fffffffffffffff
|
||||
if sum == 0 {
|
||||
return 1
|
||||
}
|
||||
return int64(sum)
|
||||
}
|
||||
|
||||
func PreferencesConfigKey(userID int64) string {
|
||||
return fmt.Sprintf("agent_preferences_%d", userID)
|
||||
}
|
||||
|
||||
func (a *Agent) getPersistentPreferences(userID int64) []PersistentPreference {
|
||||
if a.store == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw, err := a.store.GetSystemConfig(PreferencesConfigKey(userID))
|
||||
if err != nil || strings.TrimSpace(raw) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var prefs []PersistentPreference
|
||||
if err := json.Unmarshal([]byte(raw), &prefs); err != nil {
|
||||
a.logger.Warn("failed to parse persistent preferences", "error", err, "user_id", userID)
|
||||
return nil
|
||||
}
|
||||
return prefs
|
||||
}
|
||||
|
||||
func (a *Agent) savePersistentPreferences(userID int64, prefs []PersistentPreference) error {
|
||||
if a.store == nil {
|
||||
return fmt.Errorf("store unavailable")
|
||||
}
|
||||
data, err := json.Marshal(prefs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.store.SetSystemConfig(PreferencesConfigKey(userID), string(data))
|
||||
}
|
||||
|
||||
func (a *Agent) addPersistentPreference(userID int64, text string) ([]PersistentPreference, PersistentPreference, error) {
|
||||
created, err := NewPersistentPreference(text)
|
||||
if err != nil {
|
||||
return nil, PersistentPreference{}, err
|
||||
}
|
||||
prefs := a.getPersistentPreferences(userID)
|
||||
prefs = append([]PersistentPreference{created}, prefs...)
|
||||
if len(prefs) > 20 {
|
||||
prefs = prefs[:20]
|
||||
}
|
||||
if err := a.savePersistentPreferences(userID, prefs); err != nil {
|
||||
return nil, PersistentPreference{}, err
|
||||
}
|
||||
return prefs, created, nil
|
||||
}
|
||||
|
||||
func (a *Agent) updatePersistentPreference(userID int64, match, replacement string) ([]PersistentPreference, *PersistentPreference, error) {
|
||||
match = strings.TrimSpace(match)
|
||||
replacement = strings.TrimSpace(replacement)
|
||||
if match == "" || replacement == "" {
|
||||
return nil, nil, fmt.Errorf("match and replacement are required")
|
||||
}
|
||||
|
||||
prefs := a.getPersistentPreferences(userID)
|
||||
for i := range prefs {
|
||||
if prefs[i].ID == match || strings.Contains(strings.ToLower(prefs[i].Text), strings.ToLower(match)) {
|
||||
prefs[i].Text = replacement
|
||||
if err := a.savePersistentPreferences(userID, prefs); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return prefs, &prefs[i], nil
|
||||
}
|
||||
}
|
||||
return prefs, nil, fmt.Errorf("preference not found")
|
||||
}
|
||||
|
||||
func (a *Agent) deletePersistentPreference(userID int64, match string) ([]PersistentPreference, *PersistentPreference, error) {
|
||||
match = strings.TrimSpace(match)
|
||||
if match == "" {
|
||||
return nil, nil, fmt.Errorf("match required")
|
||||
}
|
||||
|
||||
prefs := a.getPersistentPreferences(userID)
|
||||
filtered := make([]PersistentPreference, 0, len(prefs))
|
||||
var removed *PersistentPreference
|
||||
for i := range prefs {
|
||||
p := prefs[i]
|
||||
if removed == nil && (p.ID == match || strings.Contains(strings.ToLower(p.Text), strings.ToLower(match))) {
|
||||
cp := p
|
||||
removed = &cp
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, p)
|
||||
}
|
||||
if removed == nil {
|
||||
return prefs, nil, fmt.Errorf("preference not found")
|
||||
}
|
||||
if err := a.savePersistentPreferences(userID, filtered); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return filtered, removed, nil
|
||||
}
|
||||
|
||||
func (a *Agent) buildPersistentPreferencesContext(userID int64) string {
|
||||
prefs := a.getPersistentPreferences(userID)
|
||||
if len(prefs) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString("[Persistent User Preferences - follow unless the user explicitly overrides them]\n")
|
||||
for _, pref := range prefs {
|
||||
if strings.TrimSpace(pref.Text) == "" {
|
||||
continue
|
||||
}
|
||||
sb.WriteString("- ")
|
||||
sb.WriteString(pref.Text)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
return strings.TrimSpace(sb.String())
|
||||
}
|
||||
Reference in New Issue
Block a user