Files
commandkit/integrations/ckprompt/request_flags.go

233 lines
11 KiB
Go

package ckprompt
import (
"fmt"
"strconv"
commandkit "go.maximumdirect.net/commandkit"
promptkit "go.maximumdirect.net/promptkit"
)
// requestFlagDefinition keeps the canonical spellings, help text, and optional
// command-scoped aliases together for consumers of the standard flag sets.
type requestFlagDefinition struct {
meta commandkit.FlagMeta
aliases []string
}
var requestFlags = struct {
Endpoint requestFlagDefinition
Model requestFlagDefinition
Temperature requestFlagDefinition
MaxTokens requestFlagDefinition
TopP requestFlagDefinition
TimeoutSeconds requestFlagDefinition
ServiceTier requestFlagDefinition
ReasoningEffort requestFlagDefinition
APIKeyEnv requestFlagDefinition
TransientRetries requestFlagDefinition
ExtraParams requestFlagDefinition
Prompt requestFlagDefinition
Profile requestFlagDefinition
Backend requestFlagDefinition
SessionID requestFlagDefinition
Var requestFlagDefinition
InputText requestFlagDefinition
InputFile requestFlagDefinition
}{
Endpoint: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-endpoint", Usage: "Use this endpoint."}, []string{"endpoint"}},
Model: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-model", Usage: "Use this model."}, []string{"model"}},
Temperature: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-temperature", Usage: "Use this temperature."}, []string{"temperature"}},
MaxTokens: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-max-tokens", Usage: "Use this maximum token count."}, []string{"max-tokens"}},
TopP: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-top-p", Usage: "Use this top-p value."}, []string{"top-p"}},
TimeoutSeconds: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-timeout-seconds", Usage: "Set the request timeout in seconds."}, []string{"timeout-seconds"}},
ServiceTier: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-service-tier", Usage: "Use this service tier."}, []string{"service-tier"}},
ReasoningEffort: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-reasoning-effort", Usage: "Use this reasoning effort."}, []string{"reasoning-effort"}},
APIKeyEnv: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-api-key-env", Usage: "Name the environment variable containing the API key."}, []string{"api-key-env"}},
TransientRetries: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-transient-retries", Usage: "Set the retry count for transient inference failures."}, []string{"transient-retries"}},
ExtraParams: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-extra-params", Usage: "Supply provider parameters as a JSON object."}, []string{"extra-params"}},
Prompt: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-prompt", Usage: "Select a prompt by ID."}, []string{"prompt"}},
Profile: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-profile", Usage: "Select a profile by ID."}, []string{"profile"}},
Backend: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-backend", Usage: "Select a backend by ID."}, []string{"backend"}},
SessionID: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-session-id", Usage: "Supply the session ID."}, []string{"session-id"}},
Var: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-var", Usage: "Supply a prompt variable as KEY=VALUE."}, []string{"var"}},
InputText: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-input-text", Usage: "Supply text for a prompt input as NAME=TEXT."}, []string{"input-text", "llm-input", "input"}},
InputFile: requestFlagDefinition{commandkit.FlagMeta{Name: "llm-input-file", Usage: "Supply a file for a prompt input as NAME=PATH."}, []string{"input-file"}},
}
// ExecutionInput contains request-scoped execution controls supplied on the CLI.
// A nil override or retry count leaves the corresponding native default intact.
type ExecutionInput struct {
Execution *promptkit.ExecutionTargetOverride
TransientRetries *int
}
// ExecutionFlags exposes independently selectable execution-control bindings.
type ExecutionFlags[I any] struct {
Endpoint, Model, Temperature, MaxTokens, TopP, TimeoutSeconds commandkit.Flag[I]
ServiceTier, ReasoningEffort, APIKeyEnv, TransientRetries, ExtraParams commandkit.Flag[I]
}
// All returns all execution controls.
func (f ExecutionFlags[I]) All() []commandkit.Flag[I] {
return appendRequestBindings(nil, "", nil, f.bindings()...)
}
func (f ExecutionFlags[I]) bindings() []requestBinding[I] {
return []requestBinding[I]{
{f.Endpoint, requestFlags.Endpoint}, {f.Model, requestFlags.Model},
{f.Temperature, requestFlags.Temperature}, {f.MaxTokens, requestFlags.MaxTokens},
{f.TopP, requestFlags.TopP}, {f.TimeoutSeconds, requestFlags.TimeoutSeconds},
{f.ServiceTier, requestFlags.ServiceTier}, {f.ReasoningEffort, requestFlags.ReasoningEffort},
{f.APIKeyEnv, requestFlags.APIKeyEnv}, {f.TransientRetries, requestFlags.TransientRetries},
{f.ExtraParams, requestFlags.ExtraParams},
}
}
// NewExecutionFlags binds execution controls to a field of the caller's input.
// Parsed provider parameters are copied when the binding is applied.
func NewExecutionFlags[I any](destination func(*I) *ExecutionInput) ExecutionFlags[I] {
target := func(input *I) *promptkit.ExecutionTargetOverride {
value := destination(input)
if value.Execution == nil {
value.Execution = &promptkit.ExecutionTargetOverride{}
}
return value.Execution
}
return ExecutionFlags[I]{
Endpoint: executionFlag(requestFlags.Endpoint.meta, parseDefinitionString, func(i *I, v string) { target(i).Endpoint = v }),
Model: executionFlag(requestFlags.Model.meta, parseDefinitionString, func(i *I, v string) { target(i).Model = v }),
Temperature: executionFlag(requestFlags.Temperature.meta, parseExecutionFloat, func(i *I, v float64) { target(i).Temperature = &v }),
MaxTokens: executionFlag(requestFlags.MaxTokens.meta, strconv.Atoi, func(i *I, v int) { target(i).MaxTokens = &v }),
TopP: executionFlag(requestFlags.TopP.meta, parseExecutionFloat, func(i *I, v float64) { target(i).TopP = &v }),
TimeoutSeconds: executionFlag(requestFlags.TimeoutSeconds.meta, strconv.Atoi, func(i *I, v int) { target(i).TimeoutSeconds = &v }),
ServiceTier: executionFlag(requestFlags.ServiceTier.meta, parseDefinitionString, func(i *I, v string) { target(i).ServiceTier = v }),
ReasoningEffort: executionFlag(requestFlags.ReasoningEffort.meta, parseDefinitionString, func(i *I, v string) { target(i).ReasoningEffort = &v }),
APIKeyEnv: executionFlag(requestFlags.APIKeyEnv.meta, parseDefinitionString, func(i *I, v string) { target(i).APIKeyEnv = v }),
TransientRetries: executionFlag(requestFlags.TransientRetries.meta, strconv.Atoi, func(i *I, v int) { destination(i).TransientRetries = &v }),
ExtraParams: executionFlag(requestFlags.ExtraParams.meta, parseExtraParams, func(i *I, v map[string]any) { target(i).ExtraParams = copyProviderParams(v) }),
}
}
func copyProviderParams(params map[string]any) map[string]any {
if params == nil {
return nil
}
result := make(map[string]any, len(params))
for key, value := range params {
result[key] = copyProviderValue(value)
}
return result
}
func copyProviderValue(value any) any {
switch value := value.(type) {
case map[string]any:
return copyProviderParams(value)
case []any:
if value == nil {
return []any(nil)
}
result := make([]any, len(value))
for i, element := range value {
result[i] = copyProviderValue(element)
}
return result
default:
return value
}
}
func executionFlag[I, V any](meta commandkit.FlagMeta, parse func(string) (V, error), set func(*I, V)) commandkit.Flag[I] {
return commandkit.ValueFlag(meta, (*V)(nil), parse, func(i *I, value commandkit.Supplied[V]) {
if value.Set {
set(i, value.Value)
}
})
}
func parseExtraParams(raw string) (map[string]any, error) {
value, err := commandkit.ParseCLIJSON(raw)
if err != nil {
return nil, err
}
object, ok := value.(map[string]any)
if !ok {
return nil, fmt.Errorf("provider parameters must be a JSON object")
}
return object, nil
}
// ResourceInput retains whether each resource identifier was explicitly supplied.
type ResourceInput struct {
Prompt, Profile, Backend, SessionID commandkit.Supplied[string]
}
// ResourceFlags exposes independently selectable resource and session bindings.
type ResourceFlags[I any] struct {
Prompt, Profile, Backend, SessionID commandkit.Flag[I]
}
// All returns all resource bindings.
func (f ResourceFlags[I]) All() []commandkit.Flag[I] {
return []commandkit.Flag[I]{f.Prompt, f.Profile, f.Backend, f.SessionID}
}
// NewResourceFlags binds resource identifiers to a field of the caller's input.
func NewResourceFlags[I any](destination func(*I) *ResourceInput) ResourceFlags[I] {
flag := func(meta commandkit.FlagMeta, selectField func(*ResourceInput) *commandkit.Supplied[string]) commandkit.Flag[I] {
return commandkit.ValueFlag(meta, (*string)(nil), parseDefinitionString, func(i *I, v commandkit.Supplied[string]) { *selectField(destination(i)) = v })
}
return ResourceFlags[I]{
Prompt: flag(requestFlags.Prompt.meta, func(v *ResourceInput) *commandkit.Supplied[string] { return &v.Prompt }),
Profile: flag(requestFlags.Profile.meta, func(v *ResourceInput) *commandkit.Supplied[string] { return &v.Profile }),
Backend: flag(requestFlags.Backend.meta, func(v *ResourceInput) *commandkit.Supplied[string] { return &v.Backend }),
SessionID: flag(requestFlags.SessionID.meta, func(v *ResourceInput) *commandkit.Supplied[string] { return &v.SessionID }),
}
}
// PromptInputs contains caller variables and native artifact references.
type PromptInputs struct {
Vars map[string]string
Inputs map[string]promptkit.ArtifactRef
}
// PromptInputFlags exposes variable and named artifact input bindings.
type PromptInputFlags[I any] struct {
Variable, Text, File commandkit.Flag[I]
}
// All returns all variable and input bindings.
func (f PromptInputFlags[I]) All() []commandkit.Flag[I] {
return []commandkit.Flag[I]{f.Variable, f.Text, f.File}
}
// NewPromptInputFlags binds caller variables and input references to a field of
// the caller's input.
func NewPromptInputFlags[I any](destination func(*I) *PromptInputs) PromptInputFlags[I] {
variable := commandkit.RepeatFlag(requestFlags.Var.meta, []variableAssignment(nil), parseVariableAssignment, func(i *I, v commandkit.Supplied[[]variableAssignment]) {
if !v.Set {
return
}
values := make(map[string]string, len(v.Value))
for _, assignment := range v.Value {
values[assignment.key] = assignment.value
}
destination(i).Vars = values
})
inputFlag := func(meta commandkit.FlagMeta, makeRef func(string) promptkit.ArtifactRef) commandkit.Flag[I] {
return commandkit.OccurrenceFlag(meta, parseInputAssignment, func(i *I, v commandkit.Supplied[inputAssignment]) {
input := destination(i)
if input.Inputs == nil {
input.Inputs = make(map[string]promptkit.ArtifactRef)
}
input.Inputs[v.Value.name] = makeRef(v.Value.value)
})
}
return PromptInputFlags[I]{Variable: variable, Text: inputFlag(requestFlags.InputText.meta, promptkit.Inline), File: inputFlag(requestFlags.InputFile.meta, promptkit.File)}
}
func parseExecutionFloat(value string) (float64, error) {
return strconv.ParseFloat(value, 64)
}