253 lines
5.1 KiB
Go
253 lines
5.1 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// LoadPipeline loads pipeline configuration from a YAML file with strict field checking.
|
|
func LoadPipeline(path string) (*PipelineConfig, error) {
|
|
var cfg PipelineConfig
|
|
if err := decodeStrictYAML("pipeline", path, &cfg); err != nil {
|
|
return nil, fmt.Errorf("load pipeline config: %w", err)
|
|
}
|
|
applyPipelineDefaults(&cfg)
|
|
return &cfg, nil
|
|
}
|
|
|
|
// LoadSession loads session configuration from a YAML file with strict field checking.
|
|
func LoadSession(path string) (*SessionConfig, error) {
|
|
var cfg SessionConfig
|
|
if err := decodeStrictYAML("session", path, &cfg); err != nil {
|
|
return nil, fmt.Errorf("load session config: %w", err)
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
// Load loads and resolves combined pipeline and session configuration.
|
|
func Load(pipelinePath, sessionPath string) (*Config, error) {
|
|
pipelineCfg, err := LoadPipeline(pipelinePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sessionCfg, err := LoadSession(sessionPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Config{
|
|
Pipeline: pipelineCfg,
|
|
Session: sessionCfg,
|
|
PipelinePath: pipelinePath,
|
|
SessionPath: sessionPath,
|
|
}, nil
|
|
}
|
|
|
|
func decodeStrictYAML(kind, path string, out any) error {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return fmt.Errorf("%s file %q: open: %w", kind, path, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
dec := yaml.NewDecoder(f)
|
|
dec.KnownFields(true)
|
|
if err := dec.Decode(out); err != nil {
|
|
return fmt.Errorf("%s file %q: strict decode failed: %w", kind, path, err)
|
|
}
|
|
|
|
var extra any
|
|
if err := dec.Decode(&extra); err != nil && err != io.EOF {
|
|
return fmt.Errorf("%s file %q: trailing content decode failed: %w", kind, path, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func shortName(path, fallback string) string {
|
|
base := filepath.Base(path)
|
|
if base == "." || base == string(filepath.Separator) {
|
|
return fallback
|
|
}
|
|
return base
|
|
}
|
|
|
|
func applyPipelineDefaults(cfg *PipelineConfig) {
|
|
if cfg == nil {
|
|
return
|
|
}
|
|
applyStorageDefaults(&cfg.Storage)
|
|
applySpoolDefaults(&cfg.Spool)
|
|
applyArchiveDefaults(&cfg.Archive)
|
|
applyWhisperXDefaults(&cfg.WhisperX)
|
|
applySeriatimDefaults(&cfg.Seriatim)
|
|
applyAuditaDefaults(&cfg.Audita)
|
|
if cfg.Normalize == nil {
|
|
cfg.Normalize = &NormalizeConfig{}
|
|
}
|
|
applyNormalizeDefaults(cfg.Normalize)
|
|
applyTrimDefaults(cfg.Trim)
|
|
applyScriptoriumDefaults(cfg.Scriptorium)
|
|
}
|
|
|
|
func applyStorageDefaults(cfg *StorageConfig) {
|
|
if cfg == nil {
|
|
return
|
|
}
|
|
if cfg.S3 == nil {
|
|
cfg.S3 = &StorageS3Config{}
|
|
}
|
|
if cfg.S3.RootPrefix == "" {
|
|
cfg.S3.RootPrefix = "dnd"
|
|
}
|
|
}
|
|
|
|
func applySpoolDefaults(cfg *SpoolConfig) {
|
|
if cfg == nil {
|
|
return
|
|
}
|
|
if cfg.Root == "" {
|
|
cfg.Root = "/var/spool/narratio"
|
|
}
|
|
}
|
|
|
|
func applyArchiveDefaults(cfg **ArchiveConfig) {
|
|
if cfg == nil {
|
|
return
|
|
}
|
|
if *cfg == nil {
|
|
*cfg = &ArchiveConfig{}
|
|
}
|
|
|
|
if (*cfg).Enabled == nil {
|
|
(*cfg).Enabled = boolPtr(true)
|
|
}
|
|
if (*cfg).UploadRun == nil {
|
|
(*cfg).UploadRun = boolPtr(true)
|
|
}
|
|
if len((*cfg).PromoteArtifacts) == 0 {
|
|
(*cfg).PromoteArtifacts = []ArchivePromotionRule{
|
|
{From: "transcripts/trimmed.json", To: "transcripts/trimmed.json", Required: boolPtr(true)},
|
|
{From: "artifacts/session_recap.md", To: "artifacts/session_recap.md", Required: boolPtr(true)},
|
|
}
|
|
}
|
|
for i := range (*cfg).PromoteArtifacts {
|
|
if (*cfg).PromoteArtifacts[i].Required == nil {
|
|
(*cfg).PromoteArtifacts[i].Required = boolPtr(true)
|
|
}
|
|
}
|
|
}
|
|
|
|
func applyWhisperXDefaults(cfg *WhisperXConfig) {
|
|
if cfg == nil {
|
|
return
|
|
}
|
|
if cfg.Language == "" {
|
|
cfg.Language = "en"
|
|
}
|
|
if cfg.Timeout == "" {
|
|
cfg.Timeout = "30m"
|
|
}
|
|
if cfg.RetryDelay == "" {
|
|
cfg.RetryDelay = "2s"
|
|
}
|
|
if cfg.Concurrency == nil {
|
|
cfg.Concurrency = intPtr(2)
|
|
}
|
|
if cfg.Retries == nil {
|
|
cfg.Retries = intPtr(3)
|
|
}
|
|
}
|
|
|
|
func intPtr(v int) *int {
|
|
p := v
|
|
return &p
|
|
}
|
|
|
|
func applySeriatimDefaults(cfg *SeriatimConfig) {
|
|
if cfg == nil {
|
|
return
|
|
}
|
|
if cfg.Timeout == "" {
|
|
cfg.Timeout = "10m"
|
|
}
|
|
if cfg.OutputSchema == "" {
|
|
cfg.OutputSchema = "seriatim-intermediate"
|
|
}
|
|
if cfg.CoalesceGap == nil {
|
|
cfg.CoalesceGap = float64Ptr(3.0)
|
|
}
|
|
if cfg.Report == nil {
|
|
cfg.Report = boolPtr(true)
|
|
}
|
|
}
|
|
|
|
func applyAuditaDefaults(cfg *AuditaConfig) {
|
|
if cfg == nil {
|
|
return
|
|
}
|
|
if cfg.Timeout == "" {
|
|
cfg.Timeout = "3h"
|
|
}
|
|
if cfg.BaseURL == "" {
|
|
cfg.BaseURL = "https://openrouter.ai/api/v1"
|
|
}
|
|
if cfg.Model == "" {
|
|
cfg.Model = "openrouter/google/gemma-4-31b-it"
|
|
}
|
|
if cfg.Report == nil {
|
|
cfg.Report = boolPtr(true)
|
|
}
|
|
}
|
|
|
|
func applyScriptoriumDefaults(cfg *ScriptoriumConfig) {
|
|
if cfg == nil {
|
|
return
|
|
}
|
|
if cfg.Timeout == "" {
|
|
cfg.Timeout = "10m"
|
|
}
|
|
}
|
|
|
|
func applyTrimDefaults(cfg *TrimConfig) {
|
|
if cfg == nil {
|
|
return
|
|
}
|
|
if cfg.Bounds.Timeout == "" {
|
|
cfg.Bounds.Timeout = "10m"
|
|
}
|
|
if cfg.Seriatim.Report == nil {
|
|
cfg.Seriatim.Report = boolPtr(false)
|
|
}
|
|
}
|
|
|
|
func applyNormalizeDefaults(cfg *NormalizeConfig) {
|
|
if cfg == nil {
|
|
return
|
|
}
|
|
if cfg.OutputSchema == "" {
|
|
cfg.OutputSchema = defaultNormalizeOutputSchema
|
|
}
|
|
if cfg.OutputPath == "" && !cfg.outputPathWasSet() {
|
|
cfg.OutputPath = defaultNormalizeOutputPath
|
|
}
|
|
if cfg.Report == nil {
|
|
cfg.Report = boolPtr(true)
|
|
}
|
|
}
|
|
|
|
func float64Ptr(v float64) *float64 {
|
|
p := v
|
|
return &p
|
|
}
|
|
|
|
func boolPtr(v bool) *bool {
|
|
p := v
|
|
return &p
|
|
}
|