feat: externalize LLM providers and tool enable flags via config

Adds ProviderConfig + providers list and per-tool enabled flags to
CorrexConfig, and rewires apps/server/Main to build the provider list
from config instead of the hardcoded buildLlamaProvider() factory. Env
vars (CORREX_MODEL_ID, CORREX_MODEL_PATH, CORREX_LLAMA_URL) remain a
fallback only when no providers are declared in config.

Completes slice A of the config layer alongside commit 0834c70 which
already shipped the TOML parser upgrade and sample config — those
parser changes referenced these types but were committed prematurely
in isolation; this commit makes HEAD buildable.
This commit is contained in:
2026-05-30 01:15:55 +04:00
parent 0834c705fd
commit 84a7568e15
2 changed files with 89 additions and 14 deletions
@@ -6,13 +6,17 @@ import com.correx.apps.server.registry.ProviderRegistry
import com.correx.core.approvals.domain.DefaultApprovalEngine import com.correx.core.approvals.domain.DefaultApprovalEngine
import com.correx.core.artifactstore.ArtifactStore import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.config.ConfigLoader import com.correx.core.config.ConfigLoader
import com.correx.core.config.CorrexConfig
import com.correx.core.config.ProviderConfig
import com.correx.core.context.builder.DefaultContextPackBuilder import com.correx.core.context.builder.DefaultContextPackBuilder
import com.correx.core.context.compression.DefaultContextCompressor import com.correx.core.context.compression.DefaultContextCompressor
import com.correx.core.events.EventDispatcher import com.correx.core.events.EventDispatcher
import com.correx.core.events.stores.EventStore import com.correx.core.events.stores.EventStore
import com.correx.core.inference.CapabilityScore
import com.correx.core.inference.DefaultInferenceRouter import com.correx.core.inference.DefaultInferenceRouter
import com.correx.core.inference.InferenceProjector import com.correx.core.inference.InferenceProjector
import com.correx.core.inference.InferenceRepository import com.correx.core.inference.InferenceRepository
import com.correx.core.inference.ModelCapability
import com.correx.core.kernel.orchestration.DefaultOrchestrationReducer import com.correx.core.kernel.orchestration.DefaultOrchestrationReducer
import com.correx.core.kernel.orchestration.DefaultSessionOrchestrator import com.correx.core.kernel.orchestration.DefaultSessionOrchestrator
import com.correx.core.kernel.orchestration.OrchestrationConfig import com.correx.core.kernel.orchestration.OrchestrationConfig
@@ -37,6 +41,7 @@ import com.correx.infrastructure.inference.DefaultProviderRegistry
import com.correx.infrastructure.inference.FirstAvailableRoutingStrategy import com.correx.infrastructure.inference.FirstAvailableRoutingStrategy
import com.correx.infrastructure.inference.llama.cpp.LlamaCppInferenceProvider import com.correx.infrastructure.inference.llama.cpp.LlamaCppInferenceProvider
import com.correx.infrastructure.tools.FileEditConfig import com.correx.infrastructure.tools.FileEditConfig
import com.correx.infrastructure.tools.FileReadConfig
import com.correx.infrastructure.tools.FileWriteConfig import com.correx.infrastructure.tools.FileWriteConfig
import com.correx.infrastructure.tools.ShellConfig import com.correx.infrastructure.tools.ShellConfig
import com.correx.infrastructure.tools.ToolConfig import com.correx.infrastructure.tools.ToolConfig
@@ -56,17 +61,19 @@ fun main() {
logStoresInfo(eventStore, artifactStore) logStoresInfo(eventStore, artifactStore)
val llamaProvider = buildLlamaProvider() val correxConfig = ConfigLoader.load()
val infraRegistry = InfrastructureModule.createProviderRegistry(listOf(llamaProvider)) val providers = buildProviders(correxConfig)
val modelId = System.getenv("CORREX_MODEL_ID") ?: "default" require(providers.isNotEmpty()) { "At least one provider must be configured" }
val modelPath = System.getenv("CORREX_MODEL_PATH") ?: "(not set)" val infraRegistry = InfrastructureModule.createProviderRegistry(providers)
val llamaUrl = System.getenv("CORREX_LLAMA_URL") ?: "http://127.0.0.1:10000" val llamaProvider = providers.first()
val modelId = llamaProvider.id.value
val modelPath = "(from config)"
val llamaUrl = "(from config)"
logModelInfo(modelId, modelPath, llamaUrl, infraRegistry) logModelInfo(modelId, modelPath, llamaUrl, infraRegistry)
val repositories = buildRepositories(eventStore) val repositories = buildRepositories(eventStore)
val approvalEngine = DefaultApprovalEngine() val approvalEngine = DefaultApprovalEngine()
val correxConfig = ConfigLoader.load()
val toolsConfig = correxConfig.tools val toolsConfig = correxConfig.tools
val sandboxRoot = System.getenv("CORREX_SANDBOX_ROOT") val sandboxRoot = System.getenv("CORREX_SANDBOX_ROOT")
?.let { Path.of(it) } ?.let { Path.of(it) }
@@ -83,6 +90,7 @@ fun main() {
sandboxRoot, sandboxRoot,
workingDir, workingDir,
shellAllowedExecutables, shellAllowedExecutables,
toolsConfig,
), ),
) )
val toolExecutor = InfrastructureModule.createToolExecutor( val toolExecutor = InfrastructureModule.createToolExecutor(
@@ -180,11 +188,58 @@ private fun logStoresInfo(
log.info(" artifact store: {}", artifactStore::class.simpleName) log.info(" artifact store: {}", artifactStore::class.simpleName)
} }
private fun buildLlamaProvider(): LlamaCppInferenceProvider = InfrastructureModule.createLlamaCppProvider( private fun buildProviders(config: CorrexConfig): List<LlamaCppInferenceProvider> {
modelId = System.getenv("CORREX_MODEL_ID") ?: "default", return if (config.providers.isNotEmpty()) {
modelPath = System.getenv("CORREX_MODEL_PATH") ?: "", log.info("Loading {} provider(s) from config", config.providers.size)
baseUrl = System.getenv("CORREX_LLAMA_URL") ?: "http://127.0.0.1:10000", config.providers.mapNotNull { providerConfig ->
) when (providerConfig.type.lowercase()) {
"llamacpp" -> buildProviderFromConfig(providerConfig)
else -> {
log.error("Unknown provider type: {}", providerConfig.type)
null
}
}
}
} else {
log.info("No providers in config; using env var fallback")
listOf(
InfrastructureModule.createLlamaCppProvider(
modelId = System.getenv("CORREX_MODEL_ID") ?: "default",
modelPath = System.getenv("CORREX_MODEL_PATH") ?: "",
baseUrl = System.getenv("CORREX_LLAMA_URL") ?: "http://127.0.0.1:10000",
),
)
}
}
private fun buildProviderFromConfig(config: ProviderConfig): LlamaCppInferenceProvider {
val capabilities = parseCapabilitiesFromConfig(config.capabilities)
return InfrastructureModule.createLlamaCppProvider(
modelId = config.modelId,
modelPath = config.modelPath,
baseUrl = config.url,
capabilities = capabilities,
)
}
private fun parseCapabilitiesFromConfig(capsMap: Map<String, Double>): Set<CapabilityScore> {
val result = mutableSetOf<CapabilityScore>()
capsMap.forEach { (capName, score) ->
val capability = when (capName.lowercase()) {
"general" -> ModelCapability.General
"coding" -> ModelCapability.Coding
"reasoning" -> ModelCapability.Reasoning
"summarization" -> ModelCapability.Summarization
"toolcalling" -> ModelCapability.ToolCalling
else -> {
log.warn("Unknown capability name: {}", capName)
return@forEach
}
}
result.add(CapabilityScore(capability, score))
}
return result
}
private fun buildRepositories( private fun buildRepositories(
eventStore: EventStore, eventStore: EventStore,
@@ -206,22 +261,27 @@ private fun buildToolConfig(
sandboxRoot: Path, sandboxRoot: Path,
workingDir: Path, workingDir: Path,
shellAllowedExecutables: Set<String>, shellAllowedExecutables: Set<String>,
toolsConfig: com.correx.core.config.ToolsConfig,
): ToolConfig = ToolConfig( ): ToolConfig = ToolConfig(
shell = ShellConfig( shell = ShellConfig(
enabled = true, enabled = toolsConfig.shellEnabled,
allowedExecutables = shellAllowedExecutables, allowedExecutables = shellAllowedExecutables,
workingDir = workingDir, workingDir = workingDir,
), ),
fileRead = FileReadConfig(
enabled = toolsConfig.fileReadEnabled,
allowedPaths = setOf(sandboxRoot, workingDir),
),
fileWrite = FileWriteConfig( fileWrite = FileWriteConfig(
allowedPaths = setOf(sandboxRoot, workingDir), allowedPaths = setOf(sandboxRoot, workingDir),
enabled = true, enabled = toolsConfig.fileWriteEnabled,
artifactStore = artifactStore, artifactStore = artifactStore,
materializingWriter = DefaultMaterializingArtifactWriter(), materializingWriter = DefaultMaterializingArtifactWriter(),
sandboxRoot = sandboxRoot, sandboxRoot = sandboxRoot,
workingDir = workingDir, workingDir = workingDir,
), ),
fileEdit = FileEditConfig( fileEdit = FileEditConfig(
enabled = true, enabled = toolsConfig.fileEditEnabled,
allowedPaths = setOf(sandboxRoot, workingDir), allowedPaths = setOf(sandboxRoot, workingDir),
workingDir = workingDir, workingDir = workingDir,
), ),
@@ -9,6 +9,7 @@ data class CorrexConfig(
val cli: CliConfig = CliConfig(), val cli: CliConfig = CliConfig(),
val approval: ApprovalConfig = ApprovalConfig(), val approval: ApprovalConfig = ApprovalConfig(),
val tools: ToolsConfig = ToolsConfig(), val tools: ToolsConfig = ToolsConfig(),
val providers: List<ProviderConfig> = emptyList(),
) )
@Serializable @Serializable
@@ -39,4 +40,18 @@ data class ToolsConfig(
val workingDir: String = "", val workingDir: String = "",
val shellAllowedExecutables: List<String> = emptyList(), val shellAllowedExecutables: List<String> = emptyList(),
val defaultSystemPromptPath: String = "~/.config/correx/prompts/default_system.md", val defaultSystemPromptPath: String = "~/.config/correx/prompts/default_system.md",
val shellEnabled: Boolean = true,
val fileReadEnabled: Boolean = true,
val fileWriteEnabled: Boolean = true,
val fileEditEnabled: Boolean = true,
)
@Serializable
data class ProviderConfig(
val id: String,
val type: String,
val modelId: String,
val modelPath: String = "",
val url: String = "http://127.0.0.1:10000",
val capabilities: Map<String, Double> = emptyMap(),
) )