Files
promptkit/requirements.go

238 lines
10 KiB
Go

package promptkit
import (
"context"
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
"gitea.maximumdirect.net/eric/promptkit/internal/usecase"
)
// RequirementInspectionRequest selects a prompt and supplies caller context
// for requirement discovery. It requires no artifact bindings or credentials.
type RequirementInspectionRequest struct {
// PromptID is the exact, case-sensitive configured prompt ID.
PromptID string
// ProfileID selects the exact effective profile identity. If empty, the
// prompt's default profile is used; the profile need not be ready for
// execution.
ProfileID string
// Vars supplies string values for caller-variable lookups and simple
// conditions. Presence is determined by exact map key, including an empty
// value. Inspection reports caller keys and presence, not their values.
// A supplied value may nevertheless appear as a resolved input identifier.
Vars map[string]string
// SessionID, when nonblank after the normal session-ID normalization,
// bypasses the prompt's session template during discovery.
SessionID string
}
// RequirementKind describes how a selected prompt may depend on an input or
// caller variable for the supplied context. It is independent of whether a
// caller-variable value is present.
type RequirementKind string
const (
// RequirementRequired identifies an input declared required or a dependency
// reached by a recognized definite access.
RequirementRequired RequirementKind = "required"
// RequirementConditional identifies a dependency whose reachability or
// identity remains unresolved for the supplied context.
RequirementConditional RequirementKind = "conditional"
// RequirementOptional identifies a dependency known not to be required,
// including a nonrequired declaration, optional lookup, or reference used
// only on a known skipped path.
RequirementOptional RequirementKind = "optional"
)
// InputRequirement describes a declared or template-referenced prompt input.
type InputRequirement struct {
// Name is the exact input identifier.
Name string
// Kind is the strongest classification established for this name.
Kind RequirementKind
// Declared reports whether the name appears in the effective input
// declarations; helper-only references have Declared false.
Declared bool
// ContentType preserves effective metadata presence: nil means absent or
// null, while a non-nil pointer may refer to an empty string.
ContentType *string
// Description preserves effective metadata presence independently of
// Explanation, including an explicitly empty string.
Description *string
// Explanation is safe human-readable context, such as why a reference is
// optional because it occurs only on a known skipped path. It is not a
// machine-readable contract.
Explanation string
}
// VariableRequirement describes a caller-map key referenced by a prompt.
type VariableRequirement struct {
// Name is the exact caller-map key, not a template-local name.
Name string
// Kind is the strongest classification established for this key.
Kind RequirementKind
// Supplied reports exact-key presence in the request Vars map. An empty
// string is supplied; the value itself is not returned.
Supplied bool
// Explanation is safe human-readable context and is not a machine-readable
// contract.
Explanation string
}
// RequirementIssueCode identifies a limitation that can affect discovery.
type RequirementIssueCode string
const (
// RequirementIssueMissingVariable means an absent caller value prevents
// resolving a dependency decision or input name.
RequirementIssueMissingVariable RequirementIssueCode = "missing_variable"
// RequirementIssueInputContent means unavailable input content affects
// dependency discovery.
RequirementIssueInputContent RequirementIssueCode = "input_content"
// RequirementIssueDynamicInputName means a referenced input name cannot be
// determined for the supplied context.
RequirementIssueDynamicInputName RequirementIssueCode = "dynamic_input_name"
// RequirementIssueUnsupportedConstruct means a dependency-relevant
// expression or scope is outside the supported discovery analysis.
RequirementIssueUnsupportedConstruct RequirementIssueCode = "unsupported_construct"
)
// RequirementIssue describes one discovery limitation using safe summary
// text. Descriptions are not intended as machine-readable details.
type RequirementIssue struct {
// Code is the machine-readable limitation category.
Code RequirementIssueCode
// InputName identifies an affected input when one is known; empty means no
// particular input was identified.
InputName string
// VariableName identifies an affected caller key when one is known; empty
// means no particular key was identified.
VariableName string
// Description is a safe human-readable summary, not a string to parse.
Description string
}
// RequirementInspection contains discovered dependencies for one selected
// prompt context. Input and variable entries are sorted by exact name. Issues
// are deduplicated by code and associated names, then sorted by that tuple.
// Their descriptions and entry explanations are safe summaries, not
// machine-readable details.
//
// Complete describes the supported analysis's discovery coverage, not whether
// requirements are supplied, templates will render, artifacts or schemas are
// valid, or execution is ready. A complete result can still contain required
// inputs or missing required variables. Valid templates using dependency-
// relevant constructs outside the deliberately small recognition scope may
// produce useful partial inventories with Complete false; this is not a
// template-definition error or an authoring prohibition.
type RequirementInspection struct {
// PromptID is the selected exact prompt ID.
PromptID string
// PromptVersion is the selected definition's descriptive version label.
PromptVersion string
// ProfileID is the selected profile identity; it does not indicate
// execution readiness.
ProfileID string
// Inputs contains declared inputs and recognizable template references,
// including entries known to be optional.
Inputs []InputRequirement
// Variables contains recognizable caller-map keys, including keys used
// only on known skipped paths.
Variables []VariableRequirement
// Complete reports discovery coverage, not requirement satisfaction or
// execution readiness.
Complete bool
// Issues contains deduplicated, structured discovery limitations.
Issues []RequirementIssue
}
// InspectRequirements discovers inputs and caller variables for one selected
// prompt before its input artifacts exist. It uses exact, case-sensitive
// prompt selection and requires a profile ID either in the request or the
// selected prompt's default. The selected profile establishes identity only;
// it need not be execution-ready. A normalized nonblank SessionID bypasses the
// captured session template. Vars are copied before analysis, and returned
// slices and metadata pointers belong to the caller.
//
// Discovery recognizes literal input names, direct caller-map keys, literal-
// key root-map lookups, simple caller-derived input names, and if/with
// conditions based on direct values, literals, negation, or two-string
// equality/inequality. It classifies recognizable references on known skipped
// paths as optional. Other dependency-relevant expressions or scopes may leave
// a useful partial result with Complete false. The returned inventories do
// not reject otherwise valid templates or prohibit their use.
//
// Results exclude artifact payloads, prompt contents, and unrelated supplied
// values. Resolved input identifiers may equal the caller values that supply
// them; those identifiers are intentionally included in the inventory.
//
// This method does not perform artifact, schema, credential, rendering,
// validation, capacity, observation, provider, or execution work. Complete
// describes dependency discovery, not whether every dependency is supplied or
// the prompt is ready to execute. Discovery limitations return a partial
// inspection and no error; selection and operational failures return no
// inspection. Blank prompt IDs, invalid session IDs, and absent profile
// selection match ErrInvalidRequest; an absent profile selection also matches
// ErrProfileRequired. Missing prompt/profile IDs match ErrPromptNotFound or
// ErrProfileNotFound. Other selection/snapshot failures match ErrPromptLoad or
// ErrProfileLoad. When cancellation prevents lookup or analysis, no inspection
// is returned and the context error remains discoverable with errors.Is. A nil
// Engine matches ErrInvalidConfig.
func (e *Engine) InspectRequirements(
ctx context.Context,
req RequirementInspectionRequest,
) (*RequirementInspection, error) {
if e == nil || e.internal == nil {
return nil, ErrInvalidConfig
}
inspection, err := e.internal.InspectRequirements(ctx, usecase.RequirementInspectionRequest{
PromptID: req.PromptID, ProfileID: req.ProfileID,
Vars: copyStringMap(req.Vars), SessionID: req.SessionID,
})
if err != nil {
return nil, mapPublicError(err)
}
return fromDomainRequirementInspection(inspection), nil
}
func fromDomainRequirementInspection(inspection *domain.RequirementInspection) *RequirementInspection {
if inspection == nil {
return nil
}
result := &RequirementInspection{
PromptID: inspection.PromptID, PromptVersion: inspection.PromptVersion,
ProfileID: inspection.ProfileID, Complete: inspection.Complete,
Inputs: make([]InputRequirement, len(inspection.Inputs)),
Variables: make([]VariableRequirement, len(inspection.Variables)),
Issues: make([]RequirementIssue, len(inspection.Issues)),
}
for index, input := range inspection.Inputs {
result.Inputs[index] = InputRequirement{
Name: input.Name, Kind: RequirementKind(input.Kind), Declared: input.Declared,
ContentType: copyRequirementString(input.ContentType), Description: copyRequirementString(input.Description),
Explanation: input.Explanation,
}
}
for index, variable := range inspection.Variables {
result.Variables[index] = VariableRequirement{
Name: variable.Name, Kind: RequirementKind(variable.Kind), Supplied: variable.Supplied,
Explanation: variable.Explanation,
}
}
for index, issue := range inspection.Issues {
result.Issues[index] = RequirementIssue{
Code: RequirementIssueCode(issue.Code), InputName: issue.InputName,
VariableName: issue.VariableName, Description: issue.Description,
}
}
return result
}
func copyRequirementString(value *string) *string {
if value == nil {
return nil
}
copy := *value
return &copy
}