629 lines
25 KiB
Go
629 lines
25 KiB
Go
package ckprompt
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"go.maximumdirect.net/ckconfig"
|
||
commandkit "go.maximumdirect.net/commandkit"
|
||
promptkit "go.maximumdirect.net/promptkit"
|
||
)
|
||
|
||
type definitionInput[I any] struct {
|
||
namespaces namespaceInput
|
||
selection ckconfig.Selection
|
||
id string
|
||
captured commandkit.FlagValues[I]
|
||
selected I
|
||
}
|
||
|
||
type catalogInput[I any] struct {
|
||
selection ckconfig.Selection
|
||
namespace commandkit.Supplied[string]
|
||
allNamespaces commandkit.Supplied[bool]
|
||
kinds []promptkit.ResourceKind
|
||
filter promptkit.NamespaceFilter
|
||
captured commandkit.FlagValues[I]
|
||
selected I
|
||
}
|
||
|
||
type requirementsInput[I any] struct {
|
||
namespaces namespaceInput
|
||
selection ckconfig.Selection
|
||
inspection promptkit.RequirementInspectionRequest
|
||
resources ResourceInput
|
||
inputs PromptInputs
|
||
captured commandkit.FlagValues[I]
|
||
selected I
|
||
}
|
||
|
||
type variableAssignment struct {
|
||
key string
|
||
value string
|
||
}
|
||
|
||
type inputAssignment struct {
|
||
name string
|
||
value string
|
||
}
|
||
|
||
// Command captures the optional PromptKit catalogs, named inspection,
|
||
// requirements discovery, concrete request validation and preparation, and smoke-test
|
||
// callbacks and adds only supplied operations. Discovery and validation share the
|
||
// `requirements` group and remain independently optional. An absent suite
|
||
// claims no namespace; attached construction issues remain reportable. It adds
|
||
// no configuration family; callers compose ConfigScope independently.
|
||
// Construction, installation, help, and version do not run suite factories or
|
||
// providers. Command captures suite callbacks and selector metadata;
|
||
// callback-referenced objects and effects remain caller-owned. Suite documents
|
||
// invocation input and state initialization, zero values, and caller ownership
|
||
// of returned objects.
|
||
// Named resource inspections return native reports through shared formatting.
|
||
// Catalog commands accept config/overlay selection and suite selectors.
|
||
func Command[I, S any](suite Suite[I, S]) commandkit.Command {
|
||
if suite.InspectPrompt == nil && suite.InspectProfile == nil && suite.InspectBackend == nil && suite.InspectRequirements == nil && suite.ValidateRequest == nil && suite.PrepareRequest == nil && suite.SmokeTest == nil && suite.ListPrompts == nil && suite.ListProfiles == nil && suite.ListBackends == nil && suite.ListNamespaces == nil && suite.ListCollections == nil {
|
||
command := commandkit.NoCommand()
|
||
for _, issue := range suite.issues {
|
||
command = commandkit.WithError(command, issue)
|
||
}
|
||
return command
|
||
}
|
||
|
||
suite.Selectors = append([]commandkit.Flag[I](nil), suite.Selectors...)
|
||
rootCommands := make([]commandkit.Command, 0, 3)
|
||
showCommands := make([]commandkit.Command, 0, 2)
|
||
if suite.InspectPrompt != nil {
|
||
showCommands = append(showCommands, newDefinitionCommand(suite, "prompt"))
|
||
}
|
||
if suite.InspectProfile != nil {
|
||
showCommands = append(showCommands, newDefinitionCommand(suite, "profile"))
|
||
}
|
||
if suite.InspectBackend != nil {
|
||
showCommands = append(showCommands, newDefinitionCommand(suite, "backend"))
|
||
}
|
||
if suite.PrepareRequest != nil {
|
||
showCommands = append(showCommands, newPreparationCommand(suite))
|
||
}
|
||
if len(showCommands) != 0 {
|
||
rootCommands = append(rootCommands, commandkit.Group("show", "Show a named PromptKit definition.", showCommands...))
|
||
}
|
||
listCommands := make([]commandkit.Command, 0, 5)
|
||
if suite.ListPrompts != nil {
|
||
listCommands = append(listCommands, newCatalogListCommand(suite, "prompts"))
|
||
}
|
||
if suite.ListProfiles != nil {
|
||
listCommands = append(listCommands, newCatalogListCommand(suite, "profiles"))
|
||
}
|
||
if suite.ListBackends != nil {
|
||
listCommands = append(listCommands, newCatalogListCommand(suite, "backends"))
|
||
}
|
||
if suite.ListNamespaces != nil {
|
||
listCommands = append(listCommands, newCatalogListCommand(suite, "namespaces"))
|
||
}
|
||
if suite.ListCollections != nil {
|
||
listCommands = append(listCommands, newCatalogListCommand(suite, "collections"))
|
||
}
|
||
if len(listCommands) != 0 {
|
||
rootCommands = append(rootCommands, commandkit.Group("list", "List PromptKit catalogs.", listCommands...))
|
||
}
|
||
requirementsCommands := make([]commandkit.Command, 0, 2)
|
||
if suite.InspectRequirements != nil {
|
||
requirementsCommands = append(requirementsCommands, newRequirementsCommand(suite))
|
||
}
|
||
if suite.ValidateRequest != nil {
|
||
requirementsCommands = append(requirementsCommands, newValidationCommand(suite))
|
||
}
|
||
if len(requirementsCommands) != 0 {
|
||
rootCommands = append(rootCommands, commandkit.Group("requirements", "Inspect and validate prompt requirements.", requirementsCommands...))
|
||
}
|
||
if suite.SmokeTest != nil {
|
||
rootCommands = append(rootCommands, newSmokeTestCommand(suite))
|
||
}
|
||
command := commandkit.RootGroup("promptkit", "Inspect, list, validate requests, and run PromptKit smoke tests.", rootCommands...)
|
||
for _, issue := range suite.issues {
|
||
command = commandkit.WithError(command, issue)
|
||
}
|
||
return command
|
||
}
|
||
|
||
func newCatalogListCommand[I, S any](suite Suite[I, S], name string) commandkit.Command {
|
||
flags := commandkit.CaptureFlags(suite.Selectors, func(input **catalogInput[I]) *commandkit.FlagValues[I] {
|
||
if input == nil || *input == nil {
|
||
return nil
|
||
}
|
||
return &(*input).captured
|
||
})
|
||
flags = append(flags,
|
||
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Configuration input path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
|
||
func(input **catalogInput[I], value commandkit.Supplied[string]) { (*input).selection.Config = value }),
|
||
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Configuration overlay path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
|
||
func(input **catalogInput[I], value commandkit.Supplied[string]) { (*input).selection.Overlay = value }),
|
||
)
|
||
if name == "prompts" || name == "profiles" || name == "backends" {
|
||
flags = append(flags,
|
||
commandkit.ValueFlag(commandkit.FlagMeta{Name: "namespace", Usage: "Select one native namespace."}, (*string)(nil), parseDefinitionString,
|
||
func(input **catalogInput[I], value commandkit.Supplied[string]) { (*input).namespace = value }),
|
||
commandkit.BoolFlag(commandkit.FlagMeta{Name: "all-namespaces", Usage: "Select identities from every namespace."}, false,
|
||
func(input **catalogInput[I], value commandkit.Supplied[bool]) { (*input).allNamespaces = value }),
|
||
)
|
||
}
|
||
if name == "namespaces" {
|
||
flags = append(flags,
|
||
commandkit.RepeatFlag(commandkit.FlagMeta{Name: "kind", Usage: "Resource kind to include."}, []promptkit.ResourceKind(nil),
|
||
func(value string) (promptkit.ResourceKind, error) { return promptkit.ResourceKind(value), nil },
|
||
func(input **catalogInput[I], value commandkit.Supplied[[]promptkit.ResourceKind]) {
|
||
(*input).kinds = value.Value
|
||
}),
|
||
)
|
||
}
|
||
return commandkit.NewCommand(commandkit.CommandSpec[*catalogInput[I]]{
|
||
Name: name,
|
||
Summary: "List PromptKit " + name + ".",
|
||
NewInput: func() *catalogInput[I] {
|
||
return &catalogInput[I]{}
|
||
},
|
||
Flags: flags,
|
||
CheckInput: func(input *catalogInput[I]) error {
|
||
if input == nil {
|
||
return errors.New("PromptKit catalog input must not be nil")
|
||
}
|
||
if err := prepareCatalogFilter(input, name); err != nil {
|
||
return err
|
||
}
|
||
selected, err := prepareCatalogInput(suite, input)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
input.selected = selected
|
||
return nil
|
||
},
|
||
Run: func(invocation commandkit.Invocation, input *catalogInput[I]) commandkit.Outcome {
|
||
switch name {
|
||
case "prompts":
|
||
return runCatalogList(invocation, suite, input, name, suite.ListPrompts, promptCatalogReport)
|
||
case "profiles":
|
||
return runCatalogList(invocation, suite, input, name, suite.ListProfiles, stringCatalogReport)
|
||
case "backends":
|
||
return runCatalogList(invocation, suite, input, name, suite.ListBackends, stringCatalogReport)
|
||
case "namespaces":
|
||
list := func(ctx context.Context, request CatalogRequest, value I, state S) (commandkit.Acquired[[]string], error) {
|
||
return suite.ListNamespaces(ctx, NamespaceRequest{Selection: request.Selection, Kinds: input.kinds}, value, state)
|
||
}
|
||
return runCatalogList(invocation, suite, input, name, list, stringCatalogReport)
|
||
case "collections":
|
||
list := func(ctx context.Context, request CatalogRequest, value I, state S) (commandkit.Acquired[[]promptkit.CollectionInfo], error) {
|
||
return suite.ListCollections(ctx, request.Selection, value, state)
|
||
}
|
||
return runCatalogList(invocation, suite, input, name, list, collectionCatalogReport)
|
||
default:
|
||
return definitionFailure("Unable to list the selected PromptKit catalog.", commandkit.Explanation{}, fmt.Errorf("unknown PromptKit catalog %q", name))
|
||
}
|
||
},
|
||
})
|
||
}
|
||
|
||
func prepareCatalogFilter[I any](input *catalogInput[I], name string) error {
|
||
input.filter = promptkit.NamespaceFilter{Mode: promptkit.NamespaceDefault}
|
||
if name != "prompts" && name != "profiles" && name != "backends" {
|
||
return nil
|
||
}
|
||
if input.allNamespaces.Set && input.allNamespaces.Value && input.namespace.Set {
|
||
return errors.New("--namespace cannot be combined with --all-namespaces")
|
||
}
|
||
if input.allNamespaces.Set && input.allNamespaces.Value {
|
||
input.filter.Mode = promptkit.NamespaceAll
|
||
return nil
|
||
}
|
||
if input.namespace.Set {
|
||
if input.namespace.Value == "" {
|
||
input.filter.Mode = promptkit.NamespaceUnqualified
|
||
} else {
|
||
input.filter.Mode = promptkit.NamespaceNamed
|
||
input.filter.Name = input.namespace.Value
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func prepareCatalogInput[I, S any](suite Suite[I, S], input *catalogInput[I]) (I, error) {
|
||
return prepareSuiteInput(suite, input.selection, input.captured)
|
||
}
|
||
|
||
func newDefinitionCommand[I, S any](suite Suite[I, S], name string) commandkit.Command {
|
||
flags := commandkit.CaptureFlags(suite.Selectors, func(input **definitionInput[I]) *commandkit.FlagValues[I] {
|
||
if input == nil || *input == nil {
|
||
return nil
|
||
}
|
||
return &(*input).captured
|
||
})
|
||
flags = append(flags,
|
||
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Configuration input path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
|
||
func(input **definitionInput[I], value commandkit.Supplied[string]) { (*input).selection.Config = value }),
|
||
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Configuration overlay path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
|
||
func(input **definitionInput[I], value commandkit.Supplied[string]) {
|
||
(*input).selection.Overlay = value
|
||
}),
|
||
)
|
||
if suite.namespaces != nil {
|
||
flags = append(flags, namespaceFlags(func(input **definitionInput[I]) *namespaceInput { return &(*input).namespaces }, name)...)
|
||
}
|
||
|
||
spec := commandkit.CommandSpec[*definitionInput[I]]{
|
||
Name: name,
|
||
Summary: "Inspect a named PromptKit " + name + ".",
|
||
NewInput: func() *definitionInput[I] {
|
||
return &definitionInput[I]{}
|
||
},
|
||
Flags: flags,
|
||
Arguments: []commandkit.Argument[*definitionInput[I]]{
|
||
commandkit.ValueArgument(commandkit.ArgumentMeta{Name: strings.ToUpper(name) + "_ID", Usage: "Resource ID."}, true, parseDefinitionString,
|
||
func(input **definitionInput[I], value commandkit.Supplied[string]) { (*input).id = value.Value }),
|
||
},
|
||
CheckInput: func(input *definitionInput[I]) error {
|
||
selected, err := prepareDefinitionInput(suite, input)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
input.selected = selected
|
||
return nil
|
||
},
|
||
Run: func(invocation commandkit.Invocation, input *definitionInput[I]) commandkit.Outcome {
|
||
return runDefinitionInspection(invocation, suite, input, name)
|
||
},
|
||
}
|
||
return commandkit.NewCommand(spec)
|
||
}
|
||
|
||
func newRequirementsCommand[I, S any](suite Suite[I, S]) commandkit.Command {
|
||
flags := commandkit.CaptureFlags(suite.Selectors, func(input **requirementsInput[I]) *commandkit.FlagValues[I] {
|
||
if input == nil || *input == nil {
|
||
return nil
|
||
}
|
||
return &(*input).captured
|
||
})
|
||
flags = append(flags,
|
||
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Configuration input path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
|
||
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
|
||
(*input).selection.Config = value
|
||
}),
|
||
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Configuration overlay path.", Completion: commandkit.CompleteFiles()}, (*string)(nil), parseDefinitionString,
|
||
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
|
||
(*input).selection.Overlay = value
|
||
}),
|
||
)
|
||
|
||
resources := NewResourceFlags(func(input **requirementsInput[I]) *ResourceInput { return &(*input).resources })
|
||
inputs := NewPromptInputFlags(func(input **requirementsInput[I]) *PromptInputs { return &(*input).inputs })
|
||
flags = append(flags, resources.Profile, resources.SessionID, inputs.Variable)
|
||
|
||
if suite.namespaces != nil {
|
||
flags = append(flags, namespaceFlags(func(input **requirementsInput[I]) *namespaceInput { return &(*input).namespaces }, "prompt", "profile")...)
|
||
}
|
||
|
||
spec := commandkit.CommandSpec[*requirementsInput[I]]{
|
||
Name: "show",
|
||
Summary: "Inspect requirements for a named PromptKit prompt.",
|
||
Description: "Profile selection establishes the reported profile identity, not a different set of requirements. A profile namespace qualifies an explicitly supplied short profile ID, not the prompt’s stored default reference.",
|
||
NewInput: func() *requirementsInput[I] {
|
||
return &requirementsInput[I]{}
|
||
},
|
||
Flags: flags,
|
||
Arguments: []commandkit.Argument[*requirementsInput[I]]{
|
||
commandkit.ValueArgument(commandkit.ArgumentMeta{Name: "id", Usage: "Prompt ID."}, true, parseDefinitionString,
|
||
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
|
||
(*input).inspection.PromptID = value.Value
|
||
}),
|
||
},
|
||
CheckInput: func(input *requirementsInput[I]) error {
|
||
input.inspection.ProfileID = input.resources.Profile.Value
|
||
input.inspection.SessionID = input.resources.SessionID.Value
|
||
input.inspection.Vars = input.inputs.Vars
|
||
selected, err := prepareSuiteInput(suite, input.selection, input.captured)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
input.selected = selected
|
||
return nil
|
||
},
|
||
Run: func(invocation commandkit.Invocation, input *requirementsInput[I]) commandkit.Outcome {
|
||
return runRequirementsInspection(invocation, suite, input)
|
||
},
|
||
}
|
||
return commandkit.NewCommand(spec)
|
||
}
|
||
|
||
func prepareDefinitionInput[I, S any](suite Suite[I, S], input *definitionInput[I]) (I, error) {
|
||
return prepareSuiteInput(suite, input.selection, input.captured)
|
||
}
|
||
|
||
func prepareSuiteInput[I, S any](suite Suite[I, S], selection ckconfig.Selection, captured commandkit.FlagValues[I]) (I, error) {
|
||
var selected I
|
||
if suite.NewInput != nil {
|
||
selected = suite.NewInput()
|
||
}
|
||
if err := captured.ApplyTo(&selected, suite.Selectors...); err != nil {
|
||
return selected, err
|
||
}
|
||
if suite.CheckInput != nil {
|
||
if err := suite.CheckInput(selection, selected); err != nil {
|
||
return selected, err
|
||
}
|
||
}
|
||
return selected, nil
|
||
}
|
||
|
||
func runCatalogList[T any, I, S any](
|
||
invocation commandkit.Invocation,
|
||
suite Suite[I, S],
|
||
input *catalogInput[I],
|
||
catalog string,
|
||
list func(context.Context, CatalogRequest, I, S) (commandkit.Acquired[T], error),
|
||
project func(T) any,
|
||
) commandkit.Outcome {
|
||
if invocation.Context != nil {
|
||
if err := invocation.Context.Err(); err != nil {
|
||
return definitionFailure("PromptKit catalog listing was canceled.", commandkit.Explanation{}, err)
|
||
}
|
||
}
|
||
var state S
|
||
if suite.NewState != nil {
|
||
state = suite.NewState()
|
||
}
|
||
if invocation.Context != nil {
|
||
if err := invocation.Context.Err(); err != nil {
|
||
return definitionFailure("PromptKit catalog listing was canceled.", commandkit.Explanation{}, err)
|
||
}
|
||
}
|
||
|
||
acquired, err := list(invocation.Context, CatalogRequest{Selection: input.selection, Filter: input.filter}, input.selected, state)
|
||
queuePromptDiagnostics(invocation.Reporter(), acquired.Diagnostics)
|
||
if err != nil {
|
||
return definitionFailure(fmt.Sprintf("Unable to list PromptKit %s.", catalog), acquired.Explanation, err)
|
||
}
|
||
if invocation.Context != nil {
|
||
if err := invocation.Context.Err(); err != nil {
|
||
return definitionFailure("PromptKit catalog listing was canceled.", acquired.Explanation, err)
|
||
}
|
||
}
|
||
_ = invocation.Reporter().Report(commandkit.Report{Value: project(acquired.Value)})
|
||
return commandkit.Success()
|
||
}
|
||
|
||
func runDefinitionInspection[I, S any](invocation commandkit.Invocation, suite Suite[I, S], input *definitionInput[I], kind string) commandkit.Outcome {
|
||
if err := promptKitContextError(invocation.Context); err != nil {
|
||
return definitionFailure("PromptKit inspection was canceled.", commandkit.Explanation{}, err)
|
||
}
|
||
var state S
|
||
if suite.NewState != nil {
|
||
state = suite.NewState()
|
||
}
|
||
if err := promptKitContextError(invocation.Context); err != nil {
|
||
return definitionFailure("PromptKit inspection was canceled.", commandkit.Explanation{}, err)
|
||
}
|
||
var value any
|
||
var diagnostics []commandkit.Diagnostic
|
||
var explanation commandkit.Explanation
|
||
var err error
|
||
switch kind {
|
||
case "prompt":
|
||
var result commandkit.Acquired[*promptkit.PromptInspection]
|
||
result, err = suite.InspectPrompt(invocation.Context, PromptRequest{Selection: input.selection, ID: input.id, Namespaces: selectedNamespaces(suite.namespaces, input.namespaces)}, input.selected, state)
|
||
if result.Value != nil {
|
||
value = result.Value
|
||
}
|
||
diagnostics, explanation = result.Diagnostics, result.Explanation
|
||
case "profile":
|
||
var result commandkit.Acquired[*promptkit.ProfileInspection]
|
||
result, err = suite.InspectProfile(invocation.Context, ProfileRequest{Selection: input.selection, ID: input.id, Namespaces: selectedNamespaces(suite.namespaces, input.namespaces)}, input.selected, state)
|
||
if result.Value != nil {
|
||
value = result.Value
|
||
}
|
||
diagnostics, explanation = result.Diagnostics, result.Explanation
|
||
case "backend":
|
||
var result commandkit.Acquired[*promptkit.BackendInspection]
|
||
result, err = suite.InspectBackend(invocation.Context, BackendRequest{Selection: input.selection, ID: input.id, Namespaces: selectedNamespaces(suite.namespaces, input.namespaces)}, input.selected, state)
|
||
if result.Value != nil {
|
||
value = result.Value
|
||
}
|
||
diagnostics, explanation = result.Diagnostics, result.Explanation
|
||
}
|
||
queuePromptDiagnostics(invocation.Reporter(), diagnostics)
|
||
if err != nil {
|
||
return definitionFailure(fmt.Sprintf("Unable to inspect PromptKit %s %q.", kind, input.id), explanation, err)
|
||
}
|
||
if value == nil {
|
||
return definitionFailure(fmt.Sprintf("PromptKit %s inspection returned no result.", kind), explanation, nil)
|
||
}
|
||
if err := promptKitContextError(invocation.Context); err != nil {
|
||
return definitionFailure(fmt.Sprintf("Inspection of PromptKit %s %q was canceled.", kind, input.id), explanation, err)
|
||
}
|
||
_ = invocation.Reporter().Report(commandkit.Report{Value: value})
|
||
return commandkit.Success()
|
||
}
|
||
|
||
type requirementsInspectionReport struct {
|
||
PromptID string `json:"prompt_id"`
|
||
ProfileID string `json:"profile_id"`
|
||
Complete bool `json:"complete"`
|
||
Inputs []requirementInputReport `json:"inputs"`
|
||
Variables []requirementVariableReport `json:"variables"`
|
||
Issues []requirementIssueReport `json:"issues"`
|
||
}
|
||
|
||
type requirementInputReport struct {
|
||
Name string `json:"name"`
|
||
Kind string `json:"kind"`
|
||
Declared bool `json:"declared"`
|
||
ContentType *string `json:"content_type"`
|
||
Description *string `json:"description"`
|
||
Explanation string `json:"explanation"`
|
||
}
|
||
|
||
type requirementVariableReport struct {
|
||
Name string `json:"name"`
|
||
Kind string `json:"kind"`
|
||
Supplied bool `json:"supplied"`
|
||
Explanation string `json:"explanation"`
|
||
}
|
||
|
||
type requirementIssueReport struct {
|
||
Code string `json:"code"`
|
||
InputName string `json:"input_name"`
|
||
VariableName string `json:"variable_name"`
|
||
Description string `json:"description"`
|
||
}
|
||
|
||
func requirementsReportFromInspection(inspection *promptkit.RequirementInspection) requirementsInspectionReport {
|
||
report := requirementsInspectionReport{
|
||
PromptID: inspection.PromptID,
|
||
ProfileID: inspection.ProfileID,
|
||
Complete: inspection.Complete,
|
||
Inputs: make([]requirementInputReport, 0, len(inspection.Inputs)),
|
||
Variables: make([]requirementVariableReport, 0, len(inspection.Variables)),
|
||
Issues: make([]requirementIssueReport, 0, len(inspection.Issues)),
|
||
}
|
||
for _, input := range inspection.Inputs {
|
||
report.Inputs = append(report.Inputs, requirementInputReport{
|
||
Name: input.Name,
|
||
Kind: string(input.Kind),
|
||
Declared: input.Declared,
|
||
ContentType: input.ContentType,
|
||
Description: input.Description,
|
||
Explanation: input.Explanation,
|
||
})
|
||
}
|
||
for _, variable := range inspection.Variables {
|
||
report.Variables = append(report.Variables, requirementVariableReport{
|
||
Name: variable.Name,
|
||
Kind: string(variable.Kind),
|
||
Supplied: variable.Supplied,
|
||
Explanation: variable.Explanation,
|
||
})
|
||
}
|
||
for _, issue := range inspection.Issues {
|
||
report.Issues = append(report.Issues, requirementIssueReport{
|
||
Code: string(issue.Code),
|
||
InputName: issue.InputName,
|
||
VariableName: issue.VariableName,
|
||
Description: issue.Description,
|
||
})
|
||
}
|
||
return report
|
||
}
|
||
|
||
func runRequirementsInspection[I, S any](invocation commandkit.Invocation, suite Suite[I, S], input *requirementsInput[I]) commandkit.Outcome {
|
||
if invocation.Context != nil {
|
||
if err := invocation.Context.Err(); err != nil {
|
||
return definitionFailure("PromptKit requirements inspection was canceled.", commandkit.Explanation{}, err)
|
||
}
|
||
}
|
||
var state S
|
||
if suite.NewState != nil {
|
||
state = suite.NewState()
|
||
}
|
||
if invocation.Context != nil {
|
||
if err := invocation.Context.Err(); err != nil {
|
||
return definitionFailure("PromptKit requirements inspection was canceled.", commandkit.Explanation{}, err)
|
||
}
|
||
}
|
||
|
||
request := RequirementsRequest{Selection: input.selection, Inspection: input.inspection, Namespaces: selectedNamespaces(suite.namespaces, input.namespaces)}
|
||
acquired, err := suite.InspectRequirements(invocation.Context, request, input.selected, state)
|
||
queuePromptDiagnostics(invocation.Reporter(), acquired.Diagnostics)
|
||
if err != nil {
|
||
return definitionFailure(fmt.Sprintf("Unable to inspect requirements for PromptKit prompt %q.", input.inspection.PromptID), acquired.Explanation, err)
|
||
}
|
||
if acquired.Value == nil {
|
||
return definitionFailure("PromptKit requirements inspection returned no result.", acquired.Explanation, nil)
|
||
}
|
||
if invocation.Context != nil {
|
||
if err := invocation.Context.Err(); err != nil {
|
||
return definitionFailure(fmt.Sprintf("Requirements inspection for PromptKit prompt %q was canceled.", input.inspection.PromptID), acquired.Explanation, err)
|
||
}
|
||
}
|
||
_ = invocation.Reporter().Report(commandkit.Report{Value: requirementsReportFromInspection(acquired.Value)})
|
||
return commandkit.Success()
|
||
}
|
||
|
||
type promptCatalogEntry struct {
|
||
ID string `json:"id"`
|
||
}
|
||
|
||
func promptCatalogReport(prompts []promptkit.PromptIdentity) any {
|
||
entries := make([]promptCatalogEntry, len(prompts))
|
||
for index, prompt := range prompts {
|
||
entries[index] = promptCatalogEntry{ID: prompt.ID}
|
||
}
|
||
return entries
|
||
}
|
||
|
||
func stringCatalogReport(values []string) any {
|
||
if values == nil {
|
||
return []string{}
|
||
}
|
||
return values
|
||
}
|
||
|
||
type collectionCatalogEntry struct {
|
||
Namespace string `json:"namespace"`
|
||
Version *string `json:"version,omitempty"`
|
||
}
|
||
|
||
func collectionCatalogReport(collections []promptkit.CollectionInfo) any {
|
||
entries := make([]collectionCatalogEntry, len(collections))
|
||
for index, collection := range collections {
|
||
entries[index] = collectionCatalogEntry{Namespace: collection.Namespace, Version: collection.ReleaseVersion}
|
||
}
|
||
return entries
|
||
}
|
||
|
||
func queuePromptDiagnostics(reporter *commandkit.Reporter, diagnostics []commandkit.Diagnostic) {
|
||
deferred := reporter.AtEnd()
|
||
for _, diagnostic := range diagnostics {
|
||
if diagnostic.Write == nil {
|
||
continue
|
||
}
|
||
switch diagnostic.Severity {
|
||
case commandkit.SeverityInfo:
|
||
_ = deferred.Info("", diagnostic.Write)
|
||
case commandkit.SeverityDebug:
|
||
_ = deferred.Debug("", diagnostic.Write)
|
||
case commandkit.SeverityProgress:
|
||
_ = reporter.Progress("", diagnostic.Write)
|
||
default:
|
||
_ = deferred.Warn("", diagnostic.Write)
|
||
}
|
||
}
|
||
}
|
||
|
||
func definitionFailure(message string, explanation commandkit.Explanation, cause error) commandkit.Outcome {
|
||
outcome := commandkit.Failure(message)
|
||
if explanation.Message != "" || len(explanation.Details) > 0 {
|
||
outcome = outcome.WithExplanation(explanation.Message, explanation.Details...)
|
||
}
|
||
if cause != nil {
|
||
outcome = outcome.WithCause(cause)
|
||
}
|
||
return outcome
|
||
}
|
||
|
||
func parseDefinitionString(value string) (string, error) { return value, nil }
|
||
|
||
func parseVariableAssignment(raw string) (variableAssignment, error) {
|
||
key, value, found := strings.Cut(raw, "=")
|
||
if !found {
|
||
return variableAssignment{}, fmt.Errorf("variable assignment %q must contain =", raw)
|
||
}
|
||
return variableAssignment{key: key, value: value}, nil
|
||
}
|
||
|
||
func parseInputAssignment(raw string) (inputAssignment, error) {
|
||
name, value, found := strings.Cut(raw, "=")
|
||
if !found {
|
||
return inputAssignment{}, fmt.Errorf("input assignment %q must contain =", raw)
|
||
}
|
||
return inputAssignment{name: name, value: value}, nil
|
||
}
|