Adopt Promptkit at application boundaries

This commit is contained in:
2026-07-28 14:04:29 +00:00
parent 309fe9b7ea
commit e13610481d
10 changed files with 169 additions and 166 deletions

1
go.mod
View File

@@ -3,6 +3,7 @@ module gitea.maximumdirect.net/eric/scriptorium
go 1.25.5 go 1.25.5
require ( require (
gitea.maximumdirect.net/eric/promptkit v0.1.0
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )

2
go.sum
View File

@@ -1,3 +1,5 @@
gitea.maximumdirect.net/eric/promptkit v0.1.0 h1:vuKeBxkiY8E54LRFbLQFjlJJCiOfMvB1++DYBCrD/ug=
gitea.maximumdirect.net/eric/promptkit v0.1.0/go.mod h1:R95NM6fbMDGDC0/UomgnSBP6ui2ns+8SZb8bESNvrDQ=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=

View File

@@ -12,7 +12,7 @@ import (
"strings" "strings"
"time" "time"
"gitea.maximumdirect.net/eric/scriptorium" "gitea.maximumdirect.net/eric/promptkit"
httpadapter "gitea.maximumdirect.net/eric/scriptorium/internal/adapter/http" httpadapter "gitea.maximumdirect.net/eric/scriptorium/internal/adapter/http"
appconfig "gitea.maximumdirect.net/eric/scriptorium/internal/config" appconfig "gitea.maximumdirect.net/eric/scriptorium/internal/config"
"gitea.maximumdirect.net/eric/scriptorium/internal/defaults" "gitea.maximumdirect.net/eric/scriptorium/internal/defaults"
@@ -209,7 +209,7 @@ func serveCommand(args []string, stderr io.Writer) int {
promptDir: cfg.promptDir, promptDir: cfg.promptDir,
profileDir: cfg.profileDir, profileDir: cfg.profileDir,
schemaDir: cfg.schemaDir, schemaDir: cfg.schemaDir,
}, scriptorium.WithArtifactReader(artifactReader)) }, promptkit.WithArtifactReader(artifactReader))
if err != nil { if err != nil {
fmt.Fprintf(stderr, "engine error: %v\n", err) fmt.Fprintf(stderr, "engine error: %v\n", err)
return ExitRuntimeError return ExitRuntimeError
@@ -538,36 +538,36 @@ func validateRequiredLibraryDirs(promptDir string) error {
return nil return nil
} }
func newEngine(cfg *runConfig, options ...scriptorium.Option) (*scriptorium.Engine, error) { func newEngine(cfg *runConfig, options ...promptkit.Option) (*promptkit.Engine, error) {
return scriptorium.NewEngine(scriptorium.Config{ return promptkit.NewEngine(promptkit.Config{
PromptDir: cfg.promptDir, PromptDir: cfg.promptDir,
ProfileDir: cfg.profileDir, ProfileDir: cfg.profileDir,
SchemaDir: cfg.schemaDir, SchemaDir: cfg.schemaDir,
}, options...) }, options...)
} }
func buildRunRequestFromConfig(cfg *runConfig) (scriptorium.RunRequest, error) { func buildRunRequestFromConfig(cfg *runConfig) (promptkit.RunRequest, error) {
inputMappings, err := parseMappings(cfg.inputRaw, false) inputMappings, err := parseMappings(cfg.inputRaw, false)
if err != nil { if err != nil {
return scriptorium.RunRequest{}, fmt.Errorf("input parse error: %w", err) return promptkit.RunRequest{}, fmt.Errorf("input parse error: %w", err)
} }
varMappings := map[string]string{} varMappings := map[string]string{}
if len(cfg.varRaw) > 0 { if len(cfg.varRaw) > 0 {
varMappings, err = parseMappings(cfg.varRaw, false) varMappings, err = parseMappings(cfg.varRaw, false)
if err != nil { if err != nil {
return scriptorium.RunRequest{}, fmt.Errorf("var parse error: %w", err) return promptkit.RunRequest{}, fmt.Errorf("var parse error: %w", err)
} }
} }
inputs := make(map[string]scriptorium.ArtifactRef, len(inputMappings)) inputs := make(map[string]promptkit.ArtifactRef, len(inputMappings))
for name, path := range inputMappings { for name, path := range inputMappings {
inputs[name] = scriptorium.File(path) inputs[name] = promptkit.File(path)
} }
var modelOverride *scriptorium.ExecutionTargetOverride var modelOverride *promptkit.ExecutionTargetOverride
if cfg.llmBaseURLSet || cfg.modelSet || cfg.temperatureSet || cfg.maxTokensSet || cfg.topPSet || cfg.apiKeyEnvSet || cfg.timeoutSet { if cfg.llmBaseURLSet || cfg.modelSet || cfg.temperatureSet || cfg.maxTokensSet || cfg.topPSet || cfg.apiKeyEnvSet || cfg.timeoutSet {
modelOverride = &scriptorium.ExecutionTargetOverride{ modelOverride = &promptkit.ExecutionTargetOverride{
Endpoint: cfg.llmBaseURL, Endpoint: cfg.llmBaseURL,
Model: cfg.model, Model: cfg.model,
APIKeyEnv: cfg.apiKeyEnv, APIKeyEnv: cfg.apiKeyEnv,
@@ -587,7 +587,7 @@ func buildRunRequestFromConfig(cfg *runConfig) (scriptorium.RunRequest, error) {
} }
} }
return scriptorium.RunRequest{ return promptkit.RunRequest{
PromptID: cfg.promptID, PromptID: cfg.promptID,
ProfileID: cfg.profileID, ProfileID: cfg.profileID,
Inputs: inputs, Inputs: inputs,
@@ -651,17 +651,17 @@ func writeOutput(stdout io.Writer, outputPath string, body []byte) error {
return os.WriteFile(outputPath, body, 0644) return os.WriteFile(outputPath, body, 0644)
} }
func determineExitCode(runErr error, result *scriptorium.RunResult) int { func determineExitCode(runErr error, result *promptkit.RunResult) int {
if runErr != nil { if runErr != nil {
return ExitRuntimeError return ExitRuntimeError
} }
if result != nil && result.Validation.Status == scriptorium.ValidationFailed { if result != nil && result.Validation.Status == promptkit.ValidationFailed {
return ExitValidationFailed return ExitValidationFailed
} }
return ExitOK return ExitOK
} }
func printSummary(stderr io.Writer, res *scriptorium.RunResult) { func printSummary(stderr io.Writer, res *promptkit.RunResult) {
if res == nil { if res == nil {
return return
} }

View File

@@ -17,7 +17,7 @@ import (
"testing" "testing"
"time" "time"
"gitea.maximumdirect.net/eric/scriptorium" "gitea.maximumdirect.net/eric/promptkit"
appconfig "gitea.maximumdirect.net/eric/scriptorium/internal/config" appconfig "gitea.maximumdirect.net/eric/scriptorium/internal/config"
"gitea.maximumdirect.net/eric/scriptorium/internal/defaults" "gitea.maximumdirect.net/eric/scriptorium/internal/defaults"
renderformat "gitea.maximumdirect.net/eric/scriptorium/internal/format" renderformat "gitea.maximumdirect.net/eric/scriptorium/internal/format"
@@ -765,13 +765,13 @@ func TestDetermineExitCode(t *testing.T) {
if got := determineExitCode(errors.New("boom"), nil); got != ExitRuntimeError { if got := determineExitCode(errors.New("boom"), nil); got != ExitRuntimeError {
t.Fatalf("expected runtime exit code, got %d", got) t.Fatalf("expected runtime exit code, got %d", got)
} }
if got := determineExitCode(nil, &scriptorium.RunResult{Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationFailed}}); got != ExitValidationFailed { if got := determineExitCode(nil, &promptkit.RunResult{Validation: promptkit.ValidationResult{Status: promptkit.ValidationFailed}}); got != ExitValidationFailed {
t.Fatalf("expected validation exit code, got %d", got) t.Fatalf("expected validation exit code, got %d", got)
} }
if got := determineExitCode(nil, &scriptorium.RunResult{Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed}}); got != ExitOK { if got := determineExitCode(nil, &promptkit.RunResult{Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed}}); got != ExitOK {
t.Fatalf("expected success exit code for passed validation, got %d", got) t.Fatalf("expected success exit code for passed validation, got %d", got)
} }
if got := determineExitCode(nil, &scriptorium.RunResult{Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationSkipped}}); got != ExitOK { if got := determineExitCode(nil, &promptkit.RunResult{Validation: promptkit.ValidationResult{Status: promptkit.ValidationSkipped}}); got != ExitOK {
t.Fatalf("expected success exit code for skipped validation, got %d", got) t.Fatalf("expected success exit code for skipped validation, got %d", got)
} }
} }
@@ -1242,12 +1242,12 @@ func TestWriteOutputAndSummaryUseSeparateWriters(t *testing.T) {
if err := writeOutput(&stdout, "", []byte("artifact-body")); err != nil { if err := writeOutput(&stdout, "", []byte("artifact-body")); err != nil {
t.Fatalf("unexpected writeOutput error: %v", err) t.Fatalf("unexpected writeOutput error: %v", err)
} }
printSummary(&stderr, &scriptorium.RunResult{ printSummary(&stderr, &promptkit.RunResult{
PromptID: "p", PromptID: "p",
PromptVersion: "1", PromptVersion: "1",
SelectedProfileID: "exec", SelectedProfileID: "exec",
ModelName: "m", ModelName: "m",
Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed, Mode: scriptorium.ValidationBasic}, Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed, Mode: promptkit.ValidationBasic},
RenderedPromptHash: "h", RenderedPromptHash: "h",
InputHashes: map[string]string{"in": "x"}, InputHashes: map[string]string{"in": "x"},
}) })
@@ -1266,15 +1266,15 @@ func TestWriteOutputAndSummaryUseSeparateWriters(t *testing.T) {
func TestPrintSummaryIncludesCacheUsageWhenPresent(t *testing.T) { func TestPrintSummaryIncludesCacheUsageWhenPresent(t *testing.T) {
var stderr bytes.Buffer var stderr bytes.Buffer
printSummary(&stderr, &scriptorium.RunResult{ printSummary(&stderr, &promptkit.RunResult{
PromptID: "p", PromptID: "p",
PromptVersion: "1", PromptVersion: "1",
SelectedProfileID: "exec", SelectedProfileID: "exec",
ModelName: "m", ModelName: "m",
Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed, Mode: scriptorium.ValidationBasic}, Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed, Mode: promptkit.ValidationBasic},
RenderedPromptHash: "h", RenderedPromptHash: "h",
InputHashes: map[string]string{"in": "x"}, InputHashes: map[string]string{"in": "x"},
Usage: scriptorium.TokenUsage{ Usage: promptkit.TokenUsage{
PromptTokens: 10, PromptTokens: 10,
CompletionTokens: 5, CompletionTokens: 5,
TotalTokens: 15, TotalTokens: 15,

View File

@@ -11,7 +11,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"gitea.maximumdirect.net/eric/scriptorium" "gitea.maximumdirect.net/eric/promptkit"
) )
var ( var (
@@ -25,7 +25,7 @@ const fallbackArtifactContentType = "text/plain"
// NewRestrictedArtifactReader creates the HTTP artifact reader for a rooted // NewRestrictedArtifactReader creates the HTTP artifact reader for a rooted
// filesystem and optional byte limit. An empty root permits inline artifacts // filesystem and optional byte limit. An empty root permits inline artifacts
// but denies file references; a zero limit permits artifacts of any size. // but denies file references; a zero limit permits artifacts of any size.
func NewRestrictedArtifactReader(root string, maxBytes int64) (scriptorium.ArtifactReader, error) { func NewRestrictedArtifactReader(root string, maxBytes int64) (promptkit.ArtifactReader, error) {
if maxBytes < 0 { if maxBytes < 0 {
return nil, fmt.Errorf("artifact size limit must be greater than or equal to 0") return nil, fmt.Errorf("artifact size limit must be greater than or equal to 0")
} }
@@ -46,9 +46,9 @@ type restrictedArtifactReader struct {
maxBytes int64 maxBytes int64
} }
var _ scriptorium.ArtifactReader = (*restrictedArtifactReader)(nil) var _ promptkit.ArtifactReader = (*restrictedArtifactReader)(nil)
func (r *restrictedArtifactReader) Read(ctx context.Context, ref scriptorium.ArtifactRef) (*scriptorium.Artifact, error) { func (r *restrictedArtifactReader) Read(ctx context.Context, ref promptkit.ArtifactRef) (*promptkit.Artifact, error) {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, ctx.Err()
@@ -56,22 +56,22 @@ func (r *restrictedArtifactReader) Read(ctx context.Context, ref scriptorium.Art
} }
switch ref.Type { switch ref.Type {
case scriptorium.ArtifactRefInline: case promptkit.ArtifactRefInline:
return readInlineArtifact(ref) return readInlineArtifact(ref)
case scriptorium.ArtifactRefFile: case promptkit.ArtifactRefFile:
return r.readFileArtifact(ref) return r.readFileArtifact(ref)
default: default:
return nil, fmt.Errorf("unsupported artifact reference type %q", ref.Type) return nil, fmt.Errorf("unsupported artifact reference type %q", ref.Type)
} }
} }
func readInlineArtifact(ref scriptorium.ArtifactRef) (*scriptorium.Artifact, error) { func readInlineArtifact(ref promptkit.ArtifactRef) (*promptkit.Artifact, error) {
if ref.Body == "" { if ref.Body == "" {
return nil, errors.New("inline artifact body is required") return nil, errors.New("inline artifact body is required")
} }
body := []byte(ref.Body) body := []byte(ref.Body)
return &scriptorium.Artifact{ return &promptkit.Artifact{
ContentType: fallbackArtifactContentType, ContentType: fallbackArtifactContentType,
Body: body, Body: body,
Size: int64(len(body)), Size: int64(len(body)),
@@ -80,7 +80,7 @@ func readInlineArtifact(ref scriptorium.ArtifactRef) (*scriptorium.Artifact, err
}, nil }, nil
} }
func (r *restrictedArtifactReader) readFileArtifact(ref scriptorium.ArtifactRef) (*scriptorium.Artifact, error) { func (r *restrictedArtifactReader) readFileArtifact(ref promptkit.ArtifactRef) (*promptkit.Artifact, error) {
if ref.URI == "" { if ref.URI == "" {
return nil, errors.New("file artifact path is required") return nil, errors.New("file artifact path is required")
} }
@@ -119,7 +119,7 @@ func (r *restrictedArtifactReader) resolveLexicalPath(rawPath string) (string, e
return absCandidate, nil return absCandidate, nil
} }
func readArtifactFile(path string, maxBytes int64) (*scriptorium.Artifact, error) { func readArtifactFile(path string, maxBytes int64) (*promptkit.Artifact, error) {
file, err := os.Open(path) file, err := os.Open(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read file %s: %w", path, err) return nil, fmt.Errorf("failed to read file %s: %w", path, err)
@@ -150,7 +150,7 @@ func readArtifactFile(path string, maxBytes int64) (*scriptorium.Artifact, error
if contentType == "" { if contentType == "" {
contentType = fallbackArtifactContentType contentType = fallbackArtifactContentType
} }
return &scriptorium.Artifact{ return &promptkit.Artifact{
Name: filepath.Base(path), Name: filepath.Base(path),
ContentType: contentType, ContentType: contentType,
Body: body, Body: body,

View File

@@ -8,7 +8,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"gitea.maximumdirect.net/eric/scriptorium" "gitea.maximumdirect.net/eric/promptkit"
) )
func TestRestrictedArtifactReaderReadsContainedFiles(t *testing.T) { func TestRestrictedArtifactReaderReadsContainedFiles(t *testing.T) {
@@ -37,9 +37,9 @@ func TestRestrictedArtifactReaderReadsContainedFiles(t *testing.T) {
t.Fatalf("construct restricted reader: %v", err) t.Fatalf("construct restricted reader: %v", err)
} }
for _, ref := range []scriptorium.ArtifactRef{ for _, ref := range []promptkit.ArtifactRef{
{Type: scriptorium.ArtifactRefFile, URI: "nested/../input.html"}, {Type: promptkit.ArtifactRefFile, URI: "nested/../input.html"},
{Type: scriptorium.ArtifactRefFile, URI: inputPath}, {Type: promptkit.ArtifactRefFile, URI: inputPath},
} { } {
artifact, err := reader.Read(context.Background(), ref) artifact, err := reader.Read(context.Background(), ref)
if err != nil { if err != nil {
@@ -56,7 +56,7 @@ func TestRestrictedArtifactReaderReadsContainedFiles(t *testing.T) {
} }
} }
artifact, err := reader.Read(context.Background(), scriptorium.File("input.unknown")) artifact, err := reader.Read(context.Background(), promptkit.File("input.unknown"))
if err != nil { if err != nil {
t.Fatalf("read unknown-extension path: %v", err) t.Fatalf("read unknown-extension path: %v", err)
} }
@@ -64,9 +64,9 @@ func TestRestrictedArtifactReaderReadsContainedFiles(t *testing.T) {
t.Fatalf("unexpected fallback content type: %q", artifact.ContentType) t.Fatalf("unexpected fallback content type: %q", artifact.ContentType)
} }
for _, ref := range []scriptorium.ArtifactRef{ for _, ref := range []promptkit.ArtifactRef{
{Type: scriptorium.ArtifactRefFile, URI: filepath.Join("..", filepath.Base(outside), "secret.txt")}, {Type: promptkit.ArtifactRefFile, URI: filepath.Join("..", filepath.Base(outside), "secret.txt")},
{Type: scriptorium.ArtifactRefFile, URI: filepath.Join(outside, "secret.txt")}, {Type: promptkit.ArtifactRefFile, URI: filepath.Join(outside, "secret.txt")},
} { } {
_, err := reader.Read(context.Background(), ref) _, err := reader.Read(context.Background(), ref)
if !errors.Is(err, ErrFileOutsideRoot) { if !errors.Is(err, ErrFileOutsideRoot) {
@@ -90,7 +90,7 @@ func TestRestrictedArtifactReaderFollowsSymlinkAfterLexicalCheck(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("construct restricted reader: %v", err) t.Fatalf("construct restricted reader: %v", err)
} }
artifact, err := reader.Read(context.Background(), scriptorium.File("linked.txt")) artifact, err := reader.Read(context.Background(), promptkit.File("linked.txt"))
if err != nil { if err != nil {
t.Fatalf("read symlink inside root: %v", err) t.Fatalf("read symlink inside root: %v", err)
} }
@@ -105,7 +105,7 @@ func TestRestrictedArtifactReaderWithoutRootDeniesFiles(t *testing.T) {
t.Fatalf("construct rootless reader: %v", err) t.Fatalf("construct rootless reader: %v", err)
} }
artifact, err := reader.Read(context.Background(), scriptorium.Inline("inline")) artifact, err := reader.Read(context.Background(), promptkit.Inline("inline"))
if err != nil { if err != nil {
t.Fatalf("read inline artifact: %v", err) t.Fatalf("read inline artifact: %v", err)
} }
@@ -113,7 +113,7 @@ func TestRestrictedArtifactReaderWithoutRootDeniesFiles(t *testing.T) {
t.Fatalf("unexpected inline artifact: %#v", artifact) t.Fatalf("unexpected inline artifact: %#v", artifact)
} }
_, err = reader.Read(context.Background(), scriptorium.File("input.txt")) _, err = reader.Read(context.Background(), promptkit.File("input.txt"))
if !errors.Is(err, ErrFileNotAllowed) { if !errors.Is(err, ErrFileNotAllowed) {
t.Fatalf("expected ErrFileNotAllowed, got %v", err) t.Fatalf("expected ErrFileNotAllowed, got %v", err)
} }
@@ -132,11 +132,11 @@ func TestRestrictedArtifactReaderEnforcesLimits(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("construct limited reader: %v", err) t.Fatalf("construct limited reader: %v", err)
} }
artifact, err := reader.Read(context.Background(), scriptorium.File("exact.txt")) artifact, err := reader.Read(context.Background(), promptkit.File("exact.txt"))
if err != nil || string(artifact.Body) != "12345" { if err != nil || string(artifact.Body) != "12345" {
t.Fatalf("expected exact-limit artifact, got %#v and %v", artifact, err) t.Fatalf("expected exact-limit artifact, got %#v and %v", artifact, err)
} }
_, err = reader.Read(context.Background(), scriptorium.File("large.txt")) _, err = reader.Read(context.Background(), promptkit.File("large.txt"))
if !errors.Is(err, ErrFileTooLarge) { if !errors.Is(err, ErrFileTooLarge) {
t.Fatalf("expected ErrFileTooLarge, got %v", err) t.Fatalf("expected ErrFileTooLarge, got %v", err)
} }
@@ -145,7 +145,7 @@ func TestRestrictedArtifactReaderEnforcesLimits(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("construct unlimited reader: %v", err) t.Fatalf("construct unlimited reader: %v", err)
} }
artifact, err = unlimited.Read(context.Background(), scriptorium.File("large.txt")) artifact, err = unlimited.Read(context.Background(), promptkit.File("large.txt"))
if err != nil || string(artifact.Body) != "123456" { if err != nil || string(artifact.Body) != "123456" {
t.Fatalf("expected unlimited artifact, got %#v and %v", artifact, err) t.Fatalf("expected unlimited artifact, got %#v and %v", artifact, err)
} }
@@ -163,9 +163,9 @@ func TestRestrictedArtifactReaderRejectsCanceledAndMalformedReferences(t *testin
canceledCtx, cancel := context.WithCancel(context.Background()) canceledCtx, cancel := context.WithCancel(context.Background())
cancel() cancel()
for _, ref := range []scriptorium.ArtifactRef{ for _, ref := range []promptkit.ArtifactRef{
scriptorium.Inline("input"), promptkit.Inline("input"),
scriptorium.File("input.txt"), promptkit.File("input.txt"),
} { } {
_, err := reader.Read(canceledCtx, ref) _, err := reader.Read(canceledCtx, ref)
if !errors.Is(err, context.Canceled) { if !errors.Is(err, context.Canceled) {
@@ -173,10 +173,10 @@ func TestRestrictedArtifactReaderRejectsCanceledAndMalformedReferences(t *testin
} }
} }
for _, ref := range []scriptorium.ArtifactRef{ for _, ref := range []promptkit.ArtifactRef{
{Type: scriptorium.ArtifactRefType("unsupported")}, {Type: promptkit.ArtifactRefType("unsupported")},
{Type: scriptorium.ArtifactRefInline}, {Type: promptkit.ArtifactRefInline},
{Type: scriptorium.ArtifactRefFile}, {Type: promptkit.ArtifactRefFile},
} { } {
if _, err := reader.Read(context.Background(), ref); err == nil { if _, err := reader.Read(context.Background(), ref); err == nil {
t.Fatalf("expected malformed reference %#v to fail", ref) t.Fatalf("expected malformed reference %#v to fail", ref)

View File

@@ -8,12 +8,12 @@ import (
"net/http" "net/http"
"strings" "strings"
"gitea.maximumdirect.net/eric/scriptorium" "gitea.maximumdirect.net/eric/promptkit"
"gitea.maximumdirect.net/eric/scriptorium/internal/defaults" "gitea.maximumdirect.net/eric/scriptorium/internal/defaults"
) )
type Runner interface { type Runner interface {
Run(ctx context.Context, req scriptorium.RunRequest) (*scriptorium.RunResult, error) Run(ctx context.Context, req promptkit.RunRequest) (*promptkit.RunResult, error)
} }
type Handler struct { type Handler struct {
@@ -81,21 +81,21 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
mappedInputs := make(map[string]scriptorium.ArtifactRef, len(req.Inputs)) mappedInputs := make(map[string]promptkit.ArtifactRef, len(req.Inputs))
for name, in := range req.Inputs { for name, in := range req.Inputs {
mappedInputs[name] = scriptorium.ArtifactRef{ mappedInputs[name] = promptkit.ArtifactRef{
Type: scriptorium.ArtifactRefType(in.Type), Type: promptkit.ArtifactRefType(in.Type),
URI: in.URI, URI: in.URI,
Body: in.Body, Body: in.Body,
} }
} }
var model *scriptorium.ExecutionTargetOverride var model *promptkit.ExecutionTargetOverride
if req.Model != nil { if req.Model != nil {
model = executionTargetOverrideFromModelOverrideDTO(req.Model) model = executionTargetOverrideFromModelOverrideDTO(req.Model)
} }
res, err := h.runner.Run(r.Context(), scriptorium.RunRequest{ res, err := h.runner.Run(r.Context(), promptkit.RunRequest{
PromptID: req.PromptID, PromptID: req.PromptID,
PromptVersion: req.PromptVersion, PromptVersion: req.PromptVersion,
ProfileID: req.ProfileID, ProfileID: req.ProfileID,
@@ -152,11 +152,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
writeLimitedJSON(w, http.StatusOK, resp, h.options.MaxResponseBytes) writeLimitedJSON(w, http.StatusOK, resp, h.options.MaxResponseBytes)
} }
func executionTargetOverrideFromModelOverrideDTO(dto *modelOverrideRequestDTO) *scriptorium.ExecutionTargetOverride { func executionTargetOverrideFromModelOverrideDTO(dto *modelOverrideRequestDTO) *promptkit.ExecutionTargetOverride {
if dto == nil { if dto == nil {
return nil return nil
} }
return &scriptorium.ExecutionTargetOverride{ return &promptkit.ExecutionTargetOverride{
Endpoint: dto.Endpoint, Endpoint: dto.Endpoint,
Model: dto.Model, Model: dto.Model,
Temperature: dto.Temperature, Temperature: dto.Temperature,
@@ -170,7 +170,7 @@ func executionTargetOverrideFromModelOverrideDTO(dto *modelOverrideRequestDTO) *
} }
} }
func modelParamsDTOFromExecutionTarget(target scriptorium.ExecutionTarget) modelParamsDTO { func modelParamsDTOFromExecutionTarget(target promptkit.ExecutionTarget) modelParamsDTO {
return modelParamsDTO{ return modelParamsDTO{
Endpoint: target.Endpoint, Endpoint: target.Endpoint,
Model: target.Model, Model: target.Model,
@@ -185,7 +185,7 @@ func modelParamsDTOFromExecutionTarget(target scriptorium.ExecutionTarget) model
} }
} }
func mapValidation(v scriptorium.ValidationResult) validationDTO { func mapValidation(v promptkit.ValidationResult) validationDTO {
return validationDTO{ return validationDTO{
Status: string(v.Status), Status: string(v.Status),
Mode: string(v.Mode), Mode: string(v.Mode),
@@ -198,31 +198,31 @@ func mapValidation(v scriptorium.ValidationResult) validationDTO {
func mapRunError(err error) (int, string, string) { func mapRunError(err error) (int, string, string) {
switch { switch {
case errors.Is(err, scriptorium.ErrPromptNotFound): case errors.Is(err, promptkit.ErrPromptNotFound):
return http.StatusNotFound, "prompt_not_found", "prompt definition not found" return http.StatusNotFound, "prompt_not_found", "prompt definition not found"
case errors.Is(err, scriptorium.ErrProfileNotFound): case errors.Is(err, promptkit.ErrProfileNotFound):
return http.StatusNotFound, "profile_not_found", "execution profile not found" return http.StatusNotFound, "profile_not_found", "execution profile not found"
case errors.Is(err, scriptorium.ErrProfileRequired): case errors.Is(err, promptkit.ErrProfileRequired):
return http.StatusBadRequest, "profile_required", "profile_id is required when prompt default_profile is not set" return http.StatusBadRequest, "profile_required", "profile_id is required when prompt default_profile is not set"
case errors.Is(err, scriptorium.ErrAPIKeyEnvMissing): case errors.Is(err, promptkit.ErrAPIKeyEnvMissing):
return http.StatusBadRequest, "api_key_env_missing", "api_key_env is set but the environment variable is missing" return http.StatusBadRequest, "api_key_env_missing", "api_key_env is set but the environment variable is missing"
case errors.Is(err, scriptorium.ErrPromptLoad): case errors.Is(err, promptkit.ErrPromptLoad):
return http.StatusBadRequest, "prompt_load_failed", "failed to load prompt definition" return http.StatusBadRequest, "prompt_load_failed", "failed to load prompt definition"
case errors.Is(err, scriptorium.ErrProfileLoad): case errors.Is(err, promptkit.ErrProfileLoad):
return http.StatusBadRequest, "profile_load_failed", "failed to load execution profile" return http.StatusBadRequest, "profile_load_failed", "failed to load execution profile"
case errors.Is(err, scriptorium.ErrInvalidRequest): case errors.Is(err, promptkit.ErrInvalidRequest):
return http.StatusBadRequest, "invalid_request", "invalid run request" return http.StatusBadRequest, "invalid_request", "invalid run request"
case errors.Is(err, ErrFileNotAllowed), errors.Is(err, ErrFileOutsideRoot): case errors.Is(err, ErrFileNotAllowed), errors.Is(err, ErrFileOutsideRoot):
return http.StatusBadRequest, "artifact_not_allowed", "file input artifact is not allowed" return http.StatusBadRequest, "artifact_not_allowed", "file input artifact is not allowed"
case errors.Is(err, ErrFileTooLarge): case errors.Is(err, ErrFileTooLarge):
return http.StatusRequestEntityTooLarge, "artifact_too_large", "file input artifact is too large" return http.StatusRequestEntityTooLarge, "artifact_too_large", "file input artifact is too large"
case errors.Is(err, scriptorium.ErrArtifactLoad): case errors.Is(err, promptkit.ErrArtifactLoad):
return http.StatusBadRequest, "artifact_read_failed", "failed to read input artifact" return http.StatusBadRequest, "artifact_read_failed", "failed to read input artifact"
case errors.Is(err, scriptorium.ErrPromptRender): case errors.Is(err, promptkit.ErrPromptRender):
return http.StatusBadRequest, "prompt_render_failed", "failed to render prompt" return http.StatusBadRequest, "prompt_render_failed", "failed to render prompt"
case errors.Is(err, scriptorium.ErrLLMGenerate): case errors.Is(err, promptkit.ErrLLMGenerate):
return http.StatusBadGateway, "llm_failed", "model generation request failed" return http.StatusBadGateway, "llm_failed", "model generation request failed"
case errors.Is(err, scriptorium.ErrValidation): case errors.Is(err, promptkit.ErrValidation):
return http.StatusInternalServerError, "validation_runtime_failed", "validation runtime failed" return http.StatusInternalServerError, "validation_runtime_failed", "validation runtime failed"
default: default:
return http.StatusInternalServerError, "internal_error", "internal server error" return http.StatusInternalServerError, "internal_error", "internal server error"

View File

@@ -14,16 +14,16 @@ import (
"testing" "testing"
"time" "time"
"gitea.maximumdirect.net/eric/scriptorium" "gitea.maximumdirect.net/eric/promptkit"
) )
type fakeRunner struct { type fakeRunner struct {
result *scriptorium.RunResult result *promptkit.RunResult
err error err error
last scriptorium.RunRequest last promptkit.RunRequest
} }
func (f *fakeRunner) Run(ctx context.Context, req scriptorium.RunRequest) (*scriptorium.RunResult, error) { func (f *fakeRunner) Run(ctx context.Context, req promptkit.RunRequest) (*promptkit.RunResult, error) {
f.last = req f.last = req
if f.err != nil { if f.err != nil {
return nil, f.err return nil, f.err
@@ -37,7 +37,7 @@ func TestMaintainedHTTPRunExampleMatchesRequestContract(t *testing.T) {
t.Fatalf("read maintained HTTP request example: %v", err) t.Fatalf("read maintained HTTP request example: %v", err)
} }
runner := &fakeRunner{result: &scriptorium.RunResult{}} runner := &fakeRunner{result: &promptkit.RunResult{}}
h := NewHandler(runner) h := NewHandler(runner)
req := httptest.NewRequest(http.MethodPost, "/v1/runs", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/v1/runs", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
@@ -65,8 +65,8 @@ func TestMaintainedHTTPRunExampleMatchesRequestContract(t *testing.T) {
type handlerLLMClient struct{} type handlerLLMClient struct{}
func (handlerLLMClient) Generate(ctx context.Context, req scriptorium.GenerateRequest) (*scriptorium.GenerateResponse, error) { func (handlerLLMClient) Generate(ctx context.Context, req promptkit.GenerateRequest) (*promptkit.GenerateResponse, error) {
return &scriptorium.GenerateResponse{Content: "ok"}, nil return &promptkit.GenerateResponse{Content: "ok"}, nil
} }
func TestHandlerPostRunsSuccessWithExplicitProfileID(t *testing.T) { func TestHandlerPostRunsSuccessWithExplicitProfileID(t *testing.T) {
@@ -75,16 +75,16 @@ func TestHandlerPostRunsSuccessWithExplicitProfileID(t *testing.T) {
const envName = "SCRIPTORIUM_API_KEY" const envName = "SCRIPTORIUM_API_KEY"
const secret = "never-include-me" const secret = "never-include-me"
r := &fakeRunner{result: &scriptorium.RunResult{ r := &fakeRunner{result: &promptkit.RunResult{
RunID: "11111111-1111-4111-8111-111111111111", RunID: "11111111-1111-4111-8111-111111111111",
Artifact: scriptorium.Artifact{ Artifact: promptkit.Artifact{
Name: "output", Name: "output",
ContentType: "text/plain", ContentType: "text/plain",
Body: []byte("hello"), Body: []byte("hello"),
Size: 5, Size: 5,
Hash: "abc", Hash: "abc",
}, },
Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed, Mode: scriptorium.ValidationBasic, IsValid: true}, Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed, Mode: promptkit.ValidationBasic, IsValid: true},
PromptID: "prompt-1", PromptID: "prompt-1",
PromptVersion: "1.0.0", PromptVersion: "1.0.0",
PromptHash: "phash", PromptHash: "phash",
@@ -92,7 +92,7 @@ func TestHandlerPostRunsSuccessWithExplicitProfileID(t *testing.T) {
SelectedProfileID: "exec-default", SelectedProfileID: "exec-default",
ModelName: "m1", ModelName: "m1",
Endpoint: "http://llm/v1", Endpoint: "http://llm/v1",
EffectiveModelParams: scriptorium.ExecutionTarget{ EffectiveModelParams: promptkit.ExecutionTarget{
Endpoint: "http://llm/v1", Endpoint: "http://llm/v1",
Model: "m1", Model: "m1",
Temperature: 0.2, Temperature: 0.2,
@@ -103,7 +103,7 @@ func TestHandlerPostRunsSuccessWithExplicitProfileID(t *testing.T) {
APIKeyEnv: envName, APIKeyEnv: envName,
}, },
InputHashes: map[string]string{"transcript": "h1"}, InputHashes: map[string]string{"transcript": "h1"},
Usage: scriptorium.TokenUsage{ Usage: promptkit.TokenUsage{
PromptTokens: 1, PromptTokens: 1,
CompletionTokens: 2, CompletionTokens: 2,
TotalTokens: 3, TotalTokens: 3,
@@ -292,13 +292,13 @@ func TestHandlerFileRefsOutsideArtifactRootAreRejected(t *testing.T) {
} }
func TestHandlerPostRunsSuccessUsingPromptDefaultProfile(t *testing.T) { func TestHandlerPostRunsSuccessUsingPromptDefaultProfile(t *testing.T) {
r := &fakeRunner{result: &scriptorium.RunResult{ r := &fakeRunner{result: &promptkit.RunResult{
Artifact: scriptorium.Artifact{Body: []byte("ok")}, Artifact: promptkit.Artifact{Body: []byte("ok")},
PromptID: "prompt-1", PromptID: "prompt-1",
PromptVersion: "1.0.0", PromptVersion: "1.0.0",
SelectedProfileID: "prompt-default", SelectedProfileID: "prompt-default",
Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed, Mode: scriptorium.ValidationBasic, IsValid: true}, Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed, Mode: promptkit.ValidationBasic, IsValid: true},
EffectiveModelParams: scriptorium.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1"}, EffectiveModelParams: promptkit.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1"},
}} }}
h := NewHandler(r) h := NewHandler(r)
@@ -327,10 +327,10 @@ func TestHandlerPostRunsSuccessUsingPromptDefaultProfile(t *testing.T) {
} }
func TestHandlerModelOverrideMapsAllSupportedExecutionFields(t *testing.T) { func TestHandlerModelOverrideMapsAllSupportedExecutionFields(t *testing.T) {
r := &fakeRunner{result: &scriptorium.RunResult{ r := &fakeRunner{result: &promptkit.RunResult{
Artifact: scriptorium.Artifact{Body: []byte("ok")}, Artifact: promptkit.Artifact{Body: []byte("ok")},
Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed, Mode: scriptorium.ValidationBasic, IsValid: true}, Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed, Mode: promptkit.ValidationBasic, IsValid: true},
EffectiveModelParams: scriptorium.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1"}, EffectiveModelParams: promptkit.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1"},
}} }}
h := NewHandler(r) h := NewHandler(r)
@@ -387,10 +387,10 @@ func TestHandlerModelOverrideMapsAllSupportedExecutionFields(t *testing.T) {
} }
func TestHandlerModelOverrideAcceptsJSONCompatibleExtraParams(t *testing.T) { func TestHandlerModelOverrideAcceptsJSONCompatibleExtraParams(t *testing.T) {
r := &fakeRunner{result: &scriptorium.RunResult{ r := &fakeRunner{result: &promptkit.RunResult{
Artifact: scriptorium.Artifact{Body: []byte("ok")}, Artifact: promptkit.Artifact{Body: []byte("ok")},
Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed, Mode: scriptorium.ValidationBasic, IsValid: true}, Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed, Mode: promptkit.ValidationBasic, IsValid: true},
EffectiveModelParams: scriptorium.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1"}, EffectiveModelParams: promptkit.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1"},
}} }}
h := NewHandler(r) h := NewHandler(r)
@@ -431,10 +431,10 @@ func TestHandlerModelOverrideAcceptsJSONCompatibleExtraParams(t *testing.T) {
} }
func TestHandlerModelOverrideExplicitZeroTemperatureMapsAsPresent(t *testing.T) { func TestHandlerModelOverrideExplicitZeroTemperatureMapsAsPresent(t *testing.T) {
r := &fakeRunner{result: &scriptorium.RunResult{ r := &fakeRunner{result: &promptkit.RunResult{
Artifact: scriptorium.Artifact{Body: []byte("ok")}, Artifact: promptkit.Artifact{Body: []byte("ok")},
Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed, Mode: scriptorium.ValidationBasic, IsValid: true}, Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed, Mode: promptkit.ValidationBasic, IsValid: true},
EffectiveModelParams: scriptorium.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1", Temperature: 0}, EffectiveModelParams: promptkit.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1", Temperature: 0},
}} }}
h := NewHandler(r) h := NewHandler(r)
@@ -458,10 +458,10 @@ func TestHandlerModelOverrideExplicitZeroTemperatureMapsAsPresent(t *testing.T)
} }
func TestHandlerModelOverrideOmittedTemperatureMapsAsAbsent(t *testing.T) { func TestHandlerModelOverrideOmittedTemperatureMapsAsAbsent(t *testing.T) {
r := &fakeRunner{result: &scriptorium.RunResult{ r := &fakeRunner{result: &promptkit.RunResult{
Artifact: scriptorium.Artifact{Body: []byte("ok")}, Artifact: promptkit.Artifact{Body: []byte("ok")},
Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed, Mode: scriptorium.ValidationBasic, IsValid: true}, Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed, Mode: promptkit.ValidationBasic, IsValid: true},
EffectiveModelParams: scriptorium.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1", Temperature: 0.7}, EffectiveModelParams: promptkit.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1", Temperature: 0.7},
}} }}
h := NewHandler(r) h := NewHandler(r)
@@ -495,16 +495,16 @@ func TestHandlerModelOverrideOmittedTemperatureMapsAsAbsent(t *testing.T) {
} }
func TestHandlerResponseMetadataModelParamsIncludesAllSupportedFields(t *testing.T) { func TestHandlerResponseMetadataModelParamsIncludesAllSupportedFields(t *testing.T) {
r := &fakeRunner{result: &scriptorium.RunResult{ r := &fakeRunner{result: &promptkit.RunResult{
Artifact: scriptorium.Artifact{ Artifact: promptkit.Artifact{
Name: "output", Name: "output",
ContentType: "text/plain", ContentType: "text/plain",
Body: []byte("ok"), Body: []byte("ok"),
Size: 2, Size: 2,
Hash: "abc", Hash: "abc",
}, },
Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed, Mode: scriptorium.ValidationBasic, IsValid: true}, Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed, Mode: promptkit.ValidationBasic, IsValid: true},
EffectiveModelParams: scriptorium.ExecutionTarget{ EffectiveModelParams: promptkit.ExecutionTarget{
Endpoint: "http://llm/v1", Endpoint: "http://llm/v1",
Model: "gpt-test", Model: "gpt-test",
Temperature: 0.4, Temperature: 0.4,
@@ -624,10 +624,10 @@ func TestHandlerMalformedJSONBelowLimitStillBadRequest(t *testing.T) {
} }
func TestHandlerResponseTooLarge(t *testing.T) { func TestHandlerResponseTooLarge(t *testing.T) {
h := NewHandlerWithOptions(&fakeRunner{result: &scriptorium.RunResult{ h := NewHandlerWithOptions(&fakeRunner{result: &promptkit.RunResult{
Artifact: scriptorium.Artifact{Body: []byte(strings.Repeat("x", 128))}, Artifact: promptkit.Artifact{Body: []byte(strings.Repeat("x", 128))},
Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed, Mode: scriptorium.ValidationBasic, IsValid: true}, Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed, Mode: promptkit.ValidationBasic, IsValid: true},
EffectiveModelParams: scriptorium.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1"}, EffectiveModelParams: promptkit.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1"},
}}, HandlerOptions{MaxRequestBytes: 1024, MaxResponseBytes: 64}) }}, HandlerOptions{MaxRequestBytes: 1024, MaxResponseBytes: 64})
req := httptest.NewRequest(http.MethodPost, "/v1/runs", bytes.NewBufferString(`{"prompt_id":"p","inputs":{"x":{"type":"file","uri":"a"}}}`)) req := httptest.NewRequest(http.MethodPost, "/v1/runs", bytes.NewBufferString(`{"prompt_id":"p","inputs":{"x":{"type":"file","uri":"a"}}}`))
w := httptest.NewRecorder() w := httptest.NewRecorder()
@@ -638,11 +638,11 @@ func TestHandlerResponseTooLarge(t *testing.T) {
} }
func TestHandlerRawOutputDoesNotBypassResponseLimit(t *testing.T) { func TestHandlerRawOutputDoesNotBypassResponseLimit(t *testing.T) {
h := NewHandlerWithOptions(&fakeRunner{result: &scriptorium.RunResult{ h := NewHandlerWithOptions(&fakeRunner{result: &promptkit.RunResult{
Artifact: scriptorium.Artifact{Body: []byte("ok")}, Artifact: promptkit.Artifact{Body: []byte("ok")},
RawOutput: strings.Repeat("raw", 80), RawOutput: strings.Repeat("raw", 80),
Validation: scriptorium.ValidationResult{Status: scriptorium.ValidationPassed, Mode: scriptorium.ValidationBasic, IsValid: true}, Validation: promptkit.ValidationResult{Status: promptkit.ValidationPassed, Mode: promptkit.ValidationBasic, IsValid: true},
EffectiveModelParams: scriptorium.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1"}, EffectiveModelParams: promptkit.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m1"},
}}, HandlerOptions{MaxRequestBytes: 1024, MaxResponseBytes: 128}) }}, HandlerOptions{MaxRequestBytes: 1024, MaxResponseBytes: 128})
req := httptest.NewRequest(http.MethodPost, "/v1/runs", bytes.NewBufferString(`{ req := httptest.NewRequest(http.MethodPost, "/v1/runs", bytes.NewBufferString(`{
"prompt_id":"p", "prompt_id":"p",
@@ -710,20 +710,20 @@ func TestHandlerPublicErrorMapping(t *testing.T) {
message string message string
avoidCause string avoidCause string
}{ }{
{name: "prompt not found", err: scriptorium.ErrPromptNotFound, status: http.StatusNotFound, code: "prompt_not_found", message: "prompt definition not found"}, {name: "prompt not found", err: promptkit.ErrPromptNotFound, status: http.StatusNotFound, code: "prompt_not_found", message: "prompt definition not found"},
{name: "prompt load", err: wrap(scriptorium.ErrPromptLoad, fmt.Errorf("read failed")), status: http.StatusBadRequest, code: "prompt_load_failed", message: "failed to load prompt definition", avoidCause: "read failed"}, {name: "prompt load", err: wrap(promptkit.ErrPromptLoad, fmt.Errorf("read failed")), status: http.StatusBadRequest, code: "prompt_load_failed", message: "failed to load prompt definition", avoidCause: "read failed"},
{name: "missing profile/default", err: wrap(scriptorium.ErrProfileRequired, scriptorium.ErrInvalidRequest), status: http.StatusBadRequest, code: "profile_required", message: "profile_id is required when prompt default_profile is not set"}, {name: "missing profile/default", err: wrap(promptkit.ErrProfileRequired, promptkit.ErrInvalidRequest), status: http.StatusBadRequest, code: "profile_required", message: "profile_id is required when prompt default_profile is not set"},
{name: "profile not found", err: scriptorium.ErrProfileNotFound, status: http.StatusNotFound, code: "profile_not_found", message: "execution profile not found"}, {name: "profile not found", err: promptkit.ErrProfileNotFound, status: http.StatusNotFound, code: "profile_not_found", message: "execution profile not found"},
{name: "profile load", err: wrap(scriptorium.ErrProfileLoad, fmt.Errorf("read failed")), status: http.StatusBadRequest, code: "profile_load_failed", message: "failed to load execution profile", avoidCause: "read failed"}, {name: "profile load", err: wrap(promptkit.ErrProfileLoad, fmt.Errorf("read failed")), status: http.StatusBadRequest, code: "profile_load_failed", message: "failed to load execution profile", avoidCause: "read failed"},
{name: "api key env missing", err: wrap(scriptorium.ErrAPIKeyEnvMissing, scriptorium.ErrInvalidRequest), status: http.StatusBadRequest, code: "api_key_env_missing", message: "api_key_env is set but the environment variable is missing"}, {name: "api key env missing", err: wrap(promptkit.ErrAPIKeyEnvMissing, promptkit.ErrInvalidRequest), status: http.StatusBadRequest, code: "api_key_env_missing", message: "api_key_env is set but the environment variable is missing"},
{name: "invalid request", err: scriptorium.ErrInvalidRequest, status: http.StatusBadRequest, code: "invalid_request", message: "invalid run request"}, {name: "invalid request", err: promptkit.ErrInvalidRequest, status: http.StatusBadRequest, code: "invalid_request", message: "invalid run request"},
{name: "file denied", err: ErrFileNotAllowed, status: http.StatusBadRequest, code: "artifact_not_allowed", message: "file input artifact is not allowed"}, {name: "file denied", err: ErrFileNotAllowed, status: http.StatusBadRequest, code: "artifact_not_allowed", message: "file input artifact is not allowed"},
{name: "file outside root", err: ErrFileOutsideRoot, status: http.StatusBadRequest, code: "artifact_not_allowed", message: "file input artifact is not allowed"}, {name: "file outside root", err: ErrFileOutsideRoot, status: http.StatusBadRequest, code: "artifact_not_allowed", message: "file input artifact is not allowed"},
{name: "file too large", err: ErrFileTooLarge, status: http.StatusRequestEntityTooLarge, code: "artifact_too_large", message: "file input artifact is too large"}, {name: "file too large", err: ErrFileTooLarge, status: http.StatusRequestEntityTooLarge, code: "artifact_too_large", message: "file input artifact is too large"},
{name: "artifact", err: wrap(scriptorium.ErrArtifactLoad, fmt.Errorf("read failed")), status: http.StatusBadRequest, code: "artifact_read_failed", message: "failed to read input artifact", avoidCause: "read failed"}, {name: "artifact", err: wrap(promptkit.ErrArtifactLoad, fmt.Errorf("read failed")), status: http.StatusBadRequest, code: "artifact_read_failed", message: "failed to read input artifact", avoidCause: "read failed"},
{name: "prompt render", err: wrap(scriptorium.ErrPromptRender, fmt.Errorf("render failed")), status: http.StatusBadRequest, code: "prompt_render_failed", message: "failed to render prompt", avoidCause: "render failed"}, {name: "prompt render", err: wrap(promptkit.ErrPromptRender, fmt.Errorf("render failed")), status: http.StatusBadRequest, code: "prompt_render_failed", message: "failed to render prompt", avoidCause: "render failed"},
{name: "llm", err: wrap(scriptorium.ErrLLMGenerate, fmt.Errorf("llm failed")), status: http.StatusBadGateway, code: "llm_failed", message: "model generation request failed", avoidCause: "llm failed"}, {name: "llm", err: wrap(promptkit.ErrLLMGenerate, fmt.Errorf("llm failed")), status: http.StatusBadGateway, code: "llm_failed", message: "model generation request failed", avoidCause: "llm failed"},
{name: "validation runtime", err: wrap(scriptorium.ErrValidation, fmt.Errorf("validator broke")), status: http.StatusInternalServerError, code: "validation_runtime_failed", message: "validation runtime failed", avoidCause: "validator broke"}, {name: "validation runtime", err: wrap(promptkit.ErrValidation, fmt.Errorf("validator broke")), status: http.StatusInternalServerError, code: "validation_runtime_failed", message: "validation runtime failed", avoidCause: "validator broke"},
} }
for _, tc := range tests { for _, tc := range tests {
@@ -776,12 +776,12 @@ func TestHandlerRawAPIKeyRejectedByStrictJSON(t *testing.T) {
} }
func TestHandlerValidationFailureStillSuccessAndRawOutputOptIn(t *testing.T) { func TestHandlerValidationFailureStillSuccessAndRawOutputOptIn(t *testing.T) {
h := NewHandler(&fakeRunner{result: &scriptorium.RunResult{ h := NewHandler(&fakeRunner{result: &promptkit.RunResult{
Artifact: scriptorium.Artifact{Body: []byte("bad json")}, Artifact: promptkit.Artifact{Body: []byte("bad json")},
RawOutput: "bad json", RawOutput: "bad json",
Validation: scriptorium.ValidationResult{ Validation: promptkit.ValidationResult{
Status: scriptorium.ValidationFailed, Status: promptkit.ValidationFailed,
Mode: scriptorium.ValidationJSON, Mode: promptkit.ValidationJSON,
Errors: []string{"invalid JSON"}, Errors: []string{"invalid JSON"},
}, },
}}) }})
@@ -839,22 +839,22 @@ func newArtifactRootHandlerWithLimit(t *testing.T, root string, maxArtifactBytes
if err != nil { if err != nil {
t.Fatalf("expected restricted artifact reader: %v", err) t.Fatalf("expected restricted artifact reader: %v", err)
} }
return NewHandler(newHandlerEngine(t, scriptorium.WithArtifactReader(reader))) return NewHandler(newHandlerEngine(t, promptkit.WithArtifactReader(reader)))
} }
func newHandlerEngine(t *testing.T, options ...scriptorium.Option) *scriptorium.Engine { func newHandlerEngine(t *testing.T, options ...promptkit.Option) *promptkit.Engine {
t.Helper() t.Helper()
return newHandlerEngineWithOptions(t, append(options, scriptorium.WithLLMClient(handlerLLMClient{}))...) return newHandlerEngineWithOptions(t, append(options, promptkit.WithLLMClient(handlerLLMClient{}))...)
} }
func newHandlerEngineWithDefaultClient(t *testing.T, options ...scriptorium.Option) *scriptorium.Engine { func newHandlerEngineWithDefaultClient(t *testing.T, options ...promptkit.Option) *promptkit.Engine {
t.Helper() t.Helper()
return newHandlerEngineWithOptions(t, options...) return newHandlerEngineWithOptions(t, options...)
} }
func newHandlerEngineWithOptions(t *testing.T, options ...scriptorium.Option) *scriptorium.Engine { func newHandlerEngineWithOptions(t *testing.T, options ...promptkit.Option) *promptkit.Engine {
t.Helper() t.Helper()
promptDir := t.TempDir() promptDir := t.TempDir()
@@ -879,7 +879,7 @@ model: model
t.Fatalf("write profile fixture: %v", err) t.Fatalf("write profile fixture: %v", err)
} }
engine, err := scriptorium.NewEngine(scriptorium.Config{ engine, err := promptkit.NewEngine(promptkit.Config{
PromptDir: promptDir, PromptDir: promptDir,
ProfileDir: profileDir, ProfileDir: profileDir,
}, options...) }, options...)

View File

@@ -9,7 +9,7 @@ import (
"sort" "sort"
"strings" "strings"
"gitea.maximumdirect.net/eric/scriptorium" "gitea.maximumdirect.net/eric/promptkit"
) )
var ErrUnknownPreparedRunFormat = errors.New("unknown prepared run format") var ErrUnknownPreparedRunFormat = errors.New("unknown prepared run format")
@@ -26,7 +26,7 @@ const (
// PreparedRunFormatter serializes a prepared run without performing use case work. // PreparedRunFormatter serializes a prepared run without performing use case work.
type PreparedRunFormatter interface { type PreparedRunFormatter interface {
Format(prepared *scriptorium.PreparedRun) ([]byte, error) Format(prepared *promptkit.PreparedRun) ([]byte, error)
} }
// ParsePreparedRunOutputFormat parses a format name. // ParsePreparedRunOutputFormat parses a format name.
@@ -56,7 +56,7 @@ func FormatterForPreparedRun(outputFormat PreparedRunOutputFormat) (PreparedRunF
} }
// FormatPreparedRun formats a prepared run using the selected format. // FormatPreparedRun formats a prepared run using the selected format.
func FormatPreparedRun(prepared *scriptorium.PreparedRun, outputFormat PreparedRunOutputFormat) ([]byte, error) { func FormatPreparedRun(prepared *promptkit.PreparedRun, outputFormat PreparedRunOutputFormat) ([]byte, error) {
formatter, err := FormatterForPreparedRun(outputFormat) formatter, err := FormatterForPreparedRun(outputFormat)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -65,7 +65,7 @@ func FormatPreparedRun(prepared *scriptorium.PreparedRun, outputFormat PreparedR
} }
// FormatPreparedRunByName parses a format name and formats a prepared run. // FormatPreparedRunByName parses a format name and formats a prepared run.
func FormatPreparedRunByName(prepared *scriptorium.PreparedRun, rawFormat string) ([]byte, error) { func FormatPreparedRunByName(prepared *promptkit.PreparedRun, rawFormat string) ([]byte, error) {
outputFormat, err := ParsePreparedRunOutputFormat(rawFormat) outputFormat, err := ParsePreparedRunOutputFormat(rawFormat)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -75,7 +75,7 @@ func FormatPreparedRunByName(prepared *scriptorium.PreparedRun, rawFormat string
type jsonPreparedRunFormatter struct{} type jsonPreparedRunFormatter struct{}
func (jsonPreparedRunFormatter) Format(prepared *scriptorium.PreparedRun) ([]byte, error) { func (jsonPreparedRunFormatter) Format(prepared *promptkit.PreparedRun) ([]byte, error) {
if prepared == nil { if prepared == nil {
return nil, errors.New("prepared run is nil") return nil, errors.New("prepared run is nil")
} }
@@ -84,7 +84,7 @@ func (jsonPreparedRunFormatter) Format(prepared *scriptorium.PreparedRun) ([]byt
type textPreparedRunFormatter struct{} type textPreparedRunFormatter struct{}
func (textPreparedRunFormatter) Format(prepared *scriptorium.PreparedRun) ([]byte, error) { func (textPreparedRunFormatter) Format(prepared *promptkit.PreparedRun) ([]byte, error) {
if prepared == nil { if prepared == nil {
return nil, errors.New("prepared run is nil") return nil, errors.New("prepared run is nil")
} }
@@ -146,7 +146,7 @@ func (textPreparedRunFormatter) Format(prepared *scriptorium.PreparedRun) ([]byt
fmt.Fprintln(&b, "messages:") fmt.Fprintln(&b, "messages:")
roleOrder := make([]string, 0) roleOrder := make([]string, 0)
byRole := make(map[string][]scriptorium.RenderedMessage) byRole := make(map[string][]promptkit.RenderedMessage)
for _, msg := range prepared.Messages { for _, msg := range prepared.Messages {
if _, exists := byRole[msg.Role]; !exists { if _, exists := byRole[msg.Role]; !exists {
roleOrder = append(roleOrder, msg.Role) roleOrder = append(roleOrder, msg.Role)

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"testing" "testing"
"gitea.maximumdirect.net/eric/scriptorium" "gitea.maximumdirect.net/eric/promptkit"
) )
func TestTextFormatterIncludesPreparedRunDetails(t *testing.T) { func TestTextFormatterIncludesPreparedRunDetails(t *testing.T) {
@@ -107,12 +107,12 @@ func TestTextFormatterDoesNotIncludeDirectAPIKeyValue(t *testing.T) {
func TestTextFormatterIncludesMessageCacheControlBeforeContent(t *testing.T) { func TestTextFormatterIncludesMessageCacheControlBeforeContent(t *testing.T) {
prepared := samplePreparedRun() prepared := samplePreparedRun()
prepared.Messages = []scriptorium.RenderedMessage{ prepared.Messages = []promptkit.RenderedMessage{
{ {
Role: "system", Role: "system",
Content: "System guidance.", Content: "System guidance.",
CacheControl: &scriptorium.CacheControl{ CacheControl: &promptkit.CacheControl{
Type: scriptorium.CacheControlEphemeral, Type: promptkit.CacheControlEphemeral,
TTL: "1h", TTL: "1h",
}, },
}, },
@@ -147,12 +147,12 @@ func TestTextFormatterIncludesSessionIDWhenPresent(t *testing.T) {
func TestTextFormatterOmitsEmptyCacheControlTTL(t *testing.T) { func TestTextFormatterOmitsEmptyCacheControlTTL(t *testing.T) {
prepared := samplePreparedRun() prepared := samplePreparedRun()
prepared.Messages = []scriptorium.RenderedMessage{ prepared.Messages = []promptkit.RenderedMessage{
{ {
Role: "system", Role: "system",
Content: "System guidance.", Content: "System guidance.",
CacheControl: &scriptorium.CacheControl{ CacheControl: &promptkit.CacheControl{
Type: scriptorium.CacheControlEphemeral, Type: promptkit.CacheControlEphemeral,
}, },
}, },
} }
@@ -230,12 +230,12 @@ func TestJSONFormatterEmitsValidJSONAndIncludesPreparedRunFields(t *testing.T) {
func TestJSONFormatterIncludesMessageCacheControlOnlyWhenPresent(t *testing.T) { func TestJSONFormatterIncludesMessageCacheControlOnlyWhenPresent(t *testing.T) {
prepared := samplePreparedRun() prepared := samplePreparedRun()
prepared.Messages = []scriptorium.RenderedMessage{ prepared.Messages = []promptkit.RenderedMessage{
{ {
Role: "system", Role: "system",
Content: "System guidance.", Content: "System guidance.",
CacheControl: &scriptorium.CacheControl{ CacheControl: &promptkit.CacheControl{
Type: scriptorium.CacheControlEphemeral, Type: promptkit.CacheControlEphemeral,
TTL: "1h", TTL: "1h",
}, },
}, },
@@ -261,7 +261,7 @@ func TestJSONFormatterIncludesMessageCacheControlOnlyWhenPresent(t *testing.T) {
if !ok { if !ok {
t.Fatalf("expected first message cache_control, got %#v", decoded.Messages[0]) t.Fatalf("expected first message cache_control, got %#v", decoded.Messages[0])
} }
if cacheControl["type"] != string(scriptorium.CacheControlEphemeral) || cacheControl["ttl"] != "1h" { if cacheControl["type"] != string(promptkit.CacheControlEphemeral) || cacheControl["ttl"] != "1h" {
t.Fatalf("unexpected cache_control payload: %#v", cacheControl) t.Fatalf("unexpected cache_control payload: %#v", cacheControl)
} }
if _, ok := decoded.Messages[1]["cache_control"]; ok { if _, ok := decoded.Messages[1]["cache_control"]; ok {
@@ -340,13 +340,13 @@ func TestFormatPreparedRunByNameUnknownFailsClearly(t *testing.T) {
} }
} }
func samplePreparedRun() *scriptorium.PreparedRun { func samplePreparedRun() *promptkit.PreparedRun {
return &scriptorium.PreparedRun{ return &promptkit.PreparedRun{
PromptID: "prompt.id", PromptID: "prompt.id",
PromptVersion: "v1", PromptVersion: "v1",
PromptHash: "prompt-hash", PromptHash: "prompt-hash",
SelectedProfileID: "local-fast", SelectedProfileID: "local-fast",
EffectiveModelParams: scriptorium.ExecutionTarget{ EffectiveModelParams: promptkit.ExecutionTarget{
Endpoint: "http://llm/v1", Endpoint: "http://llm/v1",
Model: "gpt-test", Model: "gpt-test",
Temperature: 0.4, Temperature: 0.4,
@@ -362,7 +362,7 @@ func samplePreparedRun() *scriptorium.PreparedRun {
"glossary": "hash-glossary", "glossary": "hash-glossary",
}, },
RenderedPromptHash: "rendered-hash", RenderedPromptHash: "rendered-hash",
Messages: []scriptorium.RenderedMessage{ Messages: []promptkit.RenderedMessage{
{Role: "system", Content: "System guidance."}, {Role: "system", Content: "System guidance."},
{Role: "user", Content: "Summarize the transcript.\nInclude key entities."}, {Role: "user", Content: "Summarize the transcript.\nInclude key entities."},
{Role: "user", Content: "Second user message."}, {Role: "user", Content: "Second user message."},