Capture profile and prompt listing identities
This commit is contained in:
@@ -82,7 +82,11 @@ provider discovery, new caller limits, or stronger catalog availability promises
|
|||||||
|
|
||||||
## Stage 1 — Capture And Read Identity Inventories
|
## Stage 1 — Capture And Read Identity Inventories
|
||||||
|
|
||||||
**Status: Pending.**
|
**Status: Complete (2026-10-09).** Verified with
|
||||||
|
`go test ./internal/configuration ./internal/domain` and
|
||||||
|
`go vet ./internal/configuration ./internal/domain`. Adapter tests cover
|
||||||
|
effective sorted identities, literal keys, partial profiles, empty loads,
|
||||||
|
caller-owned results, and canceled contexts.
|
||||||
|
|
||||||
**Scope:** `internal/domain`, `internal/configuration`, and focused adapter tests.
|
**Scope:** `internal/domain`, `internal/configuration`, and focused adapter tests.
|
||||||
|
|
||||||
|
|||||||
29
internal/configuration/listing.go
Normal file
29
internal/configuration/listing.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (state *loaded) ListProfiles(ctx context.Context) ([]string, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
profiles := append([]string(nil), state.profileIDs...)
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return profiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (state *loaded) ListPrompts(ctx context.Context) ([]domain.PromptIdentity, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
prompts := append([]domain.PromptIdentity(nil), state.promptIDs...)
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return prompts, nil
|
||||||
|
}
|
||||||
142
internal/configuration/listing_test.go
Normal file
142
internal/configuration/listing_test.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
|
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadedListsSortedEffectiveIdentities(t *testing.T) {
|
||||||
|
state, err := Load(mapping("identities", map[string]any{
|
||||||
|
"backends": map[string]any{
|
||||||
|
"local": map[string]any{"endpoint": "http://localhost:8080/v1"},
|
||||||
|
},
|
||||||
|
"profiles": map[string]any{
|
||||||
|
"z/profile": map[string]any{"backend": "local", "model": "z-model"},
|
||||||
|
"partial/profile": map[string]any{"backend": "local"},
|
||||||
|
"a/profile": map[string]any{"backend": "local", "model": "a-model"},
|
||||||
|
" z/profile ": map[string]any{"backend": "local", "model": "spaced-model"},
|
||||||
|
},
|
||||||
|
"prompts": map[string]any{
|
||||||
|
"z/prompt": map[string]any{"v1": listingPromptDefinition()},
|
||||||
|
"a/prompt": map[string]any{"v1": listingPromptDefinition()},
|
||||||
|
" z/prompt ": map[string]any{
|
||||||
|
"2": listingPromptDefinition(),
|
||||||
|
"10": listingPromptDefinition(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles, err := state.ListProfiles(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wantProfiles := []string{" z/profile ", "a/profile", "partial/profile", "z/profile"}
|
||||||
|
if !reflect.DeepEqual(profiles, wantProfiles) {
|
||||||
|
t.Fatalf("profile IDs = %#v, want %#v", profiles, wantProfiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
prompts, err := state.ListPrompts(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wantPrompts := []domain.PromptIdentity{
|
||||||
|
{ID: " z/prompt ", Version: "10"},
|
||||||
|
{ID: " z/prompt ", Version: "2"},
|
||||||
|
{ID: "a/prompt", Version: "v1"},
|
||||||
|
{ID: "z/prompt", Version: "v1"},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(prompts, wantPrompts) {
|
||||||
|
t.Fatalf("prompt identities = %#v, want %#v", prompts, wantPrompts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadedListsAreIndependent(t *testing.T) {
|
||||||
|
state, err := Load(mapping("identities", map[string]any{
|
||||||
|
"profiles": map[string]any{"profile": map[string]any{}},
|
||||||
|
"prompts": map[string]any{"example": map[string]any{"v1": listingPromptDefinition()}},
|
||||||
|
}), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles, err := state.ListProfiles(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
profiles = append(profiles, "caller profile")
|
||||||
|
profiles[0] = "changed profile"
|
||||||
|
|
||||||
|
prompts, err := state.ListPrompts(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
prompts[0].ID = "changed prompt"
|
||||||
|
prompts[0].Version = "changed version"
|
||||||
|
|
||||||
|
profiles, err = state.ListProfiles(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(profiles, []string{"profile"}) {
|
||||||
|
t.Fatalf("caller mutation changed profile inventory: %#v", profiles)
|
||||||
|
}
|
||||||
|
prompts, err = state.ListPrompts(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wantPrompts := []domain.PromptIdentity{{ID: "example", Version: "v1"}}
|
||||||
|
if !reflect.DeepEqual(prompts, wantPrompts) {
|
||||||
|
t.Fatalf("caller mutation changed prompt inventory: %#v", prompts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadedListsEmptyInventory(t *testing.T) {
|
||||||
|
state, err := Load(configkit.Input{Source: configkit.NoSource()}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
profiles, err := state.ListProfiles(context.Background())
|
||||||
|
if err != nil || len(profiles) != 0 {
|
||||||
|
t.Fatalf("empty profile listing = %#v, %v", profiles, err)
|
||||||
|
}
|
||||||
|
prompts, err := state.ListPrompts(context.Background())
|
||||||
|
if err != nil || len(prompts) != 0 {
|
||||||
|
t.Fatalf("empty prompt listing = %#v, %v", prompts, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadedListsHonorCanceledContext(t *testing.T) {
|
||||||
|
state, err := Load(mapping("identities", map[string]any{
|
||||||
|
"profiles": map[string]any{"partial": map[string]any{}},
|
||||||
|
"prompts": map[string]any{"example": map[string]any{"v1": listingPromptDefinition()}},
|
||||||
|
}), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
profiles, err := state.ListProfiles(ctx)
|
||||||
|
if !errors.Is(err, context.Canceled) || len(profiles) != 0 {
|
||||||
|
t.Fatalf("canceled profile listing = %#v, %v", profiles, err)
|
||||||
|
}
|
||||||
|
prompts, err := state.ListPrompts(ctx)
|
||||||
|
if !errors.Is(err, context.Canceled) || len(prompts) != 0 {
|
||||||
|
t.Fatalf("canceled prompt listing = %#v, %v", prompts, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listingPromptDefinition() map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"messages": []any{map[string]any{"role": "user", "content": "Hello."}},
|
||||||
|
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -98,6 +98,8 @@ type loaded struct {
|
|||||||
ambiguous map[string]bool
|
ambiguous map[string]bool
|
||||||
registry *backend.Registry
|
registry *backend.Registry
|
||||||
indexedProfiles map[string]domain.ExecutionProfile
|
indexedProfiles map[string]domain.ExecutionProfile
|
||||||
|
profileIDs []string
|
||||||
|
promptIDs []domain.PromptIdentity
|
||||||
}
|
}
|
||||||
|
|
||||||
var envName = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
var envName = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
||||||
@@ -143,6 +145,12 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
|
|||||||
if err := state.buildExecutionProfiles(); err != nil {
|
if err := state.buildExecutionProfiles(); err != nil {
|
||||||
return nil, fmt.Errorf("load configuration: %w", err)
|
return nil, fmt.Errorf("load configuration: %w", err)
|
||||||
}
|
}
|
||||||
|
state.profileIDs = sortedKeys(state.indexedProfiles)
|
||||||
|
for _, id := range sortedKeys(state.prompts) {
|
||||||
|
for _, version := range sortedKeys(state.prompts[id]) {
|
||||||
|
state.promptIDs = append(state.promptIDs, domain.PromptIdentity{ID: id, Version: version})
|
||||||
|
}
|
||||||
|
}
|
||||||
return state, nil
|
return state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,12 @@ type Artifact struct {
|
|||||||
Hash string
|
Hash string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PromptIdentity identifies one configured prompt version.
|
||||||
|
type PromptIdentity struct {
|
||||||
|
ID string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
// PromptDefinition represents a configured prompt execution definition.
|
// PromptDefinition represents a configured prompt execution definition.
|
||||||
type PromptDefinition struct {
|
type PromptDefinition struct {
|
||||||
ID string `yaml:"id"`
|
ID string `yaml:"id"`
|
||||||
|
|||||||
Reference in New Issue
Block a user