Files
audita/internal/cli/app_wiring.go

115 lines
4.4 KiB
Go

package cli
import (
"context"
"fmt"
"net/http"
"reflect"
"strings"
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/llm"
"gitea.maximumdirect.net/eric/audita/v3/internal/prompts"
"go.maximumdirect.net/ckconfig"
"go.maximumdirect.net/ckprompt"
commandkit "go.maximumdirect.net/commandkit"
ck "go.maximumdirect.net/configkit"
pk "go.maximumdirect.net/promptkit"
"go.maximumdirect.net/releasekit/buildinfo"
)
type invocationState struct {
engines []engineEntry
client *http.Client
}
type engineEntry struct {
selection ckconfig.Selection
engine *pk.Engine
}
func loadApplication(ctx context.Context, selection ckconfig.Selection, _ struct{}, _ *invocationState) (*ck.Result[config.Config], error) {
if err := ctx.Err(); err != nil {
return nil, err
}
var source ck.Source
if selection.Config.Set {
if strings.TrimSpace(selection.Config.Value) == "" {
return nil, fmt.Errorf("--config requires a nonempty path")
}
source = ck.File(selection.Config.Value)
} else {
source = ck.SearchConfig("audita", ck.Searches(ck.WorkingDirectory(), ck.UserConfigDirectory(), ck.UnixSystemDirectories()))
}
return config.Load(ck.Input{Source: source}, selection.Overlay.Value, selection.Overrides...)
}
func loadPromptEngine(ctx context.Context, selection ckconfig.Selection, input struct{}, state *invocationState) (*pk.Engine, error) {
loaded, err := loadApplication(ctx, selection, input, state)
if err != nil {
return nil, err
}
var client *http.Client
if state != nil {
client = state.client
}
runtime, err := llm.NewRuntime(ctx, loaded, client)
if err != nil {
return nil, err
}
return runtime.Engine, nil
}
func capturePromptEngine(ctx context.Context, selection ckconfig.Selection, input struct{}, state *invocationState) (*pk.Engine, error) {
if state != nil {
for _, entry := range state.engines {
if reflect.DeepEqual(entry.selection, selection) {
return entry.engine, nil
}
}
}
engine, err := loadPromptEngine(ctx, selection, input, state)
if err != nil {
return nil, err
}
if state != nil {
state.engines = append(state.engines, engineEntry{selection: selection, engine: engine})
}
return engine, nil
}
func buildApplication(extra ...commandkit.Declaration) (*commandkit.App, error) {
return buildApplicationWithClient(nil, extra...)
}
func buildApplicationWithClient(client *http.Client, extra ...commandkit.Declaration) (*commandkit.App, error) {
appScope := ckconfig.FromConfigKit("app", "Audita application settings.", loadApplication)
appScope.Features.Overrides = true
promptScope := ckprompt.ConfigScope(loadPromptEngine)
configCommand := ckconfig.Command(ckconfig.Suite[struct{}, *invocationState]{NewState: func() *invocationState { return &invocationState{client: client} }, Scopes: []ckconfig.Scope[struct{}, *invocationState]{appScope, promptScope}})
definitions := ckprompt.WithNamespaces(ckprompt.FromEngine(capturePromptEngine), pk.Namespaces{})
definitions.NewState = func() *invocationState { return &invocationState{client: client} }
definitions = ckprompt.WithRequestValidation(definitions, capturePromptEngine, func(_ context.Context, seed ckprompt.ValidationRequest, _ struct{}, _ *invocationState) (pk.RunRequest, error) {
return applicationPromptRequest(seed.Request, seed.Namespaces), nil
})
definitions = ckprompt.WithRequestPreparation(definitions, capturePromptEngine, func(_ context.Context, seed ckprompt.PreparationRequest, _ struct{}, _ *invocationState) (pk.RunRequest, error) {
return applicationPromptRequest(seed.Request, seed.Namespaces), nil
})
definitions = ckprompt.WithSmokeTesting(definitions, capturePromptEngine, nil)
declarations := []commandkit.Declaration{configCommand, commandkit.Completion("audita"), processCommand()}
declarations = append(declarations, ckprompt.Declarations(definitions)...)
declarations = append(declarations, extra...)
return commandkit.New(commandkit.Identity{Name: "audita", Version: buildinfo.Version()}, declarations...)
}
// applicationPromptRequest supplies instructions only for Audita's maintained prompts.
func applicationPromptRequest(request pk.RunRequest, namespaces *pk.Namespaces) pk.RunRequest {
id := request.PromptID
if !strings.Contains(id, "/") && namespaces != nil && namespaces.Prompt != "" {
id = namespaces.Prompt + "/" + id
}
if _, ok := prompts.LookupMetadata(id); ok {
request.Vars = prompts.WithSharedVariables(request.Vars)
}
return request
}