package aiprompts import ( "regexp" "strings" "github.com/descrybe/descrybe-v2/apps/api/internal/security" ) // varToken matches {{name}} with optional whitespace. Only [a-z0-9_]+ names. var varToken = regexp.MustCompile(`\{\{\s*([a-z][a-z0-9_]*)\s*\}\}`) // Vars is the substitution map for Render (keys without braces). type Vars map[string]string // Render replaces {{var}} tokens. Unknown variables become empty string (safe, deterministic). // Templates are sanitized before render; values should already be sanitized by callers. func Render(template string, vars Vars) string { template = strings.TrimSpace(template) if template == "" { return "" } return varToken.ReplaceAllStringFunc(template, func(match string) string { sub := varToken.FindStringSubmatch(match) if len(sub) < 2 { return "" } name := sub[1] if vars == nil { return "" } return vars[name] }) } // SanitizeTemplate cleans and bounds a stored prompt template. func SanitizeTemplate(s string, maxRunes int) string { return security.SanitizePrompt(s, maxRunes) } // ExtractVariables returns unique variable names found in template (sorted order of first appearance). func ExtractVariables(template string) []string { matches := varToken.FindAllStringSubmatch(template, -1) if len(matches) == 0 { return nil } seen := map[string]struct{}{} out := make([]string, 0, len(matches)) for _, m := range matches { if len(m) < 2 { continue } name := m[1] if _, ok := seen[name]; ok { continue } seen[name] = struct{}{} out = append(out, name) } return out }