package config import ( "gitea.maximumdirect.net/eric/notarius/internal/core/diagnostics" "gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline" ) const SupportedFileConfigVersion = 1 type Config struct { LLMProfiles map[string]LLMProfile `json:"llm_profiles"` Pipelines map[string]pipeline.PipelineProfile `json:"pipelines"` Concurrency ConcurrencyConfig `json:"concurrency"` Diagnostics DiagnosticsConfig `json:"diagnostics"` } type LLMProfile struct { Provider string `json:"provider,omitempty"` BaseURL string `json:"base_url,omitempty"` Model string `json:"model,omitempty"` APIKey string `json:"api_key,omitempty"` APIKeyEnv string `json:"api_key_env,omitempty"` TimeoutSeconds int `json:"timeout_seconds,omitempty"` MaxRetries int `json:"max_retries,omitempty"` MaxConcurrency int `json:"max_concurrency,omitempty"` } type ConcurrencyConfig struct { TotalLLM int `json:"total_llm"` } type DiagnosticsConfig struct { WorkDir string `json:"work_dir"` Retention diagnostics.RetentionMode `json:"retention"` } func Default() Config { return Config{ LLMProfiles: map[string]LLMProfile{ pipeline.DefaultLLMProfile: { Provider: "openai-compatible", TimeoutSeconds: 600, MaxRetries: 3, MaxConcurrency: 1, }, }, Pipelines: map[string]pipeline.PipelineProfile{}, Concurrency: ConcurrencyConfig{ TotalLLM: 1, }, Diagnostics: DiagnosticsConfig{ WorkDir: "/tmp/notarius", Retention: diagnostics.RetentionAuto, }, } } func cloneConfig(in Config) Config { out := in out.LLMProfiles = make(map[string]LLMProfile, len(in.LLMProfiles)) for key, profile := range in.LLMProfiles { out.LLMProfiles[key] = profile } out.Pipelines = make(map[string]pipeline.PipelineProfile, len(in.Pipelines)) for key, profile := range in.Pipelines { out.Pipelines[key] = clonePipelineProfile(profile) } return out } func clonePipelineProfile(in pipeline.PipelineProfile) pipeline.PipelineProfile { out := in out.Input = cloneModuleBinding(in.Input) out.Chunk = cloneModuleBinding(in.Chunk) out.Output = cloneModuleBinding(in.Output) out.References = cloneStringMap(in.References) if len(in.Artifacts) > 0 { out.Artifacts = make(map[string]pipeline.ArtifactLaneProfile, len(in.Artifacts)) for key, lane := range in.Artifacts { out.Artifacts[key] = cloneArtifactLaneProfile(lane) } } return out } func cloneArtifactLaneProfile(in pipeline.ArtifactLaneProfile) pipeline.ArtifactLaneProfile { out := in out.Extract = cloneModuleBinding(in.Extract) out.Merge = cloneModuleBinding(in.Merge) out.Normalize = cloneModuleBinding(in.Normalize) out.References = cloneStringMap(in.References) if len(in.Validators) > 0 { out.Validators = make([]pipeline.ModuleBinding, len(in.Validators)) for i, binding := range in.Validators { out.Validators[i] = cloneModuleBinding(binding) } } return out } func cloneStringMap(in map[string]string) map[string]string { if len(in) == 0 { return nil } out := make(map[string]string, len(in)) for key, value := range in { out[key] = value } return out } func cloneModuleBinding(in pipeline.ModuleBinding) pipeline.ModuleBinding { out := in if len(in.Options) > 0 { out.Options = cloneOptions(in.Options) } return out } func cloneOptions(in map[string]any) map[string]any { if len(in) == 0 { return nil } out := make(map[string]any, len(in)) for key, value := range in { out[key] = cloneOptionValue(value) } return out } func cloneOptionValue(value any) any { switch typed := value.(type) { case map[string]any: return cloneOptions(typed) case []any: out := make([]any, len(typed)) for i, item := range typed { out[i] = cloneOptionValue(item) } return out default: return typed } }