Add PromptKit requirements adapter
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Implement PromptKit requirements inspection
|
# Implement PromptKit requirements inspection
|
||||||
|
|
||||||
Status: ready for implementation; no stages have been completed.
|
Status: in progress; Stage 1 is complete.
|
||||||
|
|
||||||
## Authority and working rules
|
## Authority and working rules
|
||||||
|
|
||||||
@@ -116,6 +116,17 @@ adds the callback; its CLI availability arrives in Stage 3. Update existing
|
|||||||
absence tests when the command assembly starts counting the fifth callback in
|
absence tests when the command assembly starts counting the fifth callback in
|
||||||
that stage, rather than treating their old four-callback assumption as policy.
|
that stage, rather than treating their old four-callback assumption as policy.
|
||||||
|
|
||||||
|
**Completed:** Added `RequirementsRequest`, the optional suite callback, and
|
||||||
|
the `FromEngine` native adapter in `integrations/ckprompt/adapters.go`; added
|
||||||
|
adapter coverage in `adapters_test.go`. The ignored workspace already included
|
||||||
|
core, ckconfig, and ckprompt, and the baseline ckprompt suite passed. After the
|
||||||
|
change, `GOTOOLCHAIN=local go test ./integrations/ckprompt -run
|
||||||
|
'TestFromEngine(InspectsRequirementsWithNativeRequest|RequirementsRetainLoadDiagnosticsAndSelectionErrors|RequirementsHonorCancellationAfterLoad|PassesRequestsAndRetainsNilLoaderIssue)$'
|
||||||
|
-count=1` and `GOTOOLCHAIN=local go test ./...` from `integrations/ckprompt`
|
||||||
|
passed. The tests verify native result equivalence, loader arguments, retained
|
||||||
|
ConfigKit diagnostics, nil-engine handling, native selection error identity, and
|
||||||
|
cancellation after loading. No material limitations were encountered.
|
||||||
|
|
||||||
## Stage 2: Implement the requirements YAML report
|
## Stage 2: Implement the requirements YAML report
|
||||||
|
|
||||||
**Outcome:** native inspections map to the accepted YAML shape without losing
|
**Outcome:** native inspections map to the accepted YAML shape without losing
|
||||||
|
|||||||
@@ -31,9 +31,18 @@ type ProfileRequest struct {
|
|||||||
ID string
|
ID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suite describes application-owned input, shared state, and optional catalog
|
// RequirementsRequest selects a prompt context for native requirements
|
||||||
// and named inspection capabilities. Its private issues retain adapter
|
// inspection.
|
||||||
// construction failures.
|
type RequirementsRequest struct {
|
||||||
|
// Selection carries the configuration and overlay values supplied to the command.
|
||||||
|
Selection ckconfig.Selection
|
||||||
|
// Inspection carries PromptKit's discovery context for the selected prompt.
|
||||||
|
Inspection promptkit.RequirementInspectionRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suite describes application-owned input, shared state, and optional catalog,
|
||||||
|
// named inspection, and requirements inspection capabilities. Its private
|
||||||
|
// issues retain adapter construction failures.
|
||||||
// Factory results and callback effects remain caller-owned: Command does not
|
// Factory results and callback effects remain caller-owned: Command does not
|
||||||
// deep-copy arbitrary objects or repair aliases returned by factories.
|
// deep-copy arbitrary objects or repair aliases returned by factories.
|
||||||
// A reached operation prepares one input, applies its captured selectors, and
|
// A reached operation prepares one input, applies its captured selectors, and
|
||||||
@@ -41,24 +50,28 @@ type ProfileRequest struct {
|
|||||||
// Factories are not invoked during construction, installation, help, or version,
|
// Factories are not invoked during construction, installation, help, or version,
|
||||||
// and need not run after failure.
|
// and need not run after failure.
|
||||||
type Suite[I, S any] struct {
|
type Suite[I, S any] struct {
|
||||||
// NewInput is called once when definition or catalog input preparation is
|
// NewInput is called once when definition, requirements, or catalog input
|
||||||
// reached, before applying selectors and calling CheckInput.
|
// preparation is reached, before applying selectors and calling CheckInput.
|
||||||
// If nil, input is the zero value of I, including nil for pointer types.
|
// If nil, input is the zero value of I, including nil for pointer types.
|
||||||
NewInput func() I
|
NewInput func() I
|
||||||
// NewState is called once when an inspection or catalog callback is reached,
|
// NewState is called once when an inspection or catalog callback is reached,
|
||||||
// after input checks pass. If nil, state is the zero value of S, including
|
// after input checks pass. If nil, state is the zero value of S, including
|
||||||
// nil for pointer types.
|
// nil for pointer types.
|
||||||
NewState func() S
|
NewState func() S
|
||||||
// Selectors declares the original typed flags accepted by definition and
|
// Selectors declares the original typed flags accepted by definition,
|
||||||
// catalog commands.
|
// requirements, and catalog commands.
|
||||||
Selectors []commandkit.Flag[I]
|
Selectors []commandkit.Flag[I]
|
||||||
// CheckInput validates selected definition or catalog input before state
|
// CheckInput validates selected definition, requirements, or catalog input
|
||||||
// creation.
|
// before state creation.
|
||||||
CheckInput func(ckconfig.Selection, I) error
|
CheckInput func(ckconfig.Selection, I) error
|
||||||
// InspectPrompt inspects the selected prompt after input and state preparation.
|
// InspectPrompt inspects the selected prompt after input and state preparation.
|
||||||
InspectPrompt func(context.Context, PromptRequest, I, S) (commandkit.Acquired[*promptkit.PromptInspection], error)
|
InspectPrompt func(context.Context, PromptRequest, I, S) (commandkit.Acquired[*promptkit.PromptInspection], error)
|
||||||
// InspectProfile inspects the selected profile after input and state preparation.
|
// InspectProfile inspects the selected profile after input and state preparation.
|
||||||
InspectProfile func(context.Context, ProfileRequest, I, S) (commandkit.Acquired[*promptkit.ProfileInspection], error)
|
InspectProfile func(context.Context, ProfileRequest, I, S) (commandkit.Acquired[*promptkit.ProfileInspection], error)
|
||||||
|
// InspectRequirements inspects native PromptKit requirements after input and
|
||||||
|
// state preparation. It is optional; a nil callback omits the requirements
|
||||||
|
// command without disabling other capabilities.
|
||||||
|
InspectRequirements func(context.Context, RequirementsRequest, I, S) (commandkit.Acquired[*promptkit.RequirementInspection], error)
|
||||||
// ListPrompts lists prompt identities after input and state preparation.
|
// ListPrompts lists prompt identities after input and state preparation.
|
||||||
// It is optional; a nil callback does not install a prompt catalog command.
|
// It is optional; a nil callback does not install a prompt catalog command.
|
||||||
ListPrompts func(context.Context, ckconfig.Selection, I, S) (commandkit.Acquired[[]promptkit.PromptIdentity], error)
|
ListPrompts func(context.Context, ckconfig.Selection, I, S) (commandkit.Acquired[[]promptkit.PromptIdentity], error)
|
||||||
@@ -143,13 +156,13 @@ func promptKitContextError(ctx context.Context) error {
|
|||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromEngine supplies callbacks for prompt and profile catalogs and named
|
// FromEngine supplies callbacks for prompt and profile catalogs, named
|
||||||
// inspections backed by a PromptKit engine loader. It does not call the loader
|
// inspections, and requirements inspection backed by a PromptKit engine loader.
|
||||||
// during construction. Each selected operation passes its context, supplied
|
// It does not call the loader during construction. Each selected operation
|
||||||
// selection, initialized input, and shared invocation state to the loader before
|
// passes its context, supplied selection, initialized input, and shared
|
||||||
// calling PromptKit's public listing or inspection method. The callbacks can be
|
// invocation state to the loader before calling PromptKit's public listing or
|
||||||
// narrowed before Command captures them. A nil loader is retained as an adapter
|
// inspection method. Applications can narrow these callbacks before Command
|
||||||
// issue.
|
// captures them. A nil loader is retained as an adapter issue.
|
||||||
func FromEngine[I, S any](load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error)) Suite[I, S] {
|
func FromEngine[I, S any](load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error)) Suite[I, S] {
|
||||||
if load == nil {
|
if load == nil {
|
||||||
return Suite[I, S]{issues: []error{errors.New("PromptKit engine loader is nil")}}
|
return Suite[I, S]{issues: []error{errors.New("PromptKit engine loader is nil")}}
|
||||||
@@ -188,6 +201,14 @@ func FromEngine[I, S any](load func(context.Context, ckconfig.Selection, I, S) (
|
|||||||
inspection, err := engine.InspectProfile(ctx, request.ID)
|
inspection, err := engine.InspectProfile(ctx, request.ID)
|
||||||
return commandkit.Acquired[*promptkit.ProfileInspection]{Value: inspection, Diagnostics: diagnostics}, err
|
return commandkit.Acquired[*promptkit.ProfileInspection]{Value: inspection, Diagnostics: diagnostics}, err
|
||||||
},
|
},
|
||||||
|
InspectRequirements: func(ctx context.Context, request RequirementsRequest, input I, state S) (commandkit.Acquired[*promptkit.RequirementInspection], error) {
|
||||||
|
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load)
|
||||||
|
if err != nil {
|
||||||
|
return commandkit.Acquired[*promptkit.RequirementInspection]{Diagnostics: diagnostics}, err
|
||||||
|
}
|
||||||
|
inspection, err := engine.InspectRequirements(ctx, request.Inspection)
|
||||||
|
return commandkit.Acquired[*promptkit.RequirementInspection]{Value: inspection, Diagnostics: diagnostics}, err
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,26 @@ profiles:
|
|||||||
api_key_required: true
|
api_key_required: true
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const adapterRequirementsPrompt = `prompts:
|
||||||
|
briefing:
|
||||||
|
"1":
|
||||||
|
default_profile: default
|
||||||
|
session_id: '{{input "session-only"}} {{.session_variable}}'
|
||||||
|
messages:
|
||||||
|
- role: user
|
||||||
|
content: '{{if eq .audience "internal"}}{{input "internal-notes"}}{{else}}{{input "public-notes"}}{{end}} {{.audience}}'
|
||||||
|
output:
|
||||||
|
format: text
|
||||||
|
validation_mode: none
|
||||||
|
backends:
|
||||||
|
local:
|
||||||
|
endpoint: "https://example.invalid/v1"
|
||||||
|
profiles:
|
||||||
|
default:
|
||||||
|
backend: local
|
||||||
|
model: example
|
||||||
|
`
|
||||||
|
|
||||||
func adapterEngine(t *testing.T) *promptkit.Engine {
|
func adapterEngine(t *testing.T) *promptkit.Engine {
|
||||||
return adapterEngineFrom(t, "adapter fixture", adapterPrompt)
|
return adapterEngineFrom(t, "adapter fixture", adapterPrompt)
|
||||||
}
|
}
|
||||||
@@ -321,11 +341,126 @@ func TestFromEnginePassesRequestsAndRetainsNilLoaderIssue(t *testing.T) {
|
|||||||
empty.ListProfiles = nil
|
empty.ListProfiles = nil
|
||||||
empty.InspectPrompt = nil
|
empty.InspectPrompt = nil
|
||||||
empty.InspectProfile = nil
|
empty.InspectProfile = nil
|
||||||
|
empty.InspectRequirements = nil
|
||||||
if len(empty.issues) != 1 || !strings.Contains(empty.issues[0].Error(), "PromptKit engine loader is nil") {
|
if len(empty.issues) != 1 || !strings.Contains(empty.issues[0].Error(), "PromptKit engine loader is nil") {
|
||||||
t.Fatalf("nil-loader issue was lost when callbacks were cleared: %#v", empty.issues)
|
t.Fatalf("nil-loader issue was lost when callbacks were cleared: %#v", empty.issues)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFromEngineInspectsRequirementsWithNativeRequest(t *testing.T) {
|
||||||
|
type input struct{ value string }
|
||||||
|
type state struct{ value string }
|
||||||
|
type contextKey struct{}
|
||||||
|
|
||||||
|
engine := adapterEngineFrom(t, "requirements adapter fixture", adapterRequirementsPrompt)
|
||||||
|
wantContext := context.WithValue(context.Background(), contextKey{}, "requirements invocation")
|
||||||
|
wantInput := input{value: "typed input"}
|
||||||
|
wantState := state{value: "shared state"}
|
||||||
|
selection := ckconfig.Selection{
|
||||||
|
Config: commandkit.Supplied[string]{Value: "prompt.yml", Set: true},
|
||||||
|
Overlay: commandkit.Supplied[string]{Value: "overlay.yml", Set: true},
|
||||||
|
}
|
||||||
|
request := RequirementsRequest{
|
||||||
|
Selection: selection,
|
||||||
|
Inspection: promptkit.RequirementInspectionRequest{
|
||||||
|
PromptID: "briefing",
|
||||||
|
PromptVersion: "1",
|
||||||
|
ProfileID: "default",
|
||||||
|
Vars: map[string]string{"audience": "internal"},
|
||||||
|
SessionID: "session-42",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
want, err := engine.InspectRequirements(wantContext, request.Inspection)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("direct native requirements inspection: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loaderCalls := 0
|
||||||
|
suite := FromEngine(func(ctx context.Context, gotSelection ckconfig.Selection, gotInput input, gotState state) (*promptkit.Engine, error) {
|
||||||
|
loaderCalls++
|
||||||
|
if ctx.Value(contextKey{}) != "requirements invocation" || !reflect.DeepEqual(gotSelection, selection) || gotInput != wantInput || gotState != wantState {
|
||||||
|
t.Errorf("loader args = ctx=%v selection=%#v input=%#v state=%#v", ctx.Value(contextKey{}), gotSelection, gotInput, gotState)
|
||||||
|
}
|
||||||
|
return engine, nil
|
||||||
|
})
|
||||||
|
acquired, err := suite.InspectRequirements(wantContext, request, wantInput, wantState)
|
||||||
|
if err != nil || acquired.Value == nil || !reflect.DeepEqual(acquired.Value, want) || loaderCalls != 1 {
|
||||||
|
t.Fatalf("requirements inspection = (%#v, %v), calls=%d; want native result %#v", acquired, err, loaderCalls, want)
|
||||||
|
}
|
||||||
|
if acquired.Value.PromptID != "briefing" || acquired.Value.PromptVersion != "1" || acquired.Value.ProfileID != "default" {
|
||||||
|
t.Fatalf("requirements selection = %#v", acquired.Value)
|
||||||
|
}
|
||||||
|
inputs := make(map[string]promptkit.InputRequirement, len(acquired.Value.Inputs))
|
||||||
|
for _, input := range acquired.Value.Inputs {
|
||||||
|
inputs[input.Name] = input
|
||||||
|
}
|
||||||
|
if inputs["internal-notes"].Kind != promptkit.RequirementRequired || inputs["public-notes"].Kind != promptkit.RequirementOptional {
|
||||||
|
t.Fatalf("caller variable did not reach native discovery: %#v", acquired.Value.Inputs)
|
||||||
|
}
|
||||||
|
if _, found := inputs["session-only"]; found {
|
||||||
|
t.Fatalf("session template was not bypassed by the supplied session ID: %#v", acquired.Value.Inputs)
|
||||||
|
}
|
||||||
|
variables := make(map[string]promptkit.VariableRequirement, len(acquired.Value.Variables))
|
||||||
|
for _, variable := range acquired.Value.Variables {
|
||||||
|
variables[variable.Name] = variable
|
||||||
|
}
|
||||||
|
if !variables["audience"].Supplied {
|
||||||
|
t.Fatalf("caller variable presence was not forwarded: %#v", acquired.Value.Variables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromEngineRequirementsRetainLoadDiagnosticsAndSelectionErrors(t *testing.T) {
|
||||||
|
_, loadErr := configkit.Load[map[string]any](configkit.Bytes("invalid requirements configuration", []byte("broken: [")))
|
||||||
|
if loadErr == nil {
|
||||||
|
t.Fatal("invalid configuration unexpectedly loaded")
|
||||||
|
}
|
||||||
|
loadFailure := FromEngine[int, int](func(context.Context, ckconfig.Selection, int, int) (*promptkit.Engine, error) {
|
||||||
|
return nil, loadErr
|
||||||
|
})
|
||||||
|
acquired, err := loadFailure.InspectRequirements(context.Background(), RequirementsRequest{}, 0, 0)
|
||||||
|
var retained *configkit.LoadError
|
||||||
|
if !errors.As(err, &retained) || retained != loadErr || len(acquired.Diagnostics) == 0 || acquired.Value != nil {
|
||||||
|
t.Fatalf("requirements load failure = (%#v, %v), want original ConfigKit error and diagnostics", acquired, err)
|
||||||
|
}
|
||||||
|
if acquired.Diagnostics[0].Severity != commandkit.SeverityError {
|
||||||
|
t.Fatalf("load diagnostics = %#v, want error severity", acquired.Diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
nilEngine := FromEngine[int, int](func(context.Context, ckconfig.Selection, int, int) (*promptkit.Engine, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
acquired, err = nilEngine.InspectRequirements(context.Background(), RequirementsRequest{}, 0, 0)
|
||||||
|
if err == nil || acquired.Value != nil || !strings.Contains(err.Error(), "nil engine") {
|
||||||
|
t.Fatalf("nil engine result = (%#v, %v)", acquired, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := adapterEngine(t)
|
||||||
|
selectionFailure := FromEngine[int, int](func(context.Context, ckconfig.Selection, int, int) (*promptkit.Engine, error) {
|
||||||
|
return engine, nil
|
||||||
|
})
|
||||||
|
acquired, err = selectionFailure.InspectRequirements(context.Background(), RequirementsRequest{
|
||||||
|
Inspection: promptkit.RequirementInspectionRequest{PromptID: "missing", ProfileID: "default"},
|
||||||
|
}, 0, 0)
|
||||||
|
if !errors.Is(err, promptkit.ErrPromptNotFound) || acquired.Value != nil {
|
||||||
|
t.Fatalf("native selection failure = (%#v, %v), want ErrPromptNotFound", acquired, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromEngineRequirementsHonorCancellationAfterLoad(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
engine := adapterEngine(t)
|
||||||
|
suite := FromEngine[int, int](func(context.Context, ckconfig.Selection, int, int) (*promptkit.Engine, error) {
|
||||||
|
cancel()
|
||||||
|
return engine, nil
|
||||||
|
})
|
||||||
|
acquired, err := suite.InspectRequirements(ctx, RequirementsRequest{
|
||||||
|
Inspection: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "default"},
|
||||||
|
}, 0, 0)
|
||||||
|
if !errors.Is(err, context.Canceled) || acquired.Value != nil {
|
||||||
|
t.Fatalf("cancelled requirements inspection = (%#v, %v)", acquired, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFromEngineListsNativeCatalogsWithInvocationValues(t *testing.T) {
|
func TestFromEngineListsNativeCatalogsWithInvocationValues(t *testing.T) {
|
||||||
type input struct{ label string }
|
type input struct{ label string }
|
||||||
type state struct{ label string }
|
type state struct{ label string }
|
||||||
|
|||||||
Reference in New Issue
Block a user