Add fallback PromptKit profile assets
This commit is contained in:
@@ -21,8 +21,9 @@ type AssetSource struct {
|
||||
}
|
||||
|
||||
type AssetRegistry struct {
|
||||
prompts []AssetSource
|
||||
schemas []AssetSource
|
||||
prompts []AssetSource
|
||||
schemas []AssetSource
|
||||
fallbackProfiles []AssetSource
|
||||
}
|
||||
|
||||
type AssetHashPart struct {
|
||||
@@ -58,6 +59,20 @@ func (r *AssetRegistry) RegisterSchemaFS(fsys fs.FS, root string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterFallbackProfileFS registers profile assets that PromptKit uses only
|
||||
// when an operator-configured source does not provide a matching profile.
|
||||
func (r *AssetRegistry) RegisterFallbackProfileFS(fsys fs.FS, root string) error {
|
||||
if r == nil {
|
||||
return fmt.Errorf("asset registry must not be nil")
|
||||
}
|
||||
source, err := newAssetSource(fsys, root)
|
||||
if err != nil {
|
||||
return fmt.Errorf("register fallback profile assets: %w", err)
|
||||
}
|
||||
r.fallbackProfiles = append(r.fallbackProfiles, source)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *AssetRegistry) PromptFS() (fs.FS, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("asset registry must not be nil")
|
||||
@@ -72,19 +87,76 @@ func (r *AssetRegistry) SchemaFS() (fs.FS, error) {
|
||||
return flattenAssetSources(r.schemas)
|
||||
}
|
||||
|
||||
func (r *AssetRegistry) FallbackProfileFS() (fs.FS, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("asset registry must not be nil")
|
||||
}
|
||||
return flattenAssetSources(r.fallbackProfiles)
|
||||
}
|
||||
|
||||
// FallbackProfileDigest returns a deterministic, non-secret identity for the
|
||||
// flattened fallback profile assets.
|
||||
func (r *AssetRegistry) FallbackProfileDigest() (string, error) {
|
||||
_, digest, _, err := r.fallbackProfileAssets()
|
||||
return digest, err
|
||||
}
|
||||
|
||||
func (r *AssetRegistry) PromptKitOptions() ([]promptkit.Option, error) {
|
||||
options, _, err := r.promptKitOptions()
|
||||
return options, err
|
||||
}
|
||||
|
||||
func (r *AssetRegistry) promptKitOptions() ([]promptkit.Option, string, error) {
|
||||
promptFS, err := r.PromptFS()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prepare prompt assets: %w", err)
|
||||
return nil, "", fmt.Errorf("prepare prompt assets: %w", err)
|
||||
}
|
||||
schemaFS, err := r.SchemaFS()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("prepare schema assets: %w", err)
|
||||
return nil, "", fmt.Errorf("prepare schema assets: %w", err)
|
||||
}
|
||||
return []promptkit.Option{
|
||||
options := []promptkit.Option{
|
||||
promptkit.WithPromptFS(promptFS, "."),
|
||||
promptkit.WithSchemaFS(schemaFS, "."),
|
||||
}, nil
|
||||
}
|
||||
fallbackFS, fallbackDigest, hasFallback, err := r.fallbackProfileAssets()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if hasFallback {
|
||||
options = append(options, promptkit.WithFallbackProfileFS(fallbackFS, "."))
|
||||
}
|
||||
return options, fallbackDigest, nil
|
||||
}
|
||||
|
||||
func (r *AssetRegistry) promptKitFallbackProfileOption() (promptkit.Option, bool, error) {
|
||||
fallbackFS, _, hasFallback, err := r.fallbackProfileAssets()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !hasFallback {
|
||||
return nil, false, nil
|
||||
}
|
||||
return promptkit.WithFallbackProfileFS(fallbackFS, "."), true, nil
|
||||
}
|
||||
|
||||
func (r *AssetRegistry) fallbackProfileAssets() (fs.FS, string, bool, error) {
|
||||
if r == nil {
|
||||
return nil, "", false, fmt.Errorf("asset registry must not be nil")
|
||||
}
|
||||
if len(r.fallbackProfiles) == 0 {
|
||||
empty := sha256.Sum256([]byte("notarius:fallback-profile-assets:empty"))
|
||||
return nil, "sha256:" + hex.EncodeToString(empty[:]), false, nil
|
||||
}
|
||||
fallbackFS, err := r.FallbackProfileFS()
|
||||
if err != nil {
|
||||
return nil, "", false, fmt.Errorf("prepare fallback profile assets: %w", err)
|
||||
}
|
||||
digest, err := hashAssetFilesystem(fallbackFS)
|
||||
if err != nil {
|
||||
return nil, "", false, err
|
||||
}
|
||||
return fallbackFS, digest, true, nil
|
||||
}
|
||||
|
||||
func HashAssets(parts []AssetHashPart) (string, error) {
|
||||
@@ -117,6 +189,28 @@ func HashAssets(parts []AssetHashPart) (string, error) {
|
||||
return "sha256:" + hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func hashAssetFilesystem(fsys fs.FS) (string, error) {
|
||||
var parts []AssetHashPart
|
||||
err := fs.WalkDir(fsys, ".", func(name string, entry fs.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
if entry.IsDir() {
|
||||
return nil
|
||||
}
|
||||
parts = append(parts, AssetHashPart{FS: fsys, Path: name})
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("walk assets for digest: %w", err)
|
||||
}
|
||||
if len(parts) > 0 {
|
||||
return HashAssets(parts)
|
||||
}
|
||||
empty := sha256.Sum256([]byte("notarius:fallback-profile-assets:empty"))
|
||||
return "sha256:" + hex.EncodeToString(empty[:]), nil
|
||||
}
|
||||
|
||||
func newAssetSource(fsys fs.FS, root string) (AssetSource, error) {
|
||||
if fsys == nil {
|
||||
return AssetSource{}, fmt.Errorf("filesystem must not be nil")
|
||||
@@ -248,6 +342,9 @@ func (m assetMapFS) dirEntries(dir string) []fs.DirEntry {
|
||||
children[childName] = entry
|
||||
}
|
||||
if len(children) == 0 {
|
||||
if dir == "." {
|
||||
return []fs.DirEntry{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
names := make([]string, 0, len(children))
|
||||
|
||||
Reference in New Issue
Block a user