Adopt native PromptKit resource inspection

This commit is contained in:
2026-11-07 04:05:51 +00:00
parent 6c35da3ab6
commit 5ff0dd4ea2
12 changed files with 313 additions and 385 deletions

View File

@@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"io"
"strconv"
"strings"
"go.maximumdirect.net/ckconfig"
@@ -18,7 +17,6 @@ import (
type definitionInput[I any] struct {
selection ckconfig.Selection
id string
format string
captured commandkit.FlagValues[I]
selected I
}
@@ -63,11 +61,10 @@ type inputAssignment struct {
// callback-referenced objects and effects remain caller-owned. Suite documents
// invocation input and state initialization, zero values, and caller ownership
// of returned objects.
// Catalog commands accept config/overlay selection and suite selectors, and
// render YAML with `yaml` as the only format and default. Named-show formats are
// unchanged.
// 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.InspectRequirements == nil && suite.ValidateRequest == nil && suite.SmokeTest == nil && suite.ListPrompts == nil && suite.ListProfiles == nil && suite.ListBackends == nil && suite.ListNamespaces == nil && suite.ListCollections == nil {
if suite.InspectPrompt == nil && suite.InspectProfile == nil && suite.InspectBackend == nil && suite.InspectRequirements == nil && suite.ValidateRequest == 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)
@@ -79,10 +76,13 @@ func Command[I, S any](suite Suite[I, S]) commandkit.Command {
rootCommands := make([]commandkit.Command, 0, 3)
showCommands := make([]commandkit.Command, 0, 2)
if suite.InspectPrompt != nil {
showCommands = append(showCommands, newDefinitionCommand(suite, "prompt", true))
showCommands = append(showCommands, newDefinitionCommand(suite, "prompt"))
}
if suite.InspectProfile != nil {
showCommands = append(showCommands, newDefinitionCommand(suite, "profile", false))
showCommands = append(showCommands, newDefinitionCommand(suite, "profile"))
}
if suite.InspectBackend != nil {
showCommands = append(showCommands, newDefinitionCommand(suite, "backend"))
}
if len(showCommands) != 0 {
rootCommands = append(rootCommands, commandkit.Group("show", "Show a named PromptKit definition.", showCommands...))
@@ -248,7 +248,7 @@ func prepareCatalogInput[I, S any](suite Suite[I, S], input *catalogInput[I]) (I
return prepareSuiteInput(suite, input.selection, input.captured)
}
func newDefinitionCommand[I, S any](suite Suite[I, S], name string, prompt bool) commandkit.Command {
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
@@ -262,8 +262,6 @@ func newDefinitionCommand[I, S any](suite Suite[I, S], name string, prompt bool)
func(input **definitionInput[I], value commandkit.Supplied[string]) {
(*input).selection.Overlay = value
}),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "format", Usage: "Report format: text or yaml.", Completion: completionChoices("text", "yaml")}, stringDefault("text"), parseDefinitionString,
func(input **definitionInput[I], value commandkit.Supplied[string]) { (*input).format = value.Value }),
)
spec := commandkit.CommandSpec[*definitionInput[I]]{
Name: name,
@@ -273,13 +271,10 @@ func newDefinitionCommand[I, S any](suite Suite[I, S], name string, prompt bool)
},
Flags: flags,
Arguments: []commandkit.Argument[*definitionInput[I]]{
commandkit.ValueArgument(commandkit.ArgumentMeta{Name: "id", Usage: "Prompt or profile ID."}, true, parseDefinitionString,
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 {
if err := checkDefinitionFormat(input.format); err != nil {
return err
}
selected, err := prepareDefinitionInput(suite, input)
if err != nil {
return err
@@ -288,7 +283,7 @@ func newDefinitionCommand[I, S any](suite Suite[I, S], name string, prompt bool)
return nil
},
Run: func(invocation commandkit.Invocation, input *definitionInput[I]) commandkit.Outcome {
return runDefinitionInspection(invocation, suite, input, prompt)
return runDefinitionInspection(invocation, suite, input, name)
},
}
return commandkit.NewCommand(spec)
@@ -419,77 +414,56 @@ func runCatalogList[T any, I, S any](
})
}
func runDefinitionInspection[I, S any](invocation commandkit.Invocation, suite Suite[I, S], input *definitionInput[I], prompt bool) commandkit.Outcome {
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure("PromptKit inspection was canceled.", commandkit.Explanation{}, err)
}
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 invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure("PromptKit inspection was canceled.", commandkit.Explanation{}, err)
}
if err := promptKitContextError(invocation.Context); err != nil {
return definitionFailure("PromptKit inspection was canceled.", commandkit.Explanation{}, err)
}
if prompt {
request := PromptRequest{Selection: input.selection, ID: input.id}
acquired, err := suite.InspectPrompt(invocation.Context, request, input.selected, state)
queuePromptDiagnostics(invocation.Reporter(), acquired.Diagnostics)
if err != nil {
return definitionFailure(fmt.Sprintf("Unable to inspect PromptKit prompt %q.", input.id), acquired.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}, input.selected, state)
if result.Value != nil {
value = result.Value
}
if acquired.Value == nil {
return definitionFailure("PromptKit prompt inspection returned no result.", acquired.Explanation, nil)
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}, input.selected, state)
if result.Value != nil {
value = result.Value
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(fmt.Sprintf("Inspection of PromptKit prompt %q was canceled.", input.id), acquired.Explanation, err)
}
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}, input.selected, state)
if result.Value != nil {
value = result.Value
}
return renderPromptReport(invocation, acquired.Value, input.format, acquired.Explanation)
diagnostics, explanation = result.Diagnostics, result.Explanation
}
request := ProfileRequest{Selection: input.selection, ID: input.id}
acquired, err := suite.InspectProfile(invocation.Context, request, input.selected, state)
queuePromptDiagnostics(invocation.Reporter(), acquired.Diagnostics)
queuePromptDiagnostics(invocation.Reporter(), diagnostics)
if err != nil {
return definitionFailure(fmt.Sprintf("Unable to inspect PromptKit profile %q.", input.id), acquired.Explanation, err)
return definitionFailure(fmt.Sprintf("Unable to inspect PromptKit %s %q.", kind, input.id), explanation, err)
}
if acquired.Value == nil {
return definitionFailure("PromptKit profile inspection returned no result.", acquired.Explanation, nil)
if value == nil {
return definitionFailure(fmt.Sprintf("PromptKit %s inspection returned no result.", kind), explanation, nil)
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(fmt.Sprintf("Inspection of PromptKit profile %q was canceled.", input.id), acquired.Explanation, err)
}
if err := promptKitContextError(invocation.Context); err != nil {
return definitionFailure(fmt.Sprintf("Inspection of PromptKit %s %q was canceled.", kind, input.id), explanation, err)
}
return renderProfileReport(invocation, acquired.Value, input.format, acquired.Explanation)
}
type promptInputReport struct {
Name string `yaml:"name"`
Required bool `yaml:"required"`
ContentType string `yaml:"content_type"`
Description string `yaml:"description"`
}
type promptOutputContractReport struct {
Format string `yaml:"format"`
ValidationMode string `yaml:"validation_mode"`
SchemaPath string `yaml:"schema_path"`
RepairAttempts int `yaml:"repair_attempts"`
}
type promptInspectionReport struct {
PromptID string `yaml:"prompt_id"`
PromptHash string `yaml:"prompt_hash"`
DefaultProfileID string `yaml:"default_profile_id"`
Inputs []promptInputReport `yaml:"inputs"`
OutputContract promptOutputContractReport `yaml:"output_contract"`
_ = invocation.Reporter().Report(commandkit.Report{Value: value})
return commandkit.Success()
}
type requirementsInspectionReport struct {
@@ -524,45 +498,6 @@ type requirementIssueReport struct {
Description string `yaml:"description"`
}
type modelParamsReport struct {
BackendID string `yaml:"backend_id"`
Endpoint string `yaml:"endpoint"`
Model string `yaml:"model"`
Temperature float64 `yaml:"temperature"`
MaxTokens int `yaml:"max_tokens"`
TopP float64 `yaml:"top_p"`
TimeoutSeconds int `yaml:"timeout_seconds"`
ServiceTier string `yaml:"service_tier"`
ReasoningEffort string `yaml:"reasoning_effort"`
APIKeyEnv string `yaml:"api_key_env"`
ExtraParams map[string]any `yaml:"extra_params"`
}
type profileInspectionReport struct {
ProfileID string `yaml:"profile_id"`
EffectiveModelParams modelParamsReport `yaml:"effective_model_params"`
APIKeyRequired bool `yaml:"api_key_required"`
}
func promptReportFromInspection(inspection *promptkit.PromptInspection) promptInspectionReport {
report := promptInspectionReport{
PromptID: inspection.PromptID,
PromptHash: inspection.PromptHash,
DefaultProfileID: inspection.DefaultProfileID,
Inputs: make([]promptInputReport, 0, len(inspection.Inputs)),
OutputContract: promptOutputContractReport{
Format: string(inspection.OutputContract.Format),
ValidationMode: string(inspection.OutputContract.ValidationMode),
SchemaPath: inspection.OutputContract.SchemaPath,
RepairAttempts: inspection.OutputContract.RepairAttempts,
},
}
for _, input := range inspection.Inputs {
report.Inputs = append(report.Inputs, promptInputReport{Name: input.Name, Required: input.Required, ContentType: input.ContentType, Description: input.Description})
}
return report
}
func requirementsReportFromInspection(inspection *promptkit.RequirementInspection) requirementsInspectionReport {
report := requirementsInspectionReport{
PromptID: inspection.PromptID,
@@ -601,24 +536,6 @@ func requirementsReportFromInspection(inspection *promptkit.RequirementInspectio
return report
}
func profileReportFromInspection(inspection *promptkit.ProfileInspection) profileInspectionReport {
params := inspection.EffectiveModelParams
extra := params.ExtraParams
if extra == nil {
extra = map[string]any{}
}
return profileInspectionReport{
ProfileID: inspection.ProfileID,
EffectiveModelParams: modelParamsReport{
BackendID: params.BackendID, Endpoint: params.Endpoint, Model: params.Model,
Temperature: params.Temperature, MaxTokens: params.MaxTokens, TopP: params.TopP,
TimeoutSeconds: params.TimeoutSeconds, ServiceTier: params.ServiceTier,
ReasoningEffort: params.ReasoningEffort, APIKeyEnv: params.APIKeyEnv, ExtraParams: extra,
},
APIKeyRequired: inspection.APIKeyRequired,
}
}
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 {
@@ -654,27 +571,6 @@ func runRequirementsInspection[I, S any](invocation commandkit.Invocation, suite
})
}
func renderPromptReport(invocation commandkit.Invocation, inspection *promptkit.PromptInspection, format string, explanation commandkit.Explanation) commandkit.Outcome {
return renderPreparedPromptReport(invocation, explanation, func() ([]byte, error) {
report := promptReportFromInspection(inspection)
if format == "yaml" {
return encodePromptYAMLBytes(report)
}
return []byte(formatPromptText(report)), nil
})
}
func renderProfileReport(invocation commandkit.Invocation, inspection *promptkit.ProfileInspection, format string, explanation commandkit.Explanation) commandkit.Outcome {
return renderPreparedPromptReport(invocation, explanation, func() ([]byte, error) {
report := profileReportFromInspection(inspection)
if format == "yaml" {
return encodePromptYAMLBytes(report)
}
text, err := formatProfileText(report)
return []byte(text), err
})
}
// 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 {
@@ -696,42 +592,6 @@ func renderPreparedPromptReport(invocation commandkit.Invocation, explanation co
return commandkit.Success()
}
func formatPromptText(report promptInspectionReport) string {
var output strings.Builder
fmt.Fprintf(&output, "Prompt ID: %s\n", strconv.Quote(report.PromptID))
fmt.Fprintf(&output, "Prompt hash: %s\n", strconv.Quote(report.PromptHash))
fmt.Fprintf(&output, "Default profile ID: %s\n", strconv.Quote(report.DefaultProfileID))
output.WriteString("Inputs:\n")
for _, input := range report.Inputs {
fmt.Fprintf(&output, " - name: %s\n required: %t\n content_type: %s\n description: %s\n", strconv.Quote(input.Name), input.Required, strconv.Quote(input.ContentType), strconv.Quote(input.Description))
}
if len(report.Inputs) == 0 {
output.WriteString(" []\n")
}
fmt.Fprintf(&output, "Output contract:\n format: %s\n validation_mode: %s\n schema_path: %s\n repair_attempts: %d\n", strconv.Quote(report.OutputContract.Format), strconv.Quote(report.OutputContract.ValidationMode), strconv.Quote(report.OutputContract.SchemaPath), report.OutputContract.RepairAttempts)
return output.String()
}
func formatProfileText(report profileInspectionReport) (string, error) {
params := report.EffectiveModelParams
var output strings.Builder
fmt.Fprintf(&output, "Profile ID: %s\n", strconv.Quote(report.ProfileID))
output.WriteString("Effective model params:\n")
fmt.Fprintf(&output, " backend_id: %s\n endpoint: %s\n model: %s\n temperature: %s\n max_tokens: %d\n top_p: %s\n timeout_seconds: %d\n service_tier: %s\n reasoning_effort: %s\n api_key_env: %s\n", strconv.Quote(params.BackendID), strconv.Quote(params.Endpoint), strconv.Quote(params.Model), strconv.FormatFloat(params.Temperature, 'g', -1, 64), params.MaxTokens, strconv.FormatFloat(params.TopP, 'g', -1, 64), params.TimeoutSeconds, strconv.Quote(params.ServiceTier), strconv.Quote(params.ReasoningEffort), strconv.Quote(params.APIKeyEnv))
output.WriteString(" extra_params:\n")
var nested strings.Builder
if err := encodePromptYAML(&nested, params.ExtraParams); err != nil {
return "", err
}
for _, line := range strings.Split(strings.TrimSuffix(nested.String(), "\n"), "\n") {
output.WriteString(" ")
output.WriteString(line)
output.WriteByte('\n')
}
fmt.Fprintf(&output, "API key required: %t\n", report.APIKeyRequired)
return output.String(), nil
}
func encodePromptYAML(writer io.Writer, value any) error {
encoder := yaml.NewEncoder(writer)
encoder.SetIndent(2)
@@ -808,13 +668,6 @@ func definitionFailure(message string, explanation commandkit.Explanation, cause
return outcome
}
func checkDefinitionFormat(format string) error {
if format != "text" && format != "yaml" {
return fmt.Errorf("unsupported format %q; expected text or yaml", format)
}
return nil
}
func parseDefinitionString(value string) (string, error) { return value, nil }
func parseVariableAssignment(raw string) (variableAssignment, error) {