Formatting only (struct field alignment, import ordering) across the files that didn't comply — no semantic changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
113 lines
2.8 KiB
Go
113 lines
2.8 KiB
Go
package persona
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/VictorVargas/rony-llm-agent/pkg/llm"
|
|
)
|
|
|
|
// Persona defines the personality and behavior of the agent.
|
|
type Persona struct {
|
|
ID string
|
|
Name string
|
|
Tone string
|
|
Style string
|
|
Language string
|
|
Constraints []string
|
|
FewShot []llm.Message
|
|
}
|
|
|
|
// Loader loads personas from files.
|
|
type Loader interface {
|
|
Load(ctx context.Context, configPath string) (Persona, error)
|
|
Discover(ctx context.Context, workdir string) (Persona, error)
|
|
}
|
|
|
|
// ErrPersonaNotFound is returned when a persona file is not found.
|
|
var ErrPersonaNotFound = fmt.Errorf("persona not found")
|
|
|
|
// DefaultPersona returns a basic persona with sensible defaults.
|
|
func DefaultPersona() Persona {
|
|
return Persona{
|
|
ID: "default",
|
|
Name: "Rony",
|
|
Tone: "professional and helpful",
|
|
Style: "clear and concise",
|
|
Language: "the user's language",
|
|
}
|
|
}
|
|
|
|
// AssembleSystemPrompt builds the final system prompt from persona, base prompt, and AGENTS.md.
|
|
func AssembleSystemPrompt(p Persona, agentsMD string) string {
|
|
var parts []string
|
|
|
|
// Base system prompt
|
|
parts = append(parts, "You are an AI agent. Be helpful, accurate, and safe.")
|
|
|
|
// Persona instructions
|
|
if p.Name != "" {
|
|
parts = append(parts, fmt.Sprintf("Your name is %s.", p.Name))
|
|
}
|
|
if p.Tone != "" {
|
|
parts = append(parts, fmt.Sprintf("Use a %s tone.", p.Tone))
|
|
}
|
|
if p.Style != "" {
|
|
parts = append(parts, fmt.Sprintf("Write in a %s style.", p.Style))
|
|
}
|
|
if p.Language != "" {
|
|
if strings.EqualFold(p.Language, "the user's language") {
|
|
parts = append(parts, "Respond in the same language as the user's messages.")
|
|
} else {
|
|
parts = append(parts, fmt.Sprintf("Respond in %s.", p.Language))
|
|
}
|
|
}
|
|
for _, c := range p.Constraints {
|
|
parts = append(parts, fmt.Sprintf("CONSTRAINT: %s", c))
|
|
}
|
|
|
|
// AGENTS.md content
|
|
if agentsMD != "" {
|
|
parts = append(parts, "---")
|
|
parts = append(parts, "Project instructions:")
|
|
parts = append(parts, agentsMD)
|
|
}
|
|
|
|
return strings.Join(parts, "\n\n")
|
|
}
|
|
|
|
// DiscoverAgentsMD walks up the directory tree looking for AGENTS.md files.
|
|
func DiscoverAgentsMD(root string) string {
|
|
var parts []string
|
|
current := root
|
|
|
|
for {
|
|
agentsPath := filepath.Join(current, "AGENTS.md")
|
|
if _, err := os.Stat(agentsPath); err == nil {
|
|
data, err := os.ReadFile(agentsPath)
|
|
if err == nil {
|
|
parts = append(parts, string(data))
|
|
}
|
|
}
|
|
|
|
parent := filepath.Dir(current)
|
|
if parent == current || parent == "." {
|
|
break
|
|
}
|
|
current = parent
|
|
}
|
|
|
|
// Also check ~/.config/rony/AGENTS.md
|
|
home, err := os.UserHomeDir()
|
|
if err == nil {
|
|
globalPath := filepath.Join(home, ".config", "rony", "AGENTS.md")
|
|
if data, err := os.ReadFile(globalPath); err == nil {
|
|
parts = append(parts, string(data))
|
|
}
|
|
}
|
|
|
|
return strings.Join(parts, "\n\n")
|
|
}
|