Files
notarius/internal/modules/dnd/shared/assets.go

97 lines
3.3 KiB
Go

package shared
import (
"fmt"
"io/fs"
"strings"
rootassets "gitea.maximumdirect.net/eric/notarius/assets"
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
"gitea.maximumdirect.net/eric/notarius/internal/framework/promptfs"
)
// PromptAssetManifest is the ordered set of assets that make up one prompt.
// Module files are addressed in the owning module filesystem; shared files use
// the names in sharedPromptPaths and are mounted beneath sharedassets.
type PromptAssetManifest struct {
ModuleDir string
ModuleFiles []promptfs.ModulePromptFile
SharedFiles []string
}
var sharedPromptPaths = map[string]string{
"common-dnd-system.md": "prompts/common-dnd-system.md",
"common-dnd-extraction-evidence.md": "prompts/common-dnd-extraction-evidence.md",
"common-dnd-identity.md": "prompts/common-dnd-identity.md",
"common-dnd-transcript-full.md": "prompts/common-dnd-transcript-full.md",
"common-dnd-transcript-chunk.md": "prompts/common-dnd-transcript-chunk.md",
"common-dnd-transcript-windows.md": "prompts/common-dnd-transcript-windows.md",
"common-dnd-references.md": "prompts/common-dnd-references.md",
"common-dnd-npcs.md": "prompts/common-dnd-npcs.md",
"common-dnd-entity-reconciliation.md": "prompts/common-dnd-entity-reconciliation.md",
}
func sharedAssetFS() (fs.FS, error) {
assets, err := fs.Sub(rootassets.FS(), "dnd/shared")
if err != nil {
return nil, fmt.Errorf("scope shared D&D prompt assets: %w", err)
}
return assets, nil
}
func (manifest PromptAssetManifest) PromptFS(moduleFS fs.FS) (fs.FS, error) {
sharedFiles, err := resolveSharedPromptFiles(manifest.SharedFiles)
if err != nil {
return nil, err
}
moduleFiles := append([]promptfs.ModulePromptFile(nil), manifest.ModuleFiles...)
return promptfs.ModulePromptFS(manifest.ModuleDir, moduleFS, moduleFiles, sharedFiles...)
}
func (manifest PromptAssetManifest) Hash(moduleFS fs.FS) (string, error) {
sharedFiles, err := resolveSharedPromptFiles(manifest.SharedFiles)
if err != nil {
return "", err
}
parts := make([]llm.AssetHashPart, 0, len(manifest.ModuleFiles)+len(sharedFiles))
for _, file := range manifest.ModuleFiles {
parts = append(parts, llm.AssetHashPart{FS: moduleFS, Path: file.Path})
}
for _, file := range sharedFiles {
parts = append(parts, llm.AssetHashPart{FS: file.FS, Path: file.Path})
}
return llm.HashAssets(parts)
}
func resolveSharedPromptFiles(names []string) ([]promptfs.SharedPromptFile, error) {
assets, err := sharedAssetFS()
if err != nil {
return nil, err
}
files := make([]promptfs.SharedPromptFile, 0, len(names))
seen := make(map[string]struct{}, len(names))
for _, name := range names {
if name == "" {
return nil, fmt.Errorf("shared prompt asset name must not be empty")
}
if strings.ContainsAny(name, `/\\`) {
return nil, fmt.Errorf("shared prompt asset name %q must not contain path separators", name)
}
if _, ok := seen[name]; ok {
return nil, fmt.Errorf("duplicate shared prompt asset name %q", name)
}
path, ok := sharedPromptPaths[name]
if !ok {
return nil, fmt.Errorf("unknown shared prompt asset name %q", name)
}
seen[name] = struct{}{}
files = append(files, promptfs.SharedPromptFile{
Name: name,
FS: assets,
Path: path,
})
}
return files, nil
}