package config import ( "fmt" "net/url" "strings" ) // ProtocolDriver identifies the wire/stream semantics of a protocol profile. type ProtocolDriver string const ( ProtocolDriverOpenAIChat ProtocolDriver = "openai_chat" ProtocolDriverAnthropicMessages ProtocolDriver = "anthropic_messages" ProtocolDriverOpenAIResponses ProtocolDriver = "openai_responses" ) // validProtocolDrivers is the closed set of admissible driver values. var validProtocolDrivers = map[ProtocolDriver]struct{}{ ProtocolDriverOpenAIChat: {}, ProtocolDriverAnthropicMessages: {}, ProtocolDriverOpenAIResponses: {}, } // ProtocolOperation is a named operation within a protocol profile. type ProtocolOperation string const ( OperationModels ProtocolOperation = "models" OperationChatCompletions ProtocolOperation = "chat_completions" OperationMessages ProtocolOperation = "messages" OperationCountTokens ProtocolOperation = "count_tokens" OperationResponses ProtocolOperation = "responses" ) // validProtocolOperations is the closed set of admissible operation ids. var validProtocolOperations = map[ProtocolOperation]struct{}{ OperationModels: {}, OperationChatCompletions: {}, OperationMessages: {}, OperationCountTokens: {}, OperationResponses: {}, } // validOperationsByDriver restricts which operations each driver may declare. var validOperationsByDriver = map[ProtocolDriver]map[ProtocolOperation]struct{}{ ProtocolDriverOpenAIChat: { OperationModels: {}, OperationChatCompletions: {}, OperationResponses: {}, OperationCountTokens: {}, }, ProtocolDriverAnthropicMessages: { OperationModels: {}, OperationMessages: {}, OperationCountTokens: {}, }, ProtocolDriverOpenAIResponses: { OperationModels: {}, OperationResponses: {}, OperationCountTokens: {}, }, } // validProtocolCapabilities is the closed vocabulary shared by config, // admission, and request preparation. Lifecycle capabilities are a separate // provider concern and intentionally do not participate in this set. var validProtocolCapabilities = map[string]struct{}{ "models": {}, "chat": {}, "messages": {}, "responses": {}, "streaming": {}, "tool_calling": {}, "count_tokens": {}, } var operationCapability = map[ProtocolOperation]string{ OperationModels: "models", OperationChatCompletions: "chat", OperationMessages: "messages", OperationCountTokens: "count_tokens", OperationResponses: "responses", } // TokenCounterMode describes how a model group's input tokens are counted. type TokenCounterMode string const ( TokenCounterDeterministic TokenCounterMode = "deterministic" TokenCounterEstimate TokenCounterMode = "estimate" ) // ProtocolAuthConf declares how a profile authenticates to its upstream. type ProtocolAuthConf struct { // Header is the request header name carrying the credential (e.g. // "Authorization" or "x-api-key"). Header string `mapstructure:"header" yaml:"header,omitempty"` // Scheme is the auth scheme prefix (e.g. "Bearer"). Empty means the // raw header value is the credential. Scheme string `mapstructure:"scheme" yaml:"scheme,omitempty"` } // ProtocolProfileConf is the overlayable configuration of a protocol profile. // It is the source of truth for endpoint, operation paths, auth, and // capabilities before concrete resolution. type ProtocolProfileConf struct { // Base is the optional id of a built-in/custom profile this overlay // extends. At most one base is allowed; cycles and unknown bases are // rejected at config load. Base string `mapstructure:"base" yaml:"base,omitempty"` // Driver is the protocol driver (openai_chat, anthropic_messages, // openai_responses). Driver ProtocolDriver `mapstructure:"driver" yaml:"driver,omitempty"` // BaseURL is the normalized upstream base URL (no trailing slash). BaseURL string `mapstructure:"base_url" yaml:"base_url,omitempty"` // Operations maps operation id to a relative or absolute URL path. // Absolute URLs are returned unchanged; relative paths are joined once // to BaseURL. Operations map[string]string `mapstructure:"operations" yaml:"operations,omitempty"` // Auth declares the upstream authentication header/scheme. Auth ProtocolAuthConf `mapstructure:"auth" yaml:"auth,omitempty"` // Capabilities lists the profile's declared capabilities (e.g. // "messages", "streaming", "tool_calling"). Capabilities []string `mapstructure:"capabilities" yaml:"capabilities,omitempty"` // ModelMapping maps selected/canonical model ids to upstream model ids. ModelMapping map[string]string `mapstructure:"model_mapping" yaml:"model_mapping,omitempty"` // Extensions holds restricted profile-specific options that cannot be // expressed in the typed fields above. Extensions map[string]any `mapstructure:"extensions" yaml:"extensions,omitempty"` } // ConcreteProtocolProfile is the immutable, resolved snapshot of a protocol // profile. It is what gets carried through the wire, Node config, and adapter // resolution. It never contains a Base reference. type ConcreteProtocolProfile struct { ID string ProtocolProfileConf } // TokenCounterConf declares how a model group's input tokens are counted // without an upstream call. type TokenCounterConf struct { // Mode is "deterministic" or "estimate". Mode TokenCounterMode `mapstructure:"mode" yaml:"mode,omitempty"` // Per1kInput is the estimated input tokens per 1k characters for // estimate mode. Per1kInput int `mapstructure:"per_1k_input" yaml:"per_1k_input,omitempty"` } // builtInProtocolProfiles is the process-owned catalog. Config normalization // always clones from this private map, so callers cannot mutate runtime // defaults through the exported compatibility snapshot below. var builtInProtocolProfiles = map[string]ProtocolProfileConf{ "openai": { Driver: ProtocolDriverOpenAIChat, BaseURL: "https://api.openai.com/v1", Operations: map[string]string{ string(OperationModels): "/models", string(OperationChatCompletions): "/chat/completions", string(OperationResponses): "/responses", }, Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"models", "chat", "streaming", "tool_calling", "responses"}, }, "gemini": { Driver: ProtocolDriverOpenAIChat, BaseURL: "https://generativelanguage.googleapis.com/v1beta/openai", Operations: map[string]string{ string(OperationModels): "/models", string(OperationChatCompletions): "/chat/completions", }, Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"models", "chat", "streaming", "tool_calling"}, }, "anthropic": { Driver: ProtocolDriverAnthropicMessages, BaseURL: "https://api.anthropic.com", Operations: map[string]string{ string(OperationMessages): "/v1/messages", string(OperationCountTokens): "/v1/messages/count_tokens", }, Auth: ProtocolAuthConf{Header: "x-api-key"}, Capabilities: []string{"messages", "streaming", "tool_calling", "count_tokens"}, }, "glm": { Driver: ProtocolDriverOpenAIChat, BaseURL: "https://api.z.ai/api/paas/v4", Operations: map[string]string{ string(OperationModels): "/models", string(OperationChatCompletions): "/chat/completions", }, Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"models", "chat", "streaming", "tool_calling"}, }, "glm_coding": { Driver: ProtocolDriverOpenAIChat, BaseURL: "https://api.z.ai/api/coding/paas/v4", Operations: map[string]string{ string(OperationModels): "/models", string(OperationChatCompletions): "/chat/completions", }, Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"models", "chat", "streaming", "tool_calling"}, }, "kimi": { Driver: ProtocolDriverOpenAIChat, BaseURL: "https://api.moonshot.cn/v1", Operations: map[string]string{ string(OperationModels): "/models", string(OperationChatCompletions): "/chat/completions", }, Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"models", "chat", "streaming"}, }, "minimax_chat": { Driver: ProtocolDriverOpenAIChat, BaseURL: "https://api.minimax.io/v1", Operations: map[string]string{ string(OperationModels): "/models", string(OperationChatCompletions): "/chat/completions", }, Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"models", "chat", "streaming"}, }, "minimax_messages": { Driver: ProtocolDriverAnthropicMessages, BaseURL: "https://api.minimax.io/anthropic", Operations: map[string]string{ string(OperationMessages): "/v1/messages", }, Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"messages", "streaming"}, }, "mimo_chat": { Driver: ProtocolDriverOpenAIChat, BaseURL: "https://api.xiaomimimo.com/v1", Operations: map[string]string{ string(OperationModels): "/models", string(OperationChatCompletions): "/chat/completions", }, Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"models", "chat", "streaming"}, }, "mimo_messages": { Driver: ProtocolDriverAnthropicMessages, BaseURL: "https://api.xiaomimimo.com/anthropic", Operations: map[string]string{ string(OperationMessages): "/v1/messages", }, Auth: ProtocolAuthConf{Header: "api-key"}, Capabilities: []string{"messages", "streaming"}, }, "grok": { Driver: ProtocolDriverOpenAIChat, BaseURL: "https://api.x.ai/v1", Operations: map[string]string{ string(OperationModels): "/models", string(OperationChatCompletions): "/chat/completions", }, Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"models", "chat", "streaming"}, }, "seulgi_chat": { Driver: ProtocolDriverOpenAIChat, BaseURL: "https://seulgivibe.example.invalid/openai/v1", Operations: map[string]string{ string(OperationModels): "/models", string(OperationChatCompletions): "/chat/completions", }, Auth: ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"}, Capabilities: []string{"models", "chat", "streaming"}, }, "seulgi_messages": { Driver: ProtocolDriverAnthropicMessages, BaseURL: "https://seulgivibe.example.invalid/anthropic/v1", Operations: map[string]string{ string(OperationMessages): "/messages", string(OperationCountTokens): "/messages/count_tokens", }, Auth: ProtocolAuthConf{Header: "x-api-key"}, Capabilities: []string{"messages", "streaming", "tool_calling", "count_tokens"}, }, } // BuiltInProtocolProfiles is a compatibility snapshot for callers that resolve // profiles directly. Runtime config assembly uses a fresh private clone via // BuiltInProtocolProfileCatalog and is therefore immune to caller mutation. var BuiltInProtocolProfiles = cloneProfileCatalog(builtInProtocolProfiles) // BuiltInProtocolProfileCatalog returns a fully independent built-in catalog. func BuiltInProtocolProfileCatalog() map[string]ProtocolProfileConf { return cloneProfileCatalog(builtInProtocolProfiles) } func cloneProfileCatalog(src map[string]ProtocolProfileConf) map[string]ProtocolProfileConf { dst := make(map[string]ProtocolProfileConf, len(src)) for id, profile := range src { dst[id] = deepCopyProfileConf(profile) } return dst } // legacyProviderTypeToProfile maps legacy provider type strings to built-in // profile ids. This is used only when no explicit profile selector is set. var legacyProviderTypeToProfile = map[string]string{ "openai_api": "openai", "vllm": "openai", "vllm-mlx": "openai", "lemonade": "openai", "sglang": "openai", "openai_compat": "openai", "seulgivibe_claude": "seulgi_messages", "seulgivibe_openai": "seulgi_chat", "anthropic": "anthropic", } // LegacyProviderTypeToProfile returns the built-in profile id that a legacy // provider type normalizes to. Returns "" when no mapping exists. func LegacyProviderTypeToProfile(providerType string) string { return legacyProviderTypeToProfile[strings.TrimSpace(providerType)] } // validateProfileOperation checks that an operation id is valid for the // given driver. func validateProfileOperation(driver ProtocolDriver, op string) error { ops, ok := validOperationsByDriver[driver] if !ok { return fmt.Errorf("profile driver %q is not recognized", driver) } opID := ProtocolOperation(op) if _, valid := validProtocolOperations[opID]; !valid { return fmt.Errorf("profile operation %q is not a known operation", op) } if _, valid := ops[opID]; !valid { return fmt.Errorf("profile operation %q is not supported for driver %q", op, driver) } return nil } // validateProfileURL validates that an operation path is a well-formed // relative path or absolute URL. func validateProfileURL(baseURL, opPath string) error { trimmed := strings.TrimSpace(opPath) if trimmed == "" { return fmt.Errorf("profile operation path must not be empty") } if strings.HasPrefix(trimmed, "http://") || strings.HasPrefix(trimmed, "https://") { parsed, err := url.Parse(trimmed) if err != nil { return fmt.Errorf("profile operation path %q is not a valid absolute URL: %w", trimmed, err) } if (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Host == "" { return fmt.Errorf("profile operation path %q must be an absolute http(s) URL", trimmed) } return nil } if !strings.HasPrefix(trimmed, "/") { return fmt.Errorf("profile operation path %q must start with '/' for relative paths (leading slash required)", trimmed) } if _, err := url.Parse(baseURL); err != nil { return fmt.Errorf("profile base_url %q is not a valid URL: %w", baseURL, err) } return nil } // deepCopyProfileConf creates a deep copy of a ProtocolProfileConf, copying // all maps and slices so mutations to the copy do not affect the original. func deepCopyProfileConf(src ProtocolProfileConf) ProtocolProfileConf { dst := src if src.Operations != nil { dst.Operations = make(map[string]string, len(src.Operations)) for k, v := range src.Operations { dst.Operations[k] = v } } if src.Capabilities != nil { dst.Capabilities = append([]string(nil), src.Capabilities...) } if src.ModelMapping != nil { dst.ModelMapping = make(map[string]string, len(src.ModelMapping)) for k, v := range src.ModelMapping { dst.ModelMapping[k] = v } } if src.Extensions != nil { dst.Extensions = deepCopyExtensions(src.Extensions) } return dst } // deepCopyExtensions deep-copies a map[string]any, handling nested maps and // slices. func deepCopyExtensions(src map[string]any) map[string]any { if src == nil { return nil } dst := make(map[string]any, len(src)) for k, v := range src { dst[k] = deepCopyAny(v) } return dst } func deepCopyAny(v any) any { switch val := v.(type) { case map[string]any: return deepCopyExtensions(val) case map[string]string: out := make(map[string]string, len(val)) for k, item := range val { out[k] = item } return out case []any: out := make([]any, len(val)) for i, item := range val { out[i] = deepCopyAny(item) } return out case []string: return append([]string(nil), val...) default: return v } } // ResolveProtocolProfile resolves a provider's profile selector against the // catalog (built-in + custom overlays), producing an immutable // ConcreteProtocolProfile snapshot. It enforces: // - cycle detection (base chain must terminate) // - unknown base rejection // - driver conflict (overlay driver must match base driver) // - operation/auth/capability validation // - URL validation // // When selector is empty, legacyType is consulted for alias normalization. // If neither resolves, the returned error explains the failure. func ResolveProtocolProfile(selector, legacyType string, catalog map[string]ProtocolProfileConf) (ConcreteProtocolProfile, error) { id := strings.TrimSpace(selector) if id == "" { id = LegacyProviderTypeToProfile(legacyType) } if id == "" { return ConcreteProtocolProfile{}, fmt.Errorf("provider profile: no profile selector and no legacy type mapping for %q", legacyType) } visited := make(map[string]struct{}) var resolve func(pid string) (ProtocolProfileConf, error) resolve = func(pid string) (ProtocolProfileConf, error) { if _, cycle := visited[pid]; cycle { return ProtocolProfileConf{}, fmt.Errorf("profile %q: base cycle detected", pid) } visited[pid] = struct{}{} base, ok := catalog[pid] if !ok { return ProtocolProfileConf{}, fmt.Errorf("profile %q: unknown profile id", pid) } if strings.TrimSpace(base.Base) != "" { parent, err := resolve(base.Base) if err != nil { return ProtocolProfileConf{}, err } merged := deepCopyProfileConf(parent) overlay := deepCopyProfileConf(base) merged, err = mergeProfileOverlay(merged, overlay) if err != nil { return ProtocolProfileConf{}, err } return merged, nil } return deepCopyProfileConf(base), nil } resolved, err := resolve(id) if err != nil { return ConcreteProtocolProfile{}, err } if err := validateConcreteProfile(id, resolved); err != nil { return ConcreteProtocolProfile{}, err } return ConcreteProtocolProfile{ID: id, ProtocolProfileConf: resolved}, nil } // mergeProfileOverlay merges an overlay onto a base profile. Overlay fields // that are non-zero/non-empty replace base fields; nil/empty overlay fields // inherit from the base. A driver conflict (overlay driver differs from base // driver) returns an error. func mergeProfileOverlay(base, overlay ProtocolProfileConf) (ProtocolProfileConf, error) { merged := base if strings.TrimSpace(string(overlay.Driver)) != "" { if overlay.Driver != base.Driver { return ProtocolProfileConf{}, fmt.Errorf("profile: driver conflict: base %q vs overlay %q", base.Driver, overlay.Driver) } } if strings.TrimSpace(overlay.BaseURL) != "" { merged.BaseURL = overlay.BaseURL } if len(overlay.Operations) > 0 { if merged.Operations == nil { merged.Operations = make(map[string]string) } for k, v := range overlay.Operations { merged.Operations[k] = v } } if overlay.Auth.Header != "" { merged.Auth.Header = overlay.Auth.Header } if overlay.Auth.Scheme != "" { merged.Auth.Scheme = overlay.Auth.Scheme } if len(overlay.Capabilities) > 0 { merged.Capabilities = overlay.Capabilities } if len(overlay.ModelMapping) > 0 { if merged.ModelMapping == nil { merged.ModelMapping = make(map[string]string) } for k, v := range overlay.ModelMapping { merged.ModelMapping[k] = v } } if len(overlay.Extensions) > 0 { if merged.Extensions == nil { merged.Extensions = make(map[string]any) } for k, v := range overlay.Extensions { merged.Extensions[k] = v } } return merged, nil } // validateConcreteProfile validates a resolved concrete profile: driver is // known, operations are valid for the driver, URLs are well-formed, and // capabilities are non-empty. func validateConcreteProfile(id string, p ProtocolProfileConf) error { if _, ok := validProtocolDrivers[p.Driver]; !ok { return fmt.Errorf("profile %q: driver %q is not one of openai_chat/anthropic_messages/openai_responses", id, p.Driver) } if strings.TrimSpace(p.BaseURL) == "" { return fmt.Errorf("profile %q: base_url must not be empty", id) } base, err := url.Parse(p.BaseURL) if err != nil { return fmt.Errorf("profile %q: base_url %q is not a valid URL: %w", id, p.BaseURL, err) } if (base.Scheme != "http" && base.Scheme != "https") || base.Host == "" { return fmt.Errorf("profile %q: base_url %q must be an absolute http(s) URL", id, p.BaseURL) } if len(p.Operations) == 0 { return fmt.Errorf("profile %q: operations must not be empty", id) } if len(p.Capabilities) == 0 { return fmt.Errorf("profile %q: capabilities must not be empty", id) } capabilities := make(map[string]struct{}, len(p.Capabilities)) for _, capability := range p.Capabilities { capability = strings.TrimSpace(capability) if _, ok := validProtocolCapabilities[capability]; !ok { return fmt.Errorf("profile %q: capability %q is not recognized", id, capability) } if _, duplicate := capabilities[capability]; duplicate { return fmt.Errorf("profile %q: capability %q is duplicated", id, capability) } capabilities[capability] = struct{}{} } for op, path := range p.Operations { if err := validateProfileOperation(p.Driver, op); err != nil { return fmt.Errorf("profile %q: %w", id, err) } if err := validateProfileURL(p.BaseURL, path); err != nil { return fmt.Errorf("profile %q: operation %q: %w", id, op, err) } if required := operationCapability[ProtocolOperation(op)]; required != "" { if _, ok := capabilities[required]; !ok { return fmt.Errorf("profile %q: operation %q requires capability %q", id, op, required) } } } if p.Auth.Header == "" { return fmt.Errorf("profile %q: auth.header must not be empty", id) } if !validHTTPFieldName(p.Auth.Header) { return fmt.Errorf("profile %q: auth.header %q is not a valid HTTP header name", id, p.Auth.Header) } authHeader := strings.ToLower(p.Auth.Header) authScheme := strings.TrimSpace(p.Auth.Scheme) if authHeader == "authorization" && !strings.EqualFold(authScheme, "Bearer") { return fmt.Errorf("profile %q: Authorization auth requires Bearer scheme", id) } if authHeader != "authorization" && authScheme != "" { return fmt.Errorf("profile %q: auth header %q must use a raw credential without scheme", id, p.Auth.Header) } switch p.Driver { case ProtocolDriverOpenAIChat: if _, ok := capabilities["chat"]; !ok { return fmt.Errorf("profile %q: driver %q requires capability %q", id, p.Driver, "chat") } if _, ok := p.Operations[string(OperationChatCompletions)]; !ok { return fmt.Errorf("profile %q: driver %q requires operation %q", id, p.Driver, OperationChatCompletions) } case ProtocolDriverAnthropicMessages: if _, ok := capabilities["messages"]; !ok { return fmt.Errorf("profile %q: driver %q requires capability %q", id, p.Driver, "messages") } if _, ok := p.Operations[string(OperationMessages)]; !ok { return fmt.Errorf("profile %q: driver %q requires operation %q", id, p.Driver, OperationMessages) } case ProtocolDriverOpenAIResponses: if _, ok := capabilities["responses"]; !ok { return fmt.Errorf("profile %q: driver %q requires capability %q", id, p.Driver, "responses") } if _, ok := p.Operations[string(OperationResponses)]; !ok { return fmt.Errorf("profile %q: driver %q requires operation %q", id, p.Driver, OperationResponses) } } return nil } // validHTTPFieldName implements the RFC 9110 field-name token grammar. It is // deliberately byte-oriented because HTTP field names are ASCII-only tokens. func validHTTPFieldName(name string) bool { if name == "" { return false } for i := 0; i < len(name); i++ { c := name[i] if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') { continue } switch c { case '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~': continue default: return false } } return true } // ResolveOperationURL resolves an operation id to its full URL. Absolute // operation URLs are returned unchanged; relative paths are joined once to // the normalized base URL. Missing operations return a typed error. func (p ConcreteProtocolProfile) ResolveOperationURL(op string) (string, error) { if strings.TrimSpace(op) == "" { return "", fmt.Errorf("protocol profile %q: operation must not be empty", p.ID) } if err := validateProfileOperation(p.Driver, op); err != nil { return "", fmt.Errorf("protocol profile %q: %w", p.ID, err) } path, ok := p.Operations[op] if !ok { return "", fmt.Errorf("protocol profile %q: operation %q is not supported", p.ID, op) } return resolveOperationURL(p.BaseURL, path) } // resolveOperationURL joins a base URL and operation path exactly once. func resolveOperationURL(baseURL, opPath string) (string, error) { trimmed := strings.TrimSpace(opPath) if trimmed == "" { return "", fmt.Errorf("operation path must not be empty") } if strings.HasPrefix(trimmed, "http://") || strings.HasPrefix(trimmed, "https://") { return trimmed, nil } base, err := url.Parse(baseURL) if err != nil { return "", fmt.Errorf("base_url %q is not a valid URL: %w", baseURL, err) } operation, err := url.Parse(trimmed) if err != nil { return "", fmt.Errorf("operation path %q is not a valid URL path: %w", opPath, err) } basePath := strings.TrimRight(base.Path, "/") base.Path = basePath + operation.Path base.RawQuery = operation.RawQuery base.Fragment = operation.Fragment return base.String(), nil } // HasCapability reports whether the profile declares a capability. func (p ConcreteProtocolProfile) HasCapability(cap string) bool { for _, c := range p.Capabilities { if c == cap { return true } } return false } // MapModel returns the upstream model id for a selected/canonical model. An // absent mapping is an identity mapping. func (p ConcreteProtocolProfile) MapModel(model string) string { if mapped := strings.TrimSpace(p.ModelMapping[model]); mapped != "" { return mapped } return model } // Clone returns a deep copy of the concrete profile. func (p ConcreteProtocolProfile) Clone() ConcreteProtocolProfile { return ConcreteProtocolProfile{ ID: p.ID, ProtocolProfileConf: deepCopyProfileConf(p.ProtocolProfileConf), } }