package scriptorium import ( "context" "fmt" "gitea.maximumdirect.net/eric/narratio/internal/adapters/subprocess" "gitea.maximumdirect.net/eric/narratio/internal/fileops" ) // NoopRunner is a deterministic no-op scriptorium adapter. type NoopRunner struct{} // RunArtifact returns requested output path with placeholder metadata. func (n *NoopRunner) RunArtifact(ctx context.Context, req RunArtifactRequest) (ArtifactResult, error) { if err := ctx.Err(); err != nil { return ArtifactResult{}, err } if err := materializeRunPlaceholders(req); err != nil { return ArtifactResult{}, err } return ArtifactResult{ OutputPath: req.OutputPath, StdoutLogPath: req.StdoutLogPath, StderrLogPath: req.StderrLogPath, GeneratedConfigPath: req.GeneratedConfigPath, CommandMode: CommandModeRun, PromptID: req.PromptID, ProfileID: req.ProfileID, Metadata: map[string]any{"placeholder": true}, }, nil } // RenderArtifact returns requested output path with placeholder metadata. func (n *NoopRunner) RenderArtifact(ctx context.Context, req RenderArtifactRequest) (ArtifactResult, error) { if err := ctx.Err(); err != nil { return ArtifactResult{}, err } if err := materializeRenderPlaceholders(req); err != nil { return ArtifactResult{}, err } return ArtifactResult{ OutputPath: req.OutputPath, StdoutLogPath: req.StdoutLogPath, StderrLogPath: req.StderrLogPath, GeneratedConfigPath: req.GeneratedConfigPath, CommandMode: CommandModeRender, PromptID: req.PromptID, ProfileID: req.ProfileID, Metadata: map[string]any{"placeholder": true}, }, nil } // FakeRunner captures requests and returns deterministic responses. type FakeRunner struct { RunRequests []RunArtifactRequest RenderRequests []RenderArtifactRequest RunErr error RenderErr error RunResult ArtifactResult RenderResult ArtifactResult } // RunArtifact records request and returns configured response. func (f *FakeRunner) RunArtifact(ctx context.Context, req RunArtifactRequest) (ArtifactResult, error) { if err := ctx.Err(); err != nil { return ArtifactResult{}, err } f.RunRequests = append(f.RunRequests, req) if f.RunErr != nil { return ArtifactResult{}, f.RunErr } if err := materializeRunPlaceholders(req); err != nil { return ArtifactResult{}, err } res := f.RunResult if res.OutputPath == "" { res.OutputPath = req.OutputPath } if res.StdoutLogPath == "" { res.StdoutLogPath = req.StdoutLogPath } if res.StderrLogPath == "" { res.StderrLogPath = req.StderrLogPath } if res.GeneratedConfigPath == "" { res.GeneratedConfigPath = req.GeneratedConfigPath } if res.CommandMode == "" { res.CommandMode = CommandModeRun } if res.PromptID == "" { res.PromptID = req.PromptID } if res.ProfileID == "" { res.ProfileID = req.ProfileID } if res.Metadata == nil { res.Metadata = map[string]any{"fake": true} } return res, nil } // RenderArtifact records request and returns configured response. func (f *FakeRunner) RenderArtifact(ctx context.Context, req RenderArtifactRequest) (ArtifactResult, error) { if err := ctx.Err(); err != nil { return ArtifactResult{}, err } f.RenderRequests = append(f.RenderRequests, req) if f.RenderErr != nil { return ArtifactResult{}, f.RenderErr } if err := materializeRenderPlaceholders(req); err != nil { return ArtifactResult{}, err } res := f.RenderResult if res.OutputPath == "" { res.OutputPath = req.OutputPath } if res.StdoutLogPath == "" { res.StdoutLogPath = req.StdoutLogPath } if res.StderrLogPath == "" { res.StderrLogPath = req.StderrLogPath } if res.GeneratedConfigPath == "" { res.GeneratedConfigPath = req.GeneratedConfigPath } if res.CommandMode == "" { res.CommandMode = CommandModeRender } if res.PromptID == "" { res.PromptID = req.PromptID } if res.ProfileID == "" { res.ProfileID = req.ProfileID } if res.Metadata == nil { res.Metadata = map[string]any{"fake": true} } return res, nil } func materializeRunPlaceholders(req RunArtifactRequest) error { if req.OutputPath != "" { if err := subprocess.WriteFileAtomic(req.OutputPath, []byte("scriptorium noop/fake run artifact\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write run output %q: %w", req.OutputPath, err) } } if req.GeneratedConfigPath != "" { payload := map[string]any{ "schema": "scriptorium.generated.v1", "placeholder": true, "mode": CommandModeRun, "prompt_id": req.PromptID, "output_path": req.OutputPath, } if err := subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write generated config %q: %w", req.GeneratedConfigPath, err) } } if req.StdoutLogPath != "" { if err := subprocess.WriteFileAtomic(req.StdoutLogPath, []byte("scriptorium noop/fake run stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stdout log %q: %w", req.StdoutLogPath, err) } } if req.StderrLogPath != "" { if err := subprocess.WriteFileAtomic(req.StderrLogPath, []byte("scriptorium noop/fake run stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stderr log %q: %w", req.StderrLogPath, err) } } return nil } func materializeRenderPlaceholders(req RenderArtifactRequest) error { if req.OutputPath != "" { if err := subprocess.WriteFileAtomic(req.OutputPath, []byte("{\"schema\":\"scriptorium.render.v1\",\"placeholder\":true}\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write render output %q: %w", req.OutputPath, err) } } if req.GeneratedConfigPath != "" { payload := map[string]any{ "schema": "scriptorium.generated.v1", "placeholder": true, "mode": CommandModeRender, "prompt_id": req.PromptID, "output_path": req.OutputPath, } if err := subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write generated config %q: %w", req.GeneratedConfigPath, err) } } if req.StdoutLogPath != "" { if err := subprocess.WriteFileAtomic(req.StdoutLogPath, []byte("scriptorium noop/fake render stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stdout log %q: %w", req.StdoutLogPath, err) } } if req.StderrLogPath != "" { if err := subprocess.WriteFileAtomic(req.StderrLogPath, []byte("scriptorium noop/fake render stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stderr log %q: %w", req.StderrLogPath, err) } } return nil }