feat(config): unified [[llm.providers]] schema (#2134)#2148
Merged
Conversation
Add unified ProviderEntry struct that replaces CloudLlmConfig, OpenAiConfig, GeminiConfig, OllamaConfig, CompatibleConfig, and OrchestratorProviderConfig. Add LlmRoutingStrategy enum replacing the orchestrator/router split. Add CandleInlineConfig for use inside ProviderEntry. Add validate() and validate_pool() with B1/B2 blocker checks. No behavior change: existing types untouched, new types are additive.
…bility Introduce [[llm.providers]] array format alongside legacy [llm] fields. LlmConfig fields provider/base_url/model become Option<T> with effective_*() helpers that fall back to sensible defaults. Add migrate_llm_to_providers() to auto-convert all legacy formats (ollama, claude, openai, gemini, compatible, orchestrator, router) during --migrate-config. Update config/default.toml to use new [[llm.providers]] format. All 6397 tests pass.
…ders]] pool Add build_provider_from_entry() that constructs AnyProvider directly from a ProviderEntry without relying on legacy config sections. Update create_provider() to dispatch via the new pool when providers is non-empty, with fallback to the next pool entry on initialization failure.
…ew format - check_legacy_format() is now called during bootstrap; old-format configs fail with actionable error: "Run zeph --migrate-config" - effective_provider/base_url/model() now check providers[0] first, enabling new-format configs to work with all legacy call sites - Default impl added for ProviderEntry - --init wizard now generates [[llm.providers]] entries instead of legacy sections - Update tests to verify new-format output from build_config() Completes Phase 2 acceptance: starting with old-format config errors; --init generates new format. All 6397 tests pass.
… format C1: create_provider_from_pool() now dispatches on LlmRoutingStrategy — Ema/Thompson/Cascade initialize all pool providers and wrap in RouterProvider; Task strategy logs a warning and falls back to single provider. C2: resolve_secrets() now iterates self.llm.providers for compatible entries and fetches ZEPH_COMPATIBLE_<NAME>_API_KEY from vault. H1: migrate_llm_to_providers() now copies [llm.cloud.thinking] as TOML inline table into the migrated [[llm.providers]] entry. H2: migrate_llm_to_providers() now copies thinking_level, thinking_budget, include_thoughts from [llm.gemini] into the migrated entry. H3: check_legacy_format() no longer includes orchestrator in has_legacy detection — orchestrator is not yet migrated to [[llm.providers]] format. H4: LlmConfig.summary_provider changed from OrchestratorProviderConfig to ProviderEntry; build_summary_provider() uses build_provider_from_entry().
This was
linked to
issues
Mar 23, 2026
Remove CloudLlmConfig, OpenAiConfig, GeminiConfig, OllamaConfig, OrchestratorConfig, OrchestratorProviderConfig, CompatibleConfig from zeph-config. Remove ProviderKind::Orchestrator and ::Router variants. Remove legacy LlmConfig fields: provider, base_url, model, cloud, openai, gemini, ollama, compatible, orchestrator, vision_model. All bootstrap paths now use create_provider_from_pool() exclusively. Empty pool falls back to default Ollama on localhost. The --init wizard Orchestrator option now produces a two-entry [[llm.providers]] pool. check_legacy_format() simplified to always return Ok(()).
Restore 10 tests from bootstrap/tests.rs that were removed during the legacy struct cleanup but test live functionality unrelated to legacy config: create_mcp_manager_with_stdio_transport, create_mcp_manager_empty_servers, create_mcp_registry_when_semantic_disabled, managed_skills_dir_returns_skills_subdir, app_builder_managed_skills_dir_matches_free_fn, skill_paths_includes_managed_dir, skill_paths_does_not_duplicate_managed_dir, create_skill_matcher_when_semantic_disabled, appbuilder_qdrant_ops_invalid_url_returns_err, appbuilder_qdrant_ops_valid_url_succeeds. Test count: 5942 → 5952 (+10).
Add unified ProviderEntry struct that replaces CloudLlmConfig, OpenAiConfig, GeminiConfig, OllamaConfig, CompatibleConfig, and OrchestratorProviderConfig. Add LlmRoutingStrategy enum replacing the orchestrator/router split. Add CandleInlineConfig for use inside ProviderEntry. Add validate() and validate_pool() with B1/B2 blocker checks. No behavior change: existing types untouched, new types are additive.
…bility Introduce [[llm.providers]] array format alongside legacy [llm] fields. LlmConfig fields provider/base_url/model become Option<T> with effective_*() helpers that fall back to sensible defaults. Add migrate_llm_to_providers() to auto-convert all legacy formats (ollama, claude, openai, gemini, compatible, orchestrator, router) during --migrate-config. Update config/default.toml to use new [[llm.providers]] format. All 6397 tests pass.
…ders]] pool Add build_provider_from_entry() that constructs AnyProvider directly from a ProviderEntry without relying on legacy config sections. Update create_provider() to dispatch via the new pool when providers is non-empty, with fallback to the next pool entry on initialization failure.
…ew format - check_legacy_format() is now called during bootstrap; old-format configs fail with actionable error: "Run zeph --migrate-config" - effective_provider/base_url/model() now check providers[0] first, enabling new-format configs to work with all legacy call sites - Default impl added for ProviderEntry - --init wizard now generates [[llm.providers]] entries instead of legacy sections - Update tests to verify new-format output from build_config() Completes Phase 2 acceptance: starting with old-format config errors; --init generates new format. All 6397 tests pass.
… format C1: create_provider_from_pool() now dispatches on LlmRoutingStrategy — Ema/Thompson/Cascade initialize all pool providers and wrap in RouterProvider; Task strategy logs a warning and falls back to single provider. C2: resolve_secrets() now iterates self.llm.providers for compatible entries and fetches ZEPH_COMPATIBLE_<NAME>_API_KEY from vault. H1: migrate_llm_to_providers() now copies [llm.cloud.thinking] as TOML inline table into the migrated [[llm.providers]] entry. H2: migrate_llm_to_providers() now copies thinking_level, thinking_budget, include_thoughts from [llm.gemini] into the migrated entry. H3: check_legacy_format() no longer includes orchestrator in has_legacy detection — orchestrator is not yet migrated to [[llm.providers]] format. H4: LlmConfig.summary_provider changed from OrchestratorProviderConfig to ProviderEntry; build_summary_provider() uses build_provider_from_entry().
Remove CloudLlmConfig, OpenAiConfig, GeminiConfig, OllamaConfig, OrchestratorConfig, OrchestratorProviderConfig, CompatibleConfig from zeph-config. Remove ProviderKind::Orchestrator and ::Router variants. Remove legacy LlmConfig fields: provider, base_url, model, cloud, openai, gemini, ollama, compatible, orchestrator, vision_model. All bootstrap paths now use create_provider_from_pool() exclusively. Empty pool falls back to default Ollama on localhost. The --init wizard Orchestrator option now produces a two-entry [[llm.providers]] pool. check_legacy_format() simplified to always return Ok(()).
Restore 10 tests from bootstrap/tests.rs that were removed during the legacy struct cleanup but test live functionality unrelated to legacy config: create_mcp_manager_with_stdio_transport, create_mcp_manager_empty_servers, create_mcp_registry_when_semantic_disabled, managed_skills_dir_returns_skills_subdir, app_builder_managed_skills_dir_matches_free_fn, skill_paths_includes_managed_dir, skill_paths_does_not_duplicate_managed_dir, create_skill_matcher_when_semantic_disabled, appbuilder_qdrant_ops_invalid_url_returns_err, appbuilder_qdrant_ops_valid_url_succeeds. Test count: 5942 → 5952 (+10).
80733e0 to
7243710
Compare
…mpty pool When ZEPH_LLM_PROVIDER, ZEPH_LLM_BASE_URL, or ZEPH_LLM_MODEL are set on a config with no [[llm.providers]] entries (e.g. default config), the overrides were silently dropped because first_mut() returned None on the empty Vec. Fix: create a default ProviderEntry before applying env overrides when the pool is empty and any LLM env override is present. Fixes CI failure in config_defaults_and_env_overrides (tests/integration.rs:331).
Resolve conflict in env.rs: keep fix for env var override when providers pool is empty. Remote adds response verifier and sanitizer changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2134, #2135, #2136, #2137, #2138, #2139
Summary
Replaces 6 separate LLM provider structs and dual orchestrator/router concepts with a single
[[llm.providers]]array. Each provider is defined exactly once; routing is declared viaroutingfield.New config types:
ProviderEntryflat-union struct replacesCloudLlmConfig,OpenAiConfig,GeminiConfig,OllamaConfig,CompatibleConfig,OrchestratorProviderConfigLlmRoutingStrategyenum (None/Ema/Thompson/Cascade/Task) replacesprovider = "router"/provider = "orchestrator"Implementation:
create_provider_from_pool()dispatches on routing strategy, buildingRouterProviderfor Ema/Thompson/Cascaderesolve_secrets()updated for compatible providers in newproviderspoolmigrate_llm_to_providers()converts all 6 legacy formats; old-format configs produce startup error with--migrate-confighint--initwizard generates new formatbook/src/)Tests: 6406/6406 pass (+9 net vs main baseline of 6397)
Breaking Change
Old-format configs (
[llm.cloud],[llm.openai],[llm.orchestrator],[llm.router]) produce a startup error:All removed types documented in CHANGELOG.md.
Before / After
Test Plan
cargo +nightly fmt --check— PASScargo clippy --workspace --features full -- -D warnings— PASScargo nextest run --workspace --features full --lib --bins— 6406/6406 PASS