// Package configuration adapts ConfigKit's effective configuration to // Promptkit's definition and execution boundaries. package configuration import ( "fmt" "net/http" "regexp" "sort" "strings" "go.maximumdirect.net/configkit" "go.maximumdirect.net/promptkit/internal/backend" "go.maximumdirect.net/promptkit/internal/domain" "go.maximumdirect.net/promptkit/internal/jsonvalue" "go.maximumdirect.net/promptkit/internal/llm" ) type document struct { Version *string `yaml:"version"` Backends []backendConfig `yaml:"backends"` Profiles []profileConfig `yaml:"profiles"` Prompts []promptConfig `yaml:"prompts"` Collections map[string]collectionConfig `yaml:"collections"` } type collectionConfig struct { ReleaseVersion *string `yaml:"release_version"` Backends []backendConfig `yaml:"backends"` Profiles []profileConfig `yaml:"profiles"` Prompts []promptConfig `yaml:"prompts"` } type backendConfig struct { ID string `yaml:"id"` Endpoint *string `yaml:"endpoint"` APIKeyEnv *string `yaml:"api_key_env"` ConcurrencyLimit *int `yaml:"concurrency_limit"` QueueCapacity *int `yaml:"queue_capacity"` ExtraParams map[string]any `yaml:"extra_params"` SmokeTests *backendSmokeTestConfig `yaml:"smoke_tests"` } type backendSmokeTestConfig struct { DefaultProfile *string `yaml:"default_profile"` Reachability *reachabilityProbeConfig `yaml:"reachability"` Authentication *authenticationProbeConfig `yaml:"authentication"` } type reachabilityProbeConfig struct { Endpoint *string `yaml:"endpoint"` } type authenticationProbeConfig struct { Kind *string `yaml:"kind"` Endpoint *string `yaml:"endpoint"` } type profileConfig struct { ID string `yaml:"id"` Backend *string `yaml:"backend"` Endpoint *string `yaml:"endpoint"` Model *string `yaml:"model"` Temperature *float64 `yaml:"temperature"` TopP *float64 `yaml:"top_p"` MaxTokens *int `yaml:"max_tokens"` TimeoutSeconds *int `yaml:"timeout_seconds"` ServiceTier *string `yaml:"service_tier"` ReasoningEffort *string `yaml:"reasoning_effort"` APIKeyEnv *string `yaml:"api_key_env"` APIKeyRequired *bool `yaml:"api_key_required"` ExtraParams map[string]any `yaml:"extra_params"` } type promptConfig struct { ID string `yaml:"id"` DefaultProfile *string `yaml:"default_profile"` Description *string `yaml:"description"` SessionID *string `yaml:"session_id"` Inputs []promptInputConfig `yaml:"inputs"` Messages []promptMessageConfig `yaml:"messages"` Output *promptOutputConfig `yaml:"output"` } type promptInputConfig struct { Name string `yaml:"name"` Required bool `yaml:"required"` ContentType *string `yaml:"content_type"` Description *string `yaml:"description"` } type promptMessageConfig struct { Role *string `yaml:"role"` Content *string `yaml:"content"` ContentFile *string `yaml:"content_file"` CacheControl *cacheControlConfig `yaml:"cache_control"` } type cacheControlConfig struct { Type *string `yaml:"type"` TTL *string `yaml:"ttl"` } type promptOutputConfig struct { Format *string `yaml:"format"` ValidationMode *string `yaml:"validation_mode"` SchemaPath *string `yaml:"schema_path"` RepairAttempts int `yaml:"repair_attempts"` } // fieldState distinguishes omission, an explicit value, and an explicit null. // Inherited values are explicit because ConfigKit has already expanded them. type fieldState uint8 const ( fieldAbsent fieldState = iota fieldValue fieldNull ) type loaded struct { result *configkit.Result[document] interpreted document backends map[string]map[string]fieldState profiles map[string]map[string]fieldState promptMetadata map[string][]promptInputPresence prompts map[string]*promptEntry registry *backend.Registry indexedProfiles map[string]domain.ExecutionProfile backendIDs []string profileIDs []string promptIDs []domain.PromptIdentity namespaces map[string][]string collections []CollectionIdentity } type resourceBases struct { backends configkit.Path profiles configkit.Path prompts configkit.Path } type resourceSet struct { isCollection bool namespace string bases resourceBases backends []backendConfig profiles []profileConfig prompts []promptConfig } func rootResourceBases() resourceBases { return resourceBases{ backends: configkit.Keys("backends"), profiles: configkit.Keys("profiles"), prompts: configkit.Keys("prompts"), } } func resourceSets(config *document) []resourceSet { sets := make([]resourceSet, 0, len(config.Collections)+1) sets = append(sets, resourceSet{ bases: rootResourceBases(), backends: config.Backends, profiles: config.Profiles, prompts: config.Prompts, }) for _, namespace := range sortedKeys(config.Collections) { collection := config.Collections[namespace] base := configkit.Keys("collections", namespace) sets = append(sets, resourceSet{ isCollection: true, namespace: namespace, bases: resourceBases{ backends: base.Key("backends"), profiles: base.Key("profiles"), prompts: base.Key("prompts"), }, backends: collection.Backends, profiles: collection.Profiles, prompts: collection.Prompts, }) } return sets } func indexedResourceID(namespace, localKey string) string { qualified, valid := domain.QualifyLocalResourceID(namespace, localKey) if valid { return qualified } if namespace == "" { return localKey } return namespace + "/" + localKey } func indexedBackends(config *document) map[string]backendConfig { backends := make(map[string]backendConfig) for _, resources := range resourceSets(config) { for _, definition := range resources.backends { backends[indexedResourceID(resources.namespace, definition.ID)] = definition } } return backends } func indexedProfiles(config *document) map[string]profileConfig { profiles := make(map[string]profileConfig) for _, resources := range resourceSets(config) { for _, definition := range resources.profiles { profiles[indexedResourceID(resources.namespace, definition.ID)] = definition } } return profiles } var envName = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) var backendFields = []string{"endpoint", "api_key_env", "concurrency_limit", "queue_capacity", "extra_params", "smoke_tests"} var profileFields = []string{"backend", "endpoint", "model", "temperature", "top_p", "max_tokens", "timeout_seconds", "service_tier", "reasoning_effort", "api_key_env", "api_key_required", "extra_params"} // Load captures one immutable effective configuration for internal consumers. func Load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error) { return load(main, underlays) } func configurationDefinition(state *loaded) configkit.Definition[document] { return configkit.Definition[document]{ SequenceIdentities: []configkit.SequenceIdentity{ configkit.SequenceIdentityAt(configkit.Keys("backends"), "id"), configkit.SequenceIdentityAt(configkit.Keys("profiles"), "id"), configkit.SequenceIdentityAt(configkit.Keys("prompts"), "id"), configkit.SequenceIdentityMatching(configkit.SequenceSelector{configkit.SequenceLiteralKey("collections"), configkit.SequenceAnyMappingKey(), configkit.SequenceLiteralKey("backends")}, "id"), configkit.SequenceIdentityMatching(configkit.SequenceSelector{configkit.SequenceLiteralKey("collections"), configkit.SequenceAnyMappingKey(), configkit.SequenceLiteralKey("profiles")}, "id"), configkit.SequenceIdentityMatching(configkit.SequenceSelector{configkit.SequenceLiteralKey("collections"), configkit.SequenceAnyMappingKey(), configkit.SequenceLiteralKey("prompts")}, "id"), }, Version: &configkit.VersionRule{ Path: configkit.Keys("version"), Check: checkConfigurationVersion, }, Sensitive: secretProviderPath, Resolve: func(config *document, resolution *configkit.Resolution) error { for _, resources := range resourceSets(config) { captureFields(resources.backends, func(v backendConfig) string { return v.ID }, resources.bases.backends, resources.namespace, backendFields, resolution, state.backends) captureFields(resources.profiles, func(v profileConfig) string { return v.ID }, resources.bases.profiles, resources.namespace, profileFields, resolution, state.profiles) capturePromptMetadata(resources.prompts, resources.bases.prompts, resources.namespace, resolution, state.promptMetadata) } return nil }, Validate: func(config *document, validation *configkit.Validation) { state.interpreted = interpretConfiguration(config, validation) validateEffective(&state.interpreted, validation) }, } } // load performs one ConfigKit lifecycle over the selected source tiers. func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error) { state := &loaded{ backends: make(map[string]map[string]fieldState), profiles: make(map[string]map[string]fieldState), promptMetadata: make(map[string][]promptInputPresence), } result, err := configkit.LoadLayered(main, configkit.LayeredOptions[document]{ Underlays: underlays, SourceVersionMode: configkit.SourceVersionModeIfPresent, Definition: configurationDefinition(state), }) if err != nil { return nil, fmt.Errorf("load configuration: %w", err) } state.result = result if err := state.materializePrompts(); err != nil { return nil, fmt.Errorf("load configuration: %w", err) } if err := state.buildExecutionProfiles(); err != nil { return nil, fmt.Errorf("load configuration: %w", err) } state.backendIDs = sortedKeys(indexedBackends(&state.interpreted)) state.profileIDs = sortedKeys(state.indexedProfiles) for _, id := range sortedKeys(state.prompts) { state.promptIDs = append(state.promptIDs, domain.PromptIdentity{ID: id}) } state.namespaces = map[string][]string{ "backend": resourceNamespaces(state.backendIDs), "profile": resourceNamespaces(state.profileIDs), "prompt": promptNamespaces(state.promptIDs), } for _, namespace := range sortedKeys(result.Config.Collections) { collection := result.Config.Collections[namespace] state.collections = append(state.collections, CollectionIdentity{ Namespace: namespace, ReleaseVersion: stringPointerValue(collection.ReleaseVersion), }) } return state, nil } func resourceNamespaces(ids []string) []string { namespaces := make(map[string]struct{}) for _, id := range ids { if namespace, _, qualified := strings.Cut(id, "/"); qualified { namespaces[namespace] = struct{}{} } } return sortedKeys(namespaces) } func promptNamespaces(prompts []domain.PromptIdentity) []string { ids := make([]string, len(prompts)) for index := range prompts { ids[index] = prompts[index].ID } return resourceNamespaces(ids) } func checkConfigurationVersion(version configkit.Version, validation *configkit.Validation) error { if !version.Present { return nil } if version.Null || version.Text != "2" { validation.Error(configkit.Keys("version"), "unsupported configuration specification version") } return nil } type promptInputPresence struct { contentType fieldState description fieldState } func capturePromptMetadata(prompts []promptConfig, basePath configkit.Path, namespace string, resolution *configkit.Resolution, destination map[string][]promptInputPresence) { for promptIndex, prompt := range prompts { inputs := prompt.Inputs states := make([]promptInputPresence, len(inputs)) base := basePath.Index(promptIndex).Key("inputs") for index := range inputs { path := base.Index(index) states[index] = promptInputPresence{ contentType: captureFieldState(path.Key("content_type"), resolution), description: captureFieldState(path.Key("description"), resolution), } } destination[indexedResourceID(namespace, prompt.ID)] = states } } func captureFieldState(path configkit.Path, resolution *configkit.Resolution) fieldState { info, exists := resolution.EntryInfo(path) switch { case !exists || !info.Explicit: return fieldAbsent case info.Null: return fieldNull default: return fieldValue } } func captureFields[T any](entries []T, identity func(T) string, basePath configkit.Path, namespace string, fields []string, resolution *configkit.Resolution, destination map[string]map[string]fieldState) { for index, entry := range entries { states := make(map[string]fieldState, len(fields)) for _, field := range fields { info, exists := resolution.EntryInfo(basePath.Index(index).Key(field)) switch { case !exists || !info.Explicit: states[field] = fieldAbsent case info.Null: states[field] = fieldNull default: states[field] = fieldValue } } destination[indexedResourceID(namespace, identity(entry))] = states } } func sortedKeys[T any](entries map[string]T) []string { keys := make([]string, 0, len(entries)) for key := range entries { keys = append(keys, key) } sort.Strings(keys) return keys } func interpretConfiguration(config *document, validation *configkit.Validation) document { interpreted := *config interpreted.Backends = interpretBackends(config.Backends, configkit.Keys("backends"), validation) interpreted.Profiles = interpretProfiles(config.Profiles, configkit.Keys("profiles"), validation) interpreted.Prompts = interpretPrompts(config.Prompts) if config.Collections != nil { interpreted.Collections = make(map[string]collectionConfig, len(config.Collections)) for namespace, original := range config.Collections { collection := original base := configkit.Keys("collections", namespace) collection.Backends = interpretBackends(original.Backends, base.Key("backends"), validation) collection.Profiles = interpretProfiles(original.Profiles, base.Key("profiles"), validation) collection.Prompts = interpretPrompts(original.Prompts) interpreted.Collections[namespace] = collection } } return interpreted } func interpretBackends(source []backendConfig, basePath configkit.Path, validation *configkit.Validation) []backendConfig { if source == nil { return nil } backends := append([]backendConfig{}, source...) for index := range backends { backend := &backends[index] base := basePath.Index(index) if backend.Endpoint != nil { endpoint, err := domain.NormalizeOpenAICompatibleBaseEndpoint(*backend.Endpoint) if err != nil { validation.Error(base.Key("endpoint"), err.Error()) } else { backend.Endpoint = &endpoint } } if backend.APIKeyEnv != nil { value := strings.TrimSpace(*backend.APIKeyEnv) backend.APIKeyEnv = &value } if backend.SmokeTests != nil { smokeTests := *backend.SmokeTests backend.SmokeTests = &smokeTests smokePath := base.Key("smoke_tests") if smokeTests.Reachability != nil { reachability := *smokeTests.Reachability smokeTests.Reachability = &reachability normalizeDiagnosticEndpoint(&reachability.Endpoint, smokePath.Key("reachability").Key("endpoint"), validation) } if smokeTests.Authentication != nil { authentication := *smokeTests.Authentication smokeTests.Authentication = &authentication if authentication.Kind != nil { kind := strings.ToLower(strings.TrimSpace(*authentication.Kind)) authentication.Kind = &kind } normalizeDiagnosticEndpoint(&authentication.Endpoint, smokePath.Key("authentication").Key("endpoint"), validation) } } if backend.ConcurrencyLimit != nil && *backend.ConcurrencyLimit > 0 && backend.QueueCapacity == nil { capacity := 1024 backend.QueueCapacity = &capacity } } return backends } func normalizeDiagnosticEndpoint(value **string, path configkit.Path, validation *configkit.Validation) { if value == nil || *value == nil { return } normalized := strings.TrimSpace(**value) request, err := http.NewRequest(http.MethodGet, normalized, nil) if normalized == "" || err != nil || request.URL == nil || (strings.ToLower(request.URL.Scheme) != "http" && strings.ToLower(request.URL.Scheme) != "https") || request.URL.Hostname() == "" { validation.Error(path, "diagnostic endpoint must be an absolute HTTP(S) URL with a host") return } *value = &normalized } func interpretProfiles(source []profileConfig, basePath configkit.Path, validation *configkit.Validation) []profileConfig { if source == nil { return nil } profiles := append([]profileConfig{}, source...) for index := range profiles { profile := &profiles[index] base := basePath.Index(index) if profile.Endpoint != nil && *profile.Endpoint != "" { endpoint, err := domain.NormalizeOpenAICompatibleBaseEndpoint(*profile.Endpoint) if err != nil { validation.Error(base.Key("endpoint"), err.Error()) } else { profile.Endpoint = &endpoint } } for _, value := range []**string{&profile.ServiceTier, &profile.ReasoningEffort, &profile.APIKeyEnv} { if *value != nil { normalized := strings.TrimSpace(**value) *value = &normalized } } } return profiles } func validateEffective(config *document, validation *configkit.Validation) { sets := resourceSets(config) backendIDs := make(map[string]struct{}) for _, resources := range sets { if resources.isCollection && !domain.IsValidResourceNamespace(resources.namespace) { validation.Error(configkit.Keys("collections", resources.namespace), "collection namespace must be nonblank and contain no slash") } for _, backend := range resources.backends { backendIDs[indexedResourceID(resources.namespace, backend.ID)] = struct{}{} } } for _, resources := range sets { for index, backend := range resources.backends { base := resources.bases.backends.Index(index) if !domain.IsValidLocalResourceKey(backend.ID) { validation.Error(base.Key("id"), "backend ID must be a nonblank local key without a slash") } if backend.Endpoint == nil || *backend.Endpoint == "" { validation.Error(base.Key("endpoint"), "endpoint is required") } if backend.APIKeyEnv != nil && *backend.APIKeyEnv != "" && !envName.MatchString(*backend.APIKeyEnv) { validation.Error(base.Key("api_key_env"), "environment variable name is invalid") } if backend.ConcurrencyLimit != nil && *backend.ConcurrencyLimit < 0 { validation.Error(base.Key("concurrency_limit"), "concurrency limit must not be negative") } if backend.QueueCapacity != nil { if *backend.QueueCapacity < 0 { validation.Error(base.Key("queue_capacity"), "queue capacity must not be negative") } if backend.ConcurrencyLimit == nil || *backend.ConcurrencyLimit <= 0 { validation.Error(base.Key("queue_capacity"), "queue capacity requires a positive concurrency limit") } else if *backend.QueueCapacity > int(^uint(0)>>1)-*backend.ConcurrencyLimit { validation.Error(base.Key("queue_capacity"), "total capacity overflows int") } } validateExtraParams(backend.ExtraParams, base.Key("extra_params"), true, validation) if smokeTests := backend.SmokeTests; smokeTests != nil { smokePath := base.Key("smoke_tests") if smokeTests.DefaultProfile != nil { if strings.TrimSpace(*smokeTests.DefaultProfile) == "" { validation.Error(smokePath.Key("default_profile"), "default profile must not be blank") } else if !domain.IsValidResourceID(*smokeTests.DefaultProfile) { validation.Error(smokePath.Key("default_profile"), "default profile reference is malformed") } } if authentication := smokeTests.Authentication; authentication != nil { kindPath := smokePath.Key("authentication").Key("kind") if authentication.Kind == nil { validation.Error(kindPath, "authentication kind is required") } else if *authentication.Kind != "none" && *authentication.Kind != "openrouter_key" { validation.Error(kindPath, "authentication kind must be none or openrouter_key") } } } } for index, profile := range resources.profiles { base := resources.bases.profiles.Index(index) if !domain.IsValidLocalResourceKey(profile.ID) { validation.Error(base.Key("id"), "profile ID must be a nonblank local key without a slash") } if profile.Backend != nil && *profile.Backend != "" { if !domain.IsValidResourceID(*profile.Backend) { validation.Error(base.Key("backend"), "backend reference is malformed") } else if _, exists := backendIDs[*profile.Backend]; !exists { validation.Error(base.Key("backend"), "backend is not defined") } } if profile.Model != nil && *profile.Model != "" && strings.TrimSpace(*profile.Model) == "" { validation.Error(base.Key("model"), "model must not be blank") } if profile.Temperature != nil { if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{Temperature: *profile.Temperature}); err != nil { validation.Error(base.Key("temperature"), err.Error()) } } if profile.TopP != nil { if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{TopP: *profile.TopP}); err != nil { validation.Error(base.Key("top_p"), err.Error()) } } if profile.MaxTokens != nil { if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{MaxTokens: *profile.MaxTokens}); err != nil { validation.Error(base.Key("max_tokens"), err.Error()) } } if profile.TimeoutSeconds != nil { if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{TimeoutSeconds: *profile.TimeoutSeconds}); err != nil { validation.Error(base.Key("timeout_seconds"), err.Error()) } } validateExtraParams(profile.ExtraParams, base.Key("extra_params"), false, validation) } for index, prompt := range resources.prompts { base := resources.bases.prompts.Index(index) if !domain.IsValidLocalResourceKey(prompt.ID) { validation.Error(base.Key("id"), "prompt ID must be a nonblank local key without a slash") } if prompt.DefaultProfile != nil && *prompt.DefaultProfile != "" && !domain.IsValidResourceID(*prompt.DefaultProfile) { validation.Error(base.Key("default_profile"), "default profile reference is malformed") } } } } func validateExtraParams(values map[string]any, path configkit.Path, backend bool, validation *configkit.Validation) { if values == nil { return } if _, err := jsonvalue.CopyMap(values); err != nil { validation.Error(path, "extra parameters must be a JSON-compatible map with nonempty keys") return } if backend { for key := range values { if llm.IsReservedOpenAIChatRequestField(key) { validation.Error(path.Key(key), "extra parameter conflicts with a request field") } } } }