Move D&D spell assets into extractor module

This commit is contained in:
2026-07-04 00:39:31 +00:00
parent 07b3264b6b
commit ac14667797
16 changed files with 227 additions and 168 deletions

View File

@@ -5,6 +5,7 @@ import (
"embed"
"encoding/hex"
"fmt"
"io/fs"
"path"
"sort"
"strings"
@@ -17,7 +18,6 @@ var embeddedAssets embed.FS
const (
SourceBuiltin = "builtin"
VersionV1 = "v1"
DNDSpellsPromptID = "dnd.spells"
TestGenericPromptID = "test.generic"
)
@@ -41,21 +41,23 @@ func (m Metadata) DiagnosticsMap() map[string]any {
}
}
type definition struct {
id string
version string
embeddedDir string
systemPath string
userPath string
// Definition identifies a caller-owned system/user prompt bundle.
type Definition struct {
PromptID string
Version string
EmbeddedPath string
SystemPath string
UserPath string
}
type compiledPrompt struct {
// Bundle is a compiled system/user prompt pair.
type Bundle struct {
systemTmpl *template.Template
userTmpl *template.Template
metadata Metadata
}
var promptRegistry map[string]compiledPrompt
var promptRegistry map[string]*Bundle
var sharedHardening string
func init() {
@@ -65,30 +67,23 @@ func init() {
panic(err)
}
defs := []definition{
defs := []Definition{
{
id: DNDSpellsPromptID,
version: VersionV1,
embeddedDir: "assets/dnd/spells",
systemPath: "assets/dnd/spells/system.md",
userPath: "assets/dnd/spells/user.md",
},
{
id: TestGenericPromptID,
version: VersionV1,
embeddedDir: "assets/test/generic",
systemPath: "assets/test/generic/system.md",
userPath: "assets/test/generic/user.md",
PromptID: TestGenericPromptID,
Version: VersionV1,
EmbeddedPath: "assets/test/generic",
SystemPath: "assets/test/generic/system.md",
UserPath: "assets/test/generic/user.md",
},
}
promptRegistry = make(map[string]compiledPrompt, len(defs))
promptRegistry = make(map[string]*Bundle, len(defs))
for _, def := range defs {
compiled, compileErr := compilePrompt(def)
compiled, compileErr := LoadBundle(embeddedAssets, def)
if compileErr != nil {
panic(compileErr)
}
promptRegistry[def.id] = compiled
promptRegistry[compiled.metadata.PromptID] = compiled
}
}
@@ -138,51 +133,68 @@ func readAsset(assetPath string) (string, error) {
return string(content), nil
}
func compilePrompt(def definition) (compiledPrompt, error) {
if strings.TrimSpace(def.id) == "" {
return compiledPrompt{}, fmt.Errorf("prompt id must not be empty")
// LoadBundle compiles a system/user prompt bundle from a caller-owned filesystem.
func LoadBundle(fsys fs.FS, def Definition) (*Bundle, error) {
promptID := strings.TrimSpace(def.PromptID)
version := strings.TrimSpace(def.Version)
embeddedPath := strings.TrimSpace(def.EmbeddedPath)
systemPath := strings.TrimSpace(def.SystemPath)
userPath := strings.TrimSpace(def.UserPath)
if promptID == "" {
return nil, fmt.Errorf("prompt id must not be empty")
}
if strings.TrimSpace(def.version) == "" {
return compiledPrompt{}, fmt.Errorf("prompt version must not be empty")
if version == "" {
return nil, fmt.Errorf("prompt version must not be empty")
}
if strings.TrimSpace(def.embeddedDir) == "" {
return compiledPrompt{}, fmt.Errorf("prompt embedded path must not be empty")
if embeddedPath == "" {
return nil, fmt.Errorf("prompt embedded path must not be empty")
}
systemSource, err := readAsset(def.systemPath)
systemSource, err := readPromptAsset(fsys, systemPath)
if err != nil {
return compiledPrompt{}, err
return nil, err
}
userSource, err := readAsset(def.userPath)
userSource, err := readPromptAsset(fsys, userPath)
if err != nil {
return compiledPrompt{}, err
return nil, err
}
funcs := template.FuncMap{
"hardening": func() string { return sharedHardening },
}
systemTmpl, err := template.New(path.Base(def.systemPath)).Option("missingkey=error").Funcs(funcs).Parse(systemSource)
systemTmpl, err := template.New(path.Base(systemPath)).Option("missingkey=error").Funcs(funcs).Parse(systemSource)
if err != nil {
return compiledPrompt{}, fmt.Errorf("parse embedded system prompt %q: %w", def.systemPath, err)
return nil, fmt.Errorf("parse embedded system prompt %q: %w", systemPath, err)
}
userTmpl, err := template.New(path.Base(def.userPath)).Option("missingkey=error").Funcs(funcs).Parse(userSource)
userTmpl, err := template.New(path.Base(userPath)).Option("missingkey=error").Funcs(funcs).Parse(userSource)
if err != nil {
return compiledPrompt{}, fmt.Errorf("parse embedded user prompt %q: %w", def.userPath, err)
return nil, fmt.Errorf("parse embedded user prompt %q: %w", userPath, err)
}
hashInput := systemSource + "\n\n" + userSource
hash := sha256.Sum256([]byte(hashInput))
metadata := Metadata{
PromptID: strings.TrimSpace(def.id),
PromptVersion: strings.TrimSpace(def.version),
PromptID: promptID,
PromptVersion: version,
PromptSource: SourceBuiltin,
EmbeddedPath: strings.TrimSpace(def.embeddedDir),
EmbeddedPath: embeddedPath,
SHA256: "sha256:" + hex.EncodeToString(hash[:]),
}
return compiledPrompt{
return &Bundle{
systemTmpl: systemTmpl,
userTmpl: userTmpl,
metadata: metadata,
}, nil
}
func readPromptAsset(fsys fs.FS, assetPath string) (string, error) {
if strings.TrimSpace(assetPath) == "" {
return "", fmt.Errorf("prompt asset path must not be empty")
}
content, err := fs.ReadFile(fsys, assetPath)
if err != nil {
return "", fmt.Errorf("read embedded prompt asset %q: %w", assetPath, err)
}
return string(content), nil
}