package config import ( "encoding/hex" "encoding/json" "fmt" "os" "strings" "github.com/yeomyeonggeori/blueclaw/internal/capability" ) type RuntimeConfiguration struct { BaseURL string `json:"baseURL"` ServiceTokenPath string `json:"serviceTokenPath,omitempty"` Capabilities CapabilityConfiguration `json:"capabilities"` AgentProfiles []AgentProfileConfiguration `json:"agentProfiles"` LanguageModel LanguageModelConfiguration `json:"languageModel"` Database DatabaseConfiguration `json:"database"` Memory MemoryConfiguration `json:"memory"` Agent AgentConfiguration `json:"agent"` Connectors ConnectorConfiguration `json:"connectors"` Logging LoggingConfiguration `json:"logging"` Terminal TerminalConfiguration `json:"terminal"` Scheduler SchedulerConfiguration `json:"scheduler"` } type CapabilityConfiguration struct { Endpoint string `json:"endpoint"` UnixSocketPath string `json:"unixSocketPath"` TimeoutSecond int `json:"timeoutSecond"` ProtocolVersion string `json:"protocolVersion"` AggregateProtocolHash string `json:"aggregateProtocolHash"` ToolDescriptors []CapabilityToolDescriptor `json:"toolDescriptors,omitempty"` } // IsConfigured reports whether a capability service is reachable. Without one // Blueclaw runs standalone and the capability protocol identity is meaningless. func (configuration CapabilityConfiguration) IsConfigured() bool { return strings.TrimSpace(configuration.Endpoint) != "" || strings.TrimSpace(configuration.UnixSocketPath) != "" } type CapabilityToolDescriptor = capability.ToolDescriptor type CapabilityToolResultContract = capability.ToolResultContract type CapabilityResourceEffectContract = capability.ResourceEffectContract type CapabilityCompletionEvidence = capability.CompletionEvidence type CapabilityAvailability = capability.Availability type CapabilityIdempotency = capability.Idempotency type AgentProfileConfiguration struct { Name string `json:"name"` AllowedToolNames []string `json:"allowedToolNames"` } type EvidenceCondition = capability.EvidenceCondition type AgentConfiguration struct { Intake AgentIntakeConfiguration `json:"intake"` FailureRecovery AgentFailureRecovery `json:"failureRecovery"` GenerationOptions AgentGenerationOptions `json:"generationOptions,omitempty"` AllowAdminTaskDiagnostic bool `json:"allowAdminTaskDiagnostic"` Harness HarnessConfiguration `json:"harness,omitempty"` } type HarnessConfiguration struct { Name string `json:"name,omitempty"` AgentCommandPath string `json:"agentCommandPath,omitempty"` AgentArguments []string `json:"agentArguments,omitempty"` ToolCatalogURL string `json:"toolCatalogURL,omitempty"` } type AgentIntakeConfiguration struct { Enabled bool `json:"enabled"` ExecutionMode string `json:"executionMode"` } type AgentFailureRecovery struct { FailureDebtFinalizationGate bool `json:"failureDebtFinalizationGate"` AttemptFingerprint string `json:"attemptFingerprint"` RecoveryBudget AgentRecoveryBudget `json:"recoveryBudget"` } type AgentRecoveryBudget struct { CorrectedRetry int `json:"correctedRetry"` AlternateRoute int `json:"alternateRoute"` AdjacentTool int `json:"adjacentTool"` NoToolFallback int `json:"noToolFallback"` } type AgentGenerationOptions struct { Seed *int64 `json:"seed,omitempty"` Temperature *float64 `json:"temperature,omitempty"` } type LanguageModelConfiguration struct { Tiers map[string][]ModelEndpointConfiguration `json:"tiers,omitempty"` Embedding ModelEndpointConfiguration `json:"embedding,omitempty"` Decision ModelEndpointConfiguration `json:"decision,omitempty"` VisualReview ModelEndpointConfiguration `json:"visualReview,omitempty"` MaximumModelTier string `json:"maximumModelTier,omitempty"` MinimumModelTier string `json:"minimumModelTier,omitempty"` ContextWindowTokens int `json:"contextWindowTokens,omitempty"` Capability LanguageModelCapabilityConfiguration `json:"capability,omitempty"` } // ModelEndpointConfiguration is one rung: where to ask, what the model is // called there, and the file holding that endpoint's key. A tier is an ordered // list of these, tried in order, and nothing in this repository decides what // goes in one. type ModelEndpointConfiguration struct { Endpoint string `json:"endpoint"` Model string `json:"model"` APIKeyPath string `json:"apiKeyPath,omitempty"` APIKeyEnvironment string `json:"apiKeyEnvironment,omitempty"` ProviderOrder []string `json:"providerOrder,omitempty"` ProviderSort string `json:"providerSort,omitempty"` ReasoningEffort string `json:"reasoningEffort,omitempty"` RequesterEmailHeader string `json:"requesterEmailHeader,omitempty"` } type LanguageModelCapabilityConfiguration struct { Model string `json:"model"` MaxModel string `json:"maxModel"` XHighModel string `json:"xhighModel"` HighModel string `json:"highModel"` MediumModel string `json:"mediumModel"` LowModel string `json:"lowModel"` XLowModel string `json:"xlowModel"` DecisionModel string `json:"decisionModel"` ExecutionMode string `json:"executionMode"` } type DatabaseConfiguration struct { Driver string `json:"driver"` ConnectionString string `json:"connectionString"` MigrationDirectoryPath string `json:"migrationDirectoryPath"` MaxOpenConnections int `json:"maxOpenConnections"` } type MemoryConfiguration struct { AdminAssertionKeyPath string `json:"adminAssertionKeyPath"` EmbeddingModel string `json:"embeddingModel"` EmbeddingDimensions int `json:"embeddingDimensions"` EmbeddingExecutionMode string `json:"embeddingExecutionMode"` ExtractionDisabled bool `json:"extractionDisabled"` } type ConnectorConfiguration struct { Chatd ChatdConnectorConfiguration `json:"chatd"` } type ChatdConnectorConfiguration struct { Endpoint string `json:"endpoint,omitempty"` TimeoutSecond int `json:"timeoutSecond,omitempty"` EnabledPlatforms []string `json:"enabledPlatforms,omitempty"` } type LoggingConfiguration struct { DirectoryPath string `json:"directoryPath"` RetentionDays int `json:"retentionDays"` } type TerminalConfiguration struct { Mode string `json:"mode"` SandboxProvider string `json:"sandboxProvider"` WorkspaceRootPath string `json:"workspaceRootPath"` POSIXHelperPath string `json:"posixHelperPath"` TimeoutSecond int `json:"timeoutSecond"` OutputMaxBytes int `json:"outputMaxBytes"` SessionMaxCount int `json:"sessionMaxCount"` AllowNetwork bool `json:"allowNetwork"` AllowInteractiveShell bool `json:"allowInteractiveShell"` } type SchedulerConfiguration struct { RetentionCheckIntervalMinute int `json:"retentionCheckIntervalMinute"` SchedulePollIntervalSecond int `json:"taskSchedulePollIntervalSecond"` TaskRetentionDays int `json:"taskRetentionDays"` TasklessLLMCallRetentionDays int `json:"tasklessLLMCallRetentionDays"` } func LoadRuntimeConfiguration(path string) (RuntimeConfiguration, error) { document, errorValue := os.ReadFile(path) if errorValue != nil { return RuntimeConfiguration{}, errorValue } document, errorValue = expandEnvironmentReferences(document) if errorValue != nil { return RuntimeConfiguration{}, errorValue } var configuration RuntimeConfiguration errorValue = json.Unmarshal(document, &configuration) if errorValue != nil { return RuntimeConfiguration{}, errorValue } if errorValue := validateCapabilityProtocolIdentity(&configuration.Capabilities); errorValue != nil { return RuntimeConfiguration{}, errorValue } return configuration, nil } func capabilityRegistryIsUnstamped(configuration *CapabilityConfiguration) bool { return len(configuration.ToolDescriptors) == 0 && strings.TrimSpace(configuration.ProtocolVersion) == "" && strings.TrimSpace(configuration.AggregateProtocolHash) == "" } func validateCapabilityProtocolIdentity(configuration *CapabilityConfiguration) error { if !configuration.IsConfigured() { return nil } if capabilityRegistryIsUnstamped(configuration) { return nil } if configuration.ProtocolVersion == "" || configuration.ProtocolVersion != strings.TrimSpace(configuration.ProtocolVersion) { return fmt.Errorf("capabilities.protocolVersion must be a non-empty trimmed string") } if configuration.AggregateProtocolHash != strings.TrimSpace(configuration.AggregateProtocolHash) { return fmt.Errorf("capabilities.aggregateProtocolHash must be a 64-character lowercase hexadecimal hash") } if len(configuration.AggregateProtocolHash) != 64 { return fmt.Errorf("capabilities.aggregateProtocolHash must be a 64-character lowercase hexadecimal hash") } if _, errorValue := hex.DecodeString(configuration.AggregateProtocolHash); errorValue != nil || strings.ToLower(configuration.AggregateProtocolHash) != configuration.AggregateProtocolHash { return fmt.Errorf("capabilities.aggregateProtocolHash must be a 64-character lowercase hexadecimal hash") } return nil }