Add Promptkit configuration and inspection seams

This commit is contained in:
2026-07-31 04:27:36 +00:00
parent 6064af2295
commit 9a17a8de93
15 changed files with 584 additions and 2 deletions

View File

@@ -0,0 +1,59 @@
package cli
import (
"time"
promptkitadapter "gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/promptkit"
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptexec"
)
// PromptExecutorConfig is the project-owned construction input for one prompt
// executor. It keeps adapter implementation types out of Runner's API.
type PromptExecutorConfig struct {
Profile string
ProfileFile string
ProfileDirectory string
Timeout time.Duration
LocalEndpoint string
LocalConcurrencyLimit int
}
// ExecutorFactory constructs one executor for an action.
type ExecutorFactory func(PromptExecutorConfig) (promptexec.Executor, error)
func (r Runner) promptExecutor(cfg config.PromptkitConfig) (promptexec.Executor, error) {
factory := r.ExecutorFactory
if factory == nil {
factory = newPromptkitExecutor
}
return factory(promptExecutorConfig(cfg))
}
func promptExecutorConfig(cfg config.PromptkitConfig) PromptExecutorConfig {
result := PromptExecutorConfig{
Profile: cfg.Profile,
ProfileFile: cfg.ProfileFile,
ProfileDirectory: cfg.ProfileDir,
Timeout: cfg.Timeout,
}
if cfg.Local.Endpoint != "" {
result.LocalEndpoint = cfg.Local.Endpoint
result.LocalConcurrencyLimit = cfg.Local.ConcurrencyLimit
}
return result
}
func newPromptkitExecutor(cfg PromptExecutorConfig) (promptexec.Executor, error) {
return promptkitadapter.New(promptkitAdapterConfig(cfg))
}
func promptkitAdapterConfig(cfg PromptExecutorConfig) promptkitadapter.Config {
return promptkitadapter.Config{
ProfileDirectory: cfg.ProfileDirectory,
ProfileFile: cfg.ProfileFile,
LocalEndpoint: cfg.LocalEndpoint,
LocalConcurrencyLimit: cfg.LocalConcurrencyLimit,
Timeout: cfg.Timeout,
}
}

View File

@@ -0,0 +1,78 @@
package cli
import (
"context"
"testing"
"time"
promptkitadapter "gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/promptkit"
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptexec"
)
func TestRunnerPromptExecutorMapsConfigurationOnce(t *testing.T) {
var calls int
var received PromptExecutorConfig
runner := Runner{ExecutorFactory: func(value PromptExecutorConfig) (promptexec.Executor, error) {
calls++
received = value
return factoryExecutor{}, nil
}}
executor, err := runner.promptExecutor(config.PromptkitConfig{
Profile: "selected-profile",
ProfileFile: "/etc/weatherreporter/profile.yml",
Timeout: 45 * time.Second,
Local: config.PromptkitLocalConfig{
Endpoint: "http://127.0.0.1:8080",
ConcurrencyLimit: 3,
},
})
if err != nil || executor == nil || calls != 1 {
t.Fatalf("executor/error/calls = %#v/%v/%d", executor, err, calls)
}
want := PromptExecutorConfig{
Profile: "selected-profile", ProfileFile: "/etc/weatherreporter/profile.yml", Timeout: 45 * time.Second,
LocalEndpoint: "http://127.0.0.1:8080", LocalConcurrencyLimit: 3,
}
if received != want {
t.Fatalf("factory config = %#v, want %#v", received, want)
}
}
func TestPromptExecutorConfigLeavesBlankLocalBackendUnregistered(t *testing.T) {
value := promptExecutorConfig(config.PromptkitConfig{
Timeout: 2 * time.Minute,
Local: config.PromptkitLocalConfig{ConcurrencyLimit: 1},
})
if value.LocalEndpoint != "" || value.LocalConcurrencyLimit != 0 {
t.Fatalf("executor config = %#v, want no local backend", value)
}
}
func TestPromptkitAdapterConfigMapsExecutorSettings(t *testing.T) {
adapterConfig := promptkitAdapterConfig(PromptExecutorConfig{
ProfileDirectory: "/etc/weatherreporter/profiles",
Timeout: 30 * time.Second, LocalEndpoint: "http://127.0.0.1:8080", LocalConcurrencyLimit: 2,
})
want := promptkitadapter.Config{
ProfileDirectory: "/etc/weatherreporter/profiles",
Timeout: 30 * time.Second, LocalEndpoint: "http://127.0.0.1:8080", LocalConcurrencyLimit: 2,
}
if adapterConfig != want {
t.Fatalf("adapter config = %#v, want %#v", adapterConfig, want)
}
}
type factoryExecutor struct{}
func (factoryExecutor) InspectPrompt(context.Context, string, string) (promptexec.PromptInspection, error) {
return promptexec.PromptInspection{}, nil
}
func (factoryExecutor) InspectProfile(context.Context, string) (promptexec.ProfileInspection, error) {
return promptexec.ProfileInspection{}, nil
}
func (factoryExecutor) Execute(context.Context, promptexec.ExecuteRequest, promptexec.PreparationCallback) (*promptexec.Execution, error) {
return nil, nil
}

View File

@@ -40,7 +40,8 @@ Options:
`
type Runner struct {
Clock timeutil.Clock
Clock timeutil.Clock
ExecutorFactory ExecutorFactory
}
func Run(ctx context.Context, args []string, stdout io.Writer, stderr io.Writer) error {