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) }