Migrate PromptKit reports to shared rendering

This commit is contained in:
2026-11-07 04:10:47 +00:00
parent 5ff0dd4ea2
commit 6378ceb3c5
15 changed files with 249 additions and 265 deletions

View File

@@ -1,17 +1,14 @@
package ckprompt
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"strings"
"go.maximumdirect.net/ckconfig"
commandkit "go.maximumdirect.net/commandkit"
promptkit "go.maximumdirect.net/promptkit"
"go.yaml.in/yaml/v4"
)
type definitionInput[I any] struct {
@@ -27,7 +24,6 @@ type catalogInput[I any] struct {
allNamespaces commandkit.Supplied[bool]
kinds []promptkit.ResourceKind
filter promptkit.NamespaceFilter
format string
captured commandkit.FlagValues[I]
selected I
}
@@ -35,7 +31,6 @@ type catalogInput[I any] struct {
type requirementsInput[I any] struct {
selection ckconfig.Selection
inspection promptkit.RequirementInspectionRequest
format string
captured commandkit.FlagValues[I]
selected I
}
@@ -138,8 +133,6 @@ func newCatalogListCommand[I, S any](suite Suite[I, S], name string) commandkit.
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 }),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "format", Usage: "Catalog format: yaml.", Completion: completionChoices("yaml")}, stringDefault("yaml"), parseDefinitionString,
func(input **catalogInput[I], value commandkit.Supplied[string]) { (*input).format = value.Value }),
)
if name == "prompts" || name == "profiles" || name == "backends" {
flags = append(flags,
@@ -169,9 +162,6 @@ func newCatalogListCommand[I, S any](suite Suite[I, S], name string) commandkit.
if input == nil {
return errors.New("PromptKit catalog input must not be nil")
}
if err := checkCatalogFormat(input.format); err != nil {
return err
}
if err := prepareCatalogFilter(input, name); err != nil {
return err
}
@@ -185,21 +175,21 @@ func newCatalogListCommand[I, S any](suite Suite[I, S], name string) commandkit.
Run: func(invocation commandkit.Invocation, input *catalogInput[I]) commandkit.Outcome {
switch name {
case "prompts":
return runCatalogList(invocation, suite, input, name, suite.ListPrompts, encodePromptCatalog)
return runCatalogList(invocation, suite, input, name, suite.ListPrompts, promptCatalogReport)
case "profiles":
return runCatalogList(invocation, suite, input, name, suite.ListProfiles, encodeProfileCatalog)
return runCatalogList(invocation, suite, input, name, suite.ListProfiles, stringCatalogReport)
case "backends":
return runCatalogList(invocation, suite, input, name, suite.ListBackends, encodeProfileCatalog)
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, encodeProfileCatalog)
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, encodeCollectionCatalog)
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))
}
@@ -230,20 +220,6 @@ func prepareCatalogFilter[I any](input *catalogInput[I], name string) error {
return nil
}
func checkCatalogFormat(format string) error {
if format != "yaml" {
return fmt.Errorf("unsupported catalog format %q", format)
}
return nil
}
func checkRequirementsFormat(format string) error {
if format != "yaml" {
return fmt.Errorf("unsupported requirements format %q; expected yaml", format)
}
return nil
}
func prepareCatalogInput[I, S any](suite Suite[I, S], input *catalogInput[I]) (I, error) {
return prepareSuiteInput(suite, input.selection, input.captured)
}
@@ -313,8 +289,6 @@ func newRequirementsCommand[I, S any](suite Suite[I, S]) commandkit.Command {
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
(*input).inspection.SessionID = value.Value
}),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "format", Usage: "Report format: yaml.", Completion: completionChoices("yaml")}, stringDefault("yaml"), parseDefinitionString,
func(input **requirementsInput[I], value commandkit.Supplied[string]) { (*input).format = value.Value }),
commandkit.RepeatFlag(commandkit.FlagMeta{Name: "var", Usage: "Caller variable assignment KEY=VALUE."}, []variableAssignment(nil), parseVariableAssignment,
func(input **requirementsInput[I], value commandkit.Supplied[[]variableAssignment]) {
variables := make(map[string]string, len(value.Value))
@@ -339,9 +313,6 @@ func newRequirementsCommand[I, S any](suite Suite[I, S]) commandkit.Command {
}),
},
CheckInput: func(input *requirementsInput[I]) error {
if err := checkRequirementsFormat(input.format); err != nil {
return err
}
selected, err := prepareSuiteInput(suite, input.selection, input.captured)
if err != nil {
return err
@@ -382,7 +353,7 @@ func runCatalogList[T any, I, S any](
input *catalogInput[I],
catalog string,
list func(context.Context, CatalogRequest, I, S) (commandkit.Acquired[T], error),
encode func(T) ([]byte, error),
project func(T) any,
) commandkit.Outcome {
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
@@ -409,9 +380,8 @@ func runCatalogList[T any, I, S any](
return definitionFailure("PromptKit catalog listing was canceled.", acquired.Explanation, err)
}
}
return renderPreparedPromptReport(invocation, acquired.Explanation, func() ([]byte, error) {
return encode(acquired.Value)
})
_ = 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 {
@@ -467,35 +437,35 @@ func runDefinitionInspection[I, S any](invocation commandkit.Invocation, suite S
}
type requirementsInspectionReport struct {
PromptID string `yaml:"prompt_id"`
ProfileID string `yaml:"profile_id"`
Complete bool `yaml:"complete"`
Inputs []requirementInputReport `yaml:"inputs"`
Variables []requirementVariableReport `yaml:"variables"`
Issues []requirementIssueReport `yaml:"issues"`
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 `yaml:"name"`
Kind string `yaml:"kind"`
Declared bool `yaml:"declared"`
ContentType *string `yaml:"content_type"`
Description *string `yaml:"description"`
Explanation string `yaml:"explanation"`
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 `yaml:"name"`
Kind string `yaml:"kind"`
Supplied bool `yaml:"supplied"`
Explanation string `yaml:"explanation"`
Name string `json:"name"`
Kind string `json:"kind"`
Supplied bool `json:"supplied"`
Explanation string `json:"explanation"`
}
type requirementIssueReport struct {
Code string `yaml:"code"`
InputName string `yaml:"input_name"`
VariableName string `yaml:"variable_name"`
Description string `yaml:"description"`
Code string `json:"code"`
InputName string `json:"input_name"`
VariableName string `json:"variable_name"`
Description string `json:"description"`
}
func requirementsReportFromInspection(inspection *promptkit.RequirementInspection) requirementsInspectionReport {
@@ -566,76 +536,40 @@ func runRequirementsInspection[I, S any](invocation commandkit.Invocation, suite
return definitionFailure(fmt.Sprintf("Requirements inspection for PromptKit prompt %q was canceled.", input.inspection.PromptID), acquired.Explanation, err)
}
}
return renderPreparedPromptReport(invocation, acquired.Explanation, func() ([]byte, error) {
return encodePromptYAMLBytes(requirementsReportFromInspection(acquired.Value))
})
}
// These reports were already prepared before delivery. Keep that boundary
// inside managed rendering, with cancellation owned by the command outcome.
func renderPreparedPromptReport(invocation commandkit.Invocation, explanation commandkit.Explanation, prepare func() ([]byte, error)) commandkit.Outcome {
var canceled error
_ = invocation.Reporter().RenderOutput(func(writer io.Writer) error {
output, err := prepare()
if err != nil {
return err
}
if canceled = promptKitContextError(invocation.Context); canceled != nil {
return nil
}
_, err = writer.Write(output)
return err
})
if canceled != nil {
return definitionFailure("PromptKit report preparation was canceled.", explanation, canceled)
}
_ = invocation.Reporter().Report(commandkit.Report{Value: requirementsReportFromInspection(acquired.Value)})
return commandkit.Success()
}
func encodePromptYAML(writer io.Writer, value any) error {
encoder := yaml.NewEncoder(writer)
encoder.SetIndent(2)
return encoder.Encode(value)
}
type promptCatalogEntry struct {
ID string `yaml:"id"`
ID string `json:"id"`
}
func encodePromptCatalog(prompts []promptkit.PromptIdentity) ([]byte, error) {
func promptCatalogReport(prompts []promptkit.PromptIdentity) any {
entries := make([]promptCatalogEntry, len(prompts))
for index, prompt := range prompts {
entries[index] = promptCatalogEntry{ID: prompt.ID}
}
return encodePromptYAMLBytes(entries)
return entries
}
func encodeProfileCatalog(profiles []string) ([]byte, error) {
if profiles == nil {
profiles = []string{}
func stringCatalogReport(values []string) any {
if values == nil {
return []string{}
}
return encodePromptYAMLBytes(profiles)
return values
}
type collectionCatalogEntry struct {
Namespace string `yaml:"namespace"`
Version *string `yaml:"version,omitempty"`
Namespace string `json:"namespace"`
Version *string `json:"version,omitempty"`
}
func encodeCollectionCatalog(collections []promptkit.CollectionInfo) ([]byte, error) {
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 encodePromptYAMLBytes(entries)
}
func encodePromptYAMLBytes(value any) ([]byte, error) {
var output bytes.Buffer
if err := encodePromptYAML(&output, value); err != nil {
return nil, err
}
return output.Bytes(), nil
return entries
}
func queuePromptDiagnostics(reporter *commandkit.Reporter, diagnostics []commandkit.Diagnostic) {