115 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			115 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package prompt
 | |
| 
 | |
| import (
 | |
|     "errors"
 | |
|     "io/fs"
 | |
|     "os"
 | |
|     "path/filepath"
 | |
|     "strings"
 | |
| 
 | |
|     "gopkg.in/yaml.v3"
 | |
| )
 | |
| 
 | |
| var (
 | |
|     loadedRoles   map[string]RoleDefinition
 | |
|     defaultInstr  string
 | |
| )
 | |
| 
 | |
| // Initialize loads roles and default instructions from the configured directory.
 | |
| // dir: base directory (e.g., /etc/chorus/prompts)
 | |
| // defaultPath: optional explicit path to defaults file; if empty, will look for defaults.md or defaults.txt in dir.
 | |
| func Initialize(dir string, defaultPath string) error {
 | |
|     loadedRoles = make(map[string]RoleDefinition)
 | |
| 
 | |
|     // Load roles from all YAML files under dir
 | |
|     if dir != "" {
 | |
|         _ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
 | |
|             if err != nil || d == nil || d.IsDir() {
 | |
|                 return nil
 | |
|             }
 | |
|             name := d.Name()
 | |
|             if strings.HasSuffix(name, ".yaml") || strings.HasSuffix(name, ".yml") {
 | |
|                 _ = loadRolesFile(path)
 | |
|             }
 | |
|             return nil
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     // Load default instructions
 | |
|     if defaultPath == "" && dir != "" {
 | |
|         // Try defaults.md then defaults.txt in the directory
 | |
|         tryPaths := []string{
 | |
|             filepath.Join(dir, "defaults.md"),
 | |
|             filepath.Join(dir, "defaults.txt"),
 | |
|         }
 | |
|         for _, p := range tryPaths {
 | |
|             if b, err := os.ReadFile(p); err == nil {
 | |
|                 defaultInstr = string(b)
 | |
|                 break
 | |
|             }
 | |
|         }
 | |
|     } else if defaultPath != "" {
 | |
|         if b, err := os.ReadFile(defaultPath); err == nil {
 | |
|             defaultInstr = string(b)
 | |
|         }
 | |
|     }
 | |
|     return nil
 | |
| }
 | |
| 
 | |
| func loadRolesFile(path string) error {
 | |
|     data, err := os.ReadFile(path)
 | |
|     if err != nil {
 | |
|         return err
 | |
|     }
 | |
|     var rf RolesFile
 | |
|     if err := yaml.Unmarshal(data, &rf); err != nil {
 | |
|         return err
 | |
|     }
 | |
|     for id, def := range rf.Roles {
 | |
|         def.ID = id
 | |
|         loadedRoles[id] = def
 | |
|     }
 | |
|     return nil
 | |
| }
 | |
| 
 | |
| // GetRole returns the role definition by ID if loaded.
 | |
| func GetRole(id string) (RoleDefinition, bool) {
 | |
|     r, ok := loadedRoles[id]
 | |
|     return r, ok
 | |
| }
 | |
| 
 | |
| // ListRoles returns IDs of loaded roles.
 | |
| func ListRoles() []string {
 | |
|     ids := make([]string, 0, len(loadedRoles))
 | |
|     for id := range loadedRoles {
 | |
|         ids = append(ids, id)
 | |
|     }
 | |
|     return ids
 | |
| }
 | |
| 
 | |
| // GetDefaultInstructions returns the loaded default instructions (may be empty if not present on disk).
 | |
| func GetDefaultInstructions() string {
 | |
|     return defaultInstr
 | |
| }
 | |
| 
 | |
| // ComposeSystemPrompt concatenates the role system prompt (S) with default instructions (D).
 | |
| func ComposeSystemPrompt(roleID string) (string, error) {
 | |
|     r, ok := GetRole(roleID)
 | |
|     if !ok {
 | |
|         return "", errors.New("role not found: " + roleID)
 | |
|     }
 | |
|     s := strings.TrimSpace(r.SystemPrompt)
 | |
|     d := strings.TrimSpace(defaultInstr)
 | |
|     switch {
 | |
|     case s != "" && d != "":
 | |
|         return s + "\n\n" + d, nil
 | |
|     case s != "":
 | |
|         return s, nil
 | |
|     case d != "":
 | |
|         return d, nil
 | |
|     default:
 | |
|         return "", nil
 | |
|     }
 | |
| }
 | |
| 
 | 
