feat: implement configuration management commands#1623
Conversation
| ConfigOption::Model => { | ||
| if let Some(model_id) = select_model(api).await? { | ||
| api.set_operating_model(model_id.clone()).await?; | ||
| display_success("Model set", model_id.as_str()); | ||
| } | ||
| } |
There was a problem hiding this comment.
The PR description specifies that when provider is not set, model selection should open provider selection first. This implementation logic is missing in the handle_interactive_set function.
Before selecting a model in the ConfigOption::Model case, the code should check if a provider is set and if not, prompt the user to select a provider first. This would ensure the expected behavior described in the requirements.
| ConfigOption::Model => { | |
| if let Some(model_id) = select_model(api).await? { | |
| api.set_operating_model(model_id.clone()).await?; | |
| display_success("Model set", model_id.as_str()); | |
| } | |
| } | |
| ConfigOption::Model => { | |
| // Check if provider is set, if not, prompt to select provider first | |
| if api.get_operating_provider().await?.is_none() { | |
| println!("A provider must be selected before setting a model."); | |
| if let Some(provider_id) = select_provider(api).await? { | |
| api.set_operating_provider(provider_id.clone()).await?; | |
| display_success("Provider set", provider_id.as_str()); | |
| } else { | |
| return Ok(()); // User cancelled provider selection | |
| } | |
| } | |
| // Now proceed with model selection | |
| if let Some(model_id) = select_model(api).await? { | |
| api.set_operating_model(model_id.clone()).await?; | |
| display_success("Model set", model_id.as_str()); | |
| } | |
| } |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
| local provider_name="${selected_provider%% *}" | ||
| echo "\033[36m⏺\033[0m \033[90m[$(date '+%H:%M:%S')] Setting provider to: ${provider_name}\033[0m" | ||
| $_FORGE_BIN config set --provider "$provider_name" |
There was a problem hiding this comment.
The provider name extraction logic may not handle provider display formats correctly. The current implementation uses ${selected_provider%% *} which only captures text before the first space, but provider displays include domain information in brackets (e.g., "OpenAI [api.openai.com]").
A more robust approach would be to extract just the provider ID before any brackets or spaces. Consider using a regex pattern or a more specific shell extraction:
# Extract provider name before any space or opening bracket
local provider_name=$(echo "$selected_provider" | sed -E 's/([^ ]+).*/\1/')This ensures the correct provider ID is passed to the config command regardless of the display format.
| local provider_name="${selected_provider%% *}" | |
| echo "\033[36m⏺\033[0m \033[90m[$(date '+%H:%M:%S')] Setting provider to: ${provider_name}\033[0m" | |
| $_FORGE_BIN config set --provider "$provider_name" | |
| local provider_name=$(echo "$selected_provider" | sed -E 's/([^ ]+).*/\1/') | |
| echo "\033[36m⏺\033[0m \033[90m[$(date '+%H:%M:%S')] Setting provider to: ${provider_name}\033[0m" | |
| $_FORGE_BIN config set --provider "$provider_name" |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
| async fn handle_non_interactive_set<A: API>(api: &A, args: ConfigSetArgs) -> ConfigResult<()> { | ||
| // Set provider if specified | ||
| if let Some(provider_str) = args.provider { | ||
| let provider_id = validate_provider(api, &provider_str).await?; | ||
| api.set_provider(provider_id).await?; | ||
| display_success("Provider set", &provider_str); | ||
| } | ||
|
|
||
| // Set agent if specified | ||
| if let Some(agent_str) = args.agent { | ||
| let agent_id = validate_agent(api, &agent_str).await?; | ||
| api.set_operating_agent(agent_id.clone()).await?; | ||
| display_success("Agent set", agent_id.as_str()); | ||
| } | ||
|
|
||
| // Set model if specified | ||
| if let Some(model_str) = args.model { | ||
| let model_id = validate_model(api, &model_str).await?; | ||
| api.set_operating_model(model_id.clone()).await?; | ||
| display_success("Model set", model_id.as_str()); | ||
| } | ||
|
|
||
| Ok(()) |
There was a problem hiding this comment.
The order of operations in handle_non_interactive_set could lead to validation failures when setting both provider and model. Currently, the function validates models against the current provider before changing providers. Consider reordering the implementation to:
- Set provider first (if specified)
- Then validate and set model (if specified)
- Finally set agent (if specified)
This ensures that model validation happens against the newly selected provider's model list, preventing errors when a user wants to switch to a model that's only available on their newly selected provider.
| async fn handle_non_interactive_set<A: API>(api: &A, args: ConfigSetArgs) -> ConfigResult<()> { | |
| // Set provider if specified | |
| if let Some(provider_str) = args.provider { | |
| let provider_id = validate_provider(api, &provider_str).await?; | |
| api.set_provider(provider_id).await?; | |
| display_success("Provider set", &provider_str); | |
| } | |
| // Set agent if specified | |
| if let Some(agent_str) = args.agent { | |
| let agent_id = validate_agent(api, &agent_str).await?; | |
| api.set_operating_agent(agent_id.clone()).await?; | |
| display_success("Agent set", agent_id.as_str()); | |
| } | |
| // Set model if specified | |
| if let Some(model_str) = args.model { | |
| let model_id = validate_model(api, &model_str).await?; | |
| api.set_operating_model(model_id.clone()).await?; | |
| display_success("Model set", model_id.as_str()); | |
| } | |
| Ok(()) | |
| async fn handle_non_interactive_set<A: API>(api: &A, args: ConfigSetArgs) -> ConfigResult<()> { | |
| // Set provider if specified | |
| if let Some(provider_str) = args.provider { | |
| let provider_id = validate_provider(api, &provider_str).await?; | |
| api.set_provider(provider_id).await?; | |
| display_success("Provider set", &provider_str); | |
| } | |
| // Set model if specified | |
| if let Some(model_str) = args.model { | |
| let model_id = validate_model(api, &model_str).await?; | |
| api.set_operating_model(model_id.clone()).await?; | |
| display_success("Model set", model_id.as_str()); | |
| } | |
| // Set agent if specified | |
| if let Some(agent_str) = args.agent { | |
| let agent_id = validate_agent(api, &agent_str).await?; | |
| api.set_operating_agent(agent_id.clone()).await?; | |
| display_success("Agent set", agent_id.as_str()); | |
| } | |
| Ok(()) | |
| } | |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
| command_output=$($_FORGE_BIN show-agents 2>/dev/null) | ||
| # Create a list of available commands including providers and models | ||
| local commands="providers\nmodels\n" | ||
| commands+="$($_FORGE_BIN show-agents 2>/dev/null)" |
There was a problem hiding this comment.
| commands+="$($_FORGE_BIN show-agents 2>/dev/null)" | |
| commands+="$($_FORGE_BIN show-commands 2>/dev/null)" |
List all the commands with descriptions including, info, reset, provider and model.
| std::process::exit(1); | ||
| } | ||
| return Ok(()); | ||
| } |
| use crate::cli::{ConfigCommand, ConfigGetArgs, ConfigSetArgs}; | ||
|
|
||
| /// Handle config command | ||
| pub async fn handle_config_command<A: API>(api: &A, command: ConfigCommand) -> Result<()> { |
There was a problem hiding this comment.
convert to a struct ConfigManager that has access to Api. So we don't need to pipe api thru all the functions.
| let provider_id = ProviderId::from_str(provider_str).with_context(|| { | ||
| format!( | ||
| "Invalid provider: '{}'. Valid providers are: {}", | ||
| provider_str, | ||
| get_valid_provider_names().join(", ") | ||
| ) | ||
| })?; |
There was a problem hiding this comment.
The error handling here could be improved. When ProviderId::from_str() fails, the custom error message is added as context but then immediately converted to a ConfigError::Api via the ? operator. This means the specific validation error message won't be presented to users as intended.
Consider handling the error directly and returning a more specific error type:
let provider_id = match ProviderId::from_str(provider_str) {
Ok(id) => id,
Err(_) => {
return Err(ConfigError::InvalidField {
field: provider_str.to_string()
});
}
};This would provide users with clearer feedback about invalid provider names.
| let provider_id = ProviderId::from_str(provider_str).with_context(|| { | |
| format!( | |
| "Invalid provider: '{}'. Valid providers are: {}", | |
| provider_str, | |
| get_valid_provider_names().join(", ") | |
| ) | |
| })?; | |
| let provider_id = match ProviderId::from_str(provider_str) { | |
| Ok(id) => id, | |
| Err(_) => { | |
| return Err(ConfigError::InvalidField { | |
| field: format!( | |
| "Invalid provider: '{}'. Valid providers are: {}", | |
| provider_str, | |
| get_valid_provider_names().join(", ") | |
| ), | |
| }); | |
| } | |
| }; |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
- Added a new error module with ConfigError enum to handle various configuration-related errors. - Updated handler functions to return ConfigResult instead of Result, improving error handling. - Refactored validation functions to use ConfigError for specific error cases (e.g., agent not found, model not found, provider not available). - Integrated the new error handling into the config command logic, ensuring clearer error messages and better user feedback.
…rs with display wrappers
… interactive selection
…r to model module
…idate command existence
…instantiation in UI
6e4e637 to
b765bbc
Compare
|
Looks like there are a few issues preventing this PR from being merged!
If you'd like me to help, just leave a comment, like Feel free to include any additional details that might help me get this PR into a better state. You can manage your notification settings |
| async fn handle_set(&self, args: ConfigSetArgs) -> ConfigResult<()> { | ||
| if args.has_any_field() { | ||
| // Non-interactive mode: set specified values | ||
| self.handle_non_interactive_set(args).await | ||
| } else { | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
The handle_set method returns Ok(()) when no fields are specified, but doesn't implement any interactive mode functionality despite the comment suggesting this is "interactive mode". Consider either:
- Implementing the interactive selection UI for this case, or
- Returning an error indicating that at least one configuration field must be specified
This creates a potential user experience gap where a user running config set with no arguments would receive no feedback or action.
| async fn handle_set(&self, args: ConfigSetArgs) -> ConfigResult<()> { | |
| if args.has_any_field() { | |
| // Non-interactive mode: set specified values | |
| self.handle_non_interactive_set(args).await | |
| } else { | |
| Ok(()) | |
| } | |
| async fn handle_set(&self, args: ConfigSetArgs) -> ConfigResult<()> { | |
| if args.has_any_field() { | |
| // Non-interactive mode: set specified values | |
| self.handle_non_interactive_set(args).await | |
| } else { | |
| // Return an error when no fields are specified | |
| Err(ConfigError::InvalidArgument( | |
| "At least one configuration field must be specified".to_string(), | |
| )) | |
| } | |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
escape should go to previous selection should not exitIf provider is not set model selection should open provider selection:modelif provider is not set show provider list first
:providerenter will open list of available providers:retryenter will retry last message to LLM