feat(config): collapse ProviderKind/ApiProvider dual enums behind Provider trait#2479
feat(config): collapse ProviderKind/ApiProvider dual enums behind Provider trait#2479sximelon wants to merge 1 commit into
Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
4a216a3 to
f0e7882
Compare
2ec2600 to
238a82c
Compare
|
Reviewed for the v0.8.50 triage branch. I’m keeping this open rather than harvesting it today. The provider-trait direction fits CodeWhale, but the current branch still has release-risk blockers from the code review: Once those DeepSeek-CN compatibility paths are fixed with focused tests, this becomes a much better candidate for a feature branch or the next minor slice. DeepSeek support itself remains first-class; the blocker here is the migration/regression risk, not the direction. |
|
Thanks for the review. I've addressed the blockers:
|
7b33c13 to
3840f9b
Compare
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
7a031d3 to
b37c0a9
Compare
|
Thanks @sximelon. I reviewed this for the v0.9.0 stewardship branch. The provider trait/registry shape is a useful direction, so if we harvest that architecture we should credit #2479. I am not going to merge this as-is into the release train. The branch is green, but it is still additive rather than collapsing the duplicated provider surfaces required by #2075: A safer harvest would take the trait shape, |
Add a metadata-only provider registry foundation from #2479. The registry exposes canonical lookup, alias-aware resolution, defaults, config table keys, and API-key env candidates without changing runtime routing or activating fallback providers. Co-authored-by: sximelon <62371427+sximelon@users.noreply.github.com>
|
Thanks again @sximelon. I harvested the release-safe v0.9 provider-foundation slice into #2820, now merged into What landed is intentionally metadata-only: Your credit is preserved in the commit co-author trailer, #2820 body, changelog, and README v0.9 track credits. I am leaving this PR open rather than closing it because the broader ProviderKind/ApiProvider collapse from #2075 is still a larger design problem; this landed the safe foundation without pretending the whole duplicated provider surface is solved. Verification on the harvest included:
And #2762 is green again after the merge. Appreciate you pushing the provider unification direction forward. |
Summary
Introduce a
Providertrait and 18 concrete provider structs in a newcrates/config/src/provider.rsmodule, replacing the scattered match-arm duplication that every provider addition currently requires.What's in this PR
Providertrait —id,display_name,default_base_url,env_vars,default_model,wire,provider_config_key,thinking_supported,cache_telemetry_supported,apply_reasoning_effort.PROVIDER_REGISTRY—lookup_provider(),resolve_provider()(with alias normalization),default_provider(),all_providers().ProviderKind::provider()— bridge method that resolves aProviderKindenum variant to its&'static dyn Providertrait object via the registry.What's NOT in this PR (intentionally deferred)
This is the foundation. The existing
ProviderKindenum,ApiProviderenum, and all their match arms inlib.rs,tui/src/config.rs,cli/src/lib.rs, andtui/src/client.rsare not yet migrated.They remain the source of truth for the runtime.
The trait and registry are additive — callers can start migrating piecemeal in follow-up PRs.
Closes #2075
Testing
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --all-featurescargo test --workspace --all-featuresChecklist
Greptile Summary
This PR centralizes provider metadata into a new
Providertrait with 18 concrete zero-sized struct implementations incrates/config/src/provider.rs, and adds aProviderKind::provider()bridge method so the existing enums can delegate to trait objects without a full enum migration.provider.rsmodule: definesWireFormat, theProvidertrait (id, display name, base URL, env vars, default model, wire format, config key, reasoning-effort application), 18 concrete structs, aOnceLock-backedPROVIDER_REGISTRY, and public helpersresolve_provider,lookup_provider,all_providers, anddefault_provider.ProviderKind::provider()bridge (lib.rs): resolves the enum variant to a&'static dyn Providerby callingresolve_provider(self.as_str()), falling back todefault_provider()(DeepSeek) if the ID is unrecognised.Confidence Score: 4/5
Mostly safe to merge; the new trait layer is correct for all current providers, but the SiliconflowCN config-routing disconnect from prior rounds remains unresolved.
The new trait and registry are internally consistent for all 18 providers. The unresolved SiliconflowCN mismatch means callers using provider_config_key() for TOML lookups will read the wrong config section for that provider. New findings here are minor cleanup items.
crates/config/src/lib.rs and crates/config/src/provider.rs — the SiliconflowCN provider_config_key vs for_provider mismatch needs resolution before the new trait API is used for config lookups.
Important Files Changed
Providertrait and 18 concrete zero-sized struct implementations, plus a globalOnceLock-backed registry withresolve_providerandall_providershelpers. One dead match arm ("siliconflow-CN"after lowercase normalization) is the only new finding here.pub mod providerand aProviderKind::provider()bridge method. Theunwrap_or_else(default_provider)fallback creates a silent-failure risk for future variants not registered inresolve_provider.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[ProviderKind variant] -->|".as_str()"| B["&'static str ID"] B -->|"resolve_provider(id)"| C{ID in registry?} C -->|Yes| D["&'static dyn Provider"] C -->|No| E["default_provider() → &Deepseek"] D --> F["provider_config_key()"] D --> G["default_base_url()"] D --> H["env_vars()"] D --> I["apply_reasoning_effort()"] F -->|"used by TUI"| J["[providers.key] TOML lookup"] J -.->|"SiliconflowCN: key=siliconflow_cn but reads self.siliconflow"| K["⚠ Config mismatch (prior rounds)"]Comments Outside Diff (6)
crates/config/src/provider.rs, line 898-921 (link)The canonical-alias table now lives in
ProviderKind::parse, the serde#[serde(alias = …)]attributes on each enum variant, andresolve_providerhere — three independent copies of the same information. If a new alias is added in one place it must be remembered in the other two. A single source of truth (e.g., aconst ALIASES: &[(&str, &str)]array consumed by all three sites) would prevent the lists from drifting.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
crates/cli/src/lib.rs, line 1473-1493 (link)ProviderKind::DeepseekCNmissing from TUI provider whitelistBefore this PR,
"deepseek-cn"parsed toProviderKind::Deepseek, which passes thematches!guard. After this PR, it parses to the newProviderKind::DeepseekCNvariant, which is absent from the guard. Any user whose config file containsprovider = "deepseek-cn"— or who passes--provider deepseek-cn— will hit the bail and be completely unable to launch the interactive TUI after upgrading.crates/cli/src/lib.rs, line 788-790 (link)auth login deepseek-cnsilently fails to refresh the rootapi_keyafter this PR. Before the PR"deepseek-cn"parsed toProviderKind::Deepseek, so this branch ran and updatedstore.config.api_key. Now it parses toProviderKind::DeepseekCNand misses the branch entirely — the new credential is written only toproviders.deepseek_cn.api_key. Because the TUI'sdeepseek_api_keychecksself.api_key(root) before the provider-scoped entry, any pre-existing root key silently shadows the newly saved one, making the login effectively a no-op for those users.crates/cli/src/lib.rs, line 804-809 (link)auth logout deepseek-cnno longer clears the rootapi_keyafter this PR's enum split. If a user previously ranauth login deepseek(which setsapi_keyat the root), switching todeepseek-cnand later logging out leaves the root key intact. The TUI checks the rootapi_keyfirst forDeepseekCN, so the credential persists after logout.crates/tui/src/config.rs, line 4863-4869 (link)provider_config_keyreturns wrong TOML section forSiliconflowCnafter delegationApiProvider::SiliconflowCn.provider().provider_config_key()now resolves throughProviderKind::SiliconflowCN → &provider::Siliconflow → "siliconflow". The old explicit arm returned"siliconflow-CN". Becausesave_provider_auth_mode_forcalls this function to determine the TOML key it writes to, any auth-mode entry it creates forSiliconflowCnwill now land in[providers.siliconflow]instead of[providers.siliconflow-CN], diverging from any existing config that users saved under the old key name.crates/config/src/lib.rs, line 302-305 (link)siliconflow_cnconfig field is never readProvidersTomlgains a newpub siliconflow_cn: ProviderConfigTomlfield that serde will happily populate from[providers.siliconflow_cn], butfor_provider(ProviderKind::SiliconflowCN)still returns&self.siliconflow. Any user who follows the new docs row — which lists[providers.siliconflow_cn]as the config table forsiliconflow-cn— and setsapi_key,base_url, ormodelthere will have those values silently discarded at runtime; the CLI reads from[providers.siliconflow]instead. Either changefor_provider(SiliconflowCN)to return&self.siliconflow_cn(making the field live and the docs accurate) or remove the dead field and update the docs to show[providers.siliconflow].Reviews (13): Last reviewed commit: "feat(config): collapse ProviderKind/ApiP..." | Re-trigger Greptile