100 lines
2.7 KiB
Go
100 lines
2.7 KiB
Go
package scriptorium
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
artifactadapter "gitea.maximumdirect.net/eric/scriptorium/internal/artifact"
|
|
"gitea.maximumdirect.net/eric/scriptorium/internal/defaults"
|
|
"gitea.maximumdirect.net/eric/scriptorium/internal/llm"
|
|
"gitea.maximumdirect.net/eric/scriptorium/internal/profile"
|
|
"gitea.maximumdirect.net/eric/scriptorium/internal/prompt"
|
|
"gitea.maximumdirect.net/eric/scriptorium/internal/promptdef"
|
|
"gitea.maximumdirect.net/eric/scriptorium/internal/usecase"
|
|
"gitea.maximumdirect.net/eric/scriptorium/internal/validate"
|
|
)
|
|
|
|
// ErrInvalidConfig indicates invalid public engine configuration.
|
|
var ErrInvalidConfig = errors.New("invalid engine configuration")
|
|
|
|
// Engine prepares Scriptorium prompt requests.
|
|
type Engine struct {
|
|
runner *usecase.Runner
|
|
}
|
|
|
|
// Config configures a public Scriptorium engine.
|
|
type Config struct {
|
|
PromptDir string
|
|
ProfileDir string
|
|
SchemaDir string
|
|
Timeout time.Duration
|
|
HTTPClient *http.Client
|
|
}
|
|
|
|
// Option customizes engine construction.
|
|
type Option func(*engineOptions) error
|
|
|
|
type engineOptions struct{}
|
|
|
|
// NewEngine constructs an Engine using the same default internal components as
|
|
// the CLI and HTTP adapters.
|
|
func NewEngine(cfg Config, opts ...Option) (*Engine, error) {
|
|
if strings.TrimSpace(cfg.PromptDir) == "" {
|
|
return nil, fmt.Errorf("%w: prompt directory is required", ErrInvalidConfig)
|
|
}
|
|
if strings.TrimSpace(cfg.ProfileDir) == "" {
|
|
return nil, fmt.Errorf("%w: profile directory is required", ErrInvalidConfig)
|
|
}
|
|
|
|
var options engineOptions
|
|
for _, opt := range opts {
|
|
if opt == nil {
|
|
continue
|
|
}
|
|
if err := opt(&options); err != nil {
|
|
return nil, fmt.Errorf("%w: %v", ErrInvalidConfig, err)
|
|
}
|
|
}
|
|
|
|
schemaDir := cfg.SchemaDir
|
|
if strings.TrimSpace(schemaDir) == "" {
|
|
schemaDir = defaults.SchemaDirDefault
|
|
}
|
|
|
|
llmClient, err := llm.NewOpenAICompatibleClient(llm.OpenAICompatibleConfig{
|
|
Timeout: cfg.Timeout,
|
|
HTTPClient: cfg.HTTPClient,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: %v", ErrInvalidConfig, err)
|
|
}
|
|
|
|
return &Engine{
|
|
runner: usecase.NewRunner(
|
|
promptdef.NewFilesystemRepository(cfg.PromptDir),
|
|
profile.NewFilesystemRepository(cfg.ProfileDir),
|
|
artifactadapter.NewCompositeReader(),
|
|
prompt.NewGoRenderer(),
|
|
llmClient,
|
|
validate.NewStandardValidator(schemaDir),
|
|
),
|
|
}, nil
|
|
}
|
|
|
|
// Prepare resolves a prompt request without calling an LLM.
|
|
func (e *Engine) Prepare(ctx context.Context, req RunRequest) (*PreparedRun, error) {
|
|
if e == nil || e.runner == nil {
|
|
return nil, fmt.Errorf("%w: engine is nil", ErrInvalidConfig)
|
|
}
|
|
|
|
prepared, err := e.runner.Prepare(ctx, toDomainRunRequest(req))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return fromDomainPreparedRun(prepared), nil
|
|
}
|