Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: implement configuration management commands#1623

Merged
tusharmath merged 27 commits into
mainfrom
feat/config-selection
Oct 3, 2025
Merged

feat: implement configuration management commands#1623
tusharmath merged 27 commits into
mainfrom
feat/config-selection

Conversation

@amitksingh1490

@amitksingh1490 amitksingh1490 commented Oct 1, 2025

Copy link
Copy Markdown
Contributor
  • escape should go to previous selection should not exit
  • model switching should be reused from forge_main
  • agent selection should be reused from forge_main
  • If provider is not set model selection should open provider selection
  • Add get providers command
  • Add get models command
  • Drop inquire
  • Invalid command should throw an error

:model
if provider is not set show provider list first
:provider enter will open list of available providers
:retry enter will retry last message to LLM

@github-actions github-actions Bot added the type: feature Brand new functionality, features, pages, workflows, endpoints, etc. label Oct 1, 2025
@amitksingh1490 amitksingh1490 marked this pull request as ready for review October 1, 2025 16:14
Comment thread crates/forge_main/src/config/handler.rs Outdated
Comment on lines +74 to +79
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());
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment thread shell-plugin/forge.plugin.zsh Outdated
Comment on lines +172 to +174
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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment thread crates/forge_main/src/config/handler.rs Outdated
Comment on lines +30 to +52
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(())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Set provider first (if specified)
  2. Then validate and set model (if specified)
  3. 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.

Suggested change
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

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Comment thread shell-plugin/forge.plugin.zsh Outdated
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)"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Comment thread crates/forge_main/src/main.rs Outdated
std::process::exit(1);
}
return Ok(());
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this?

Comment thread crates/forge_main/src/config/handler.rs Outdated
use crate::cli::{ConfigCommand, ConfigGetArgs, ConfigSetArgs};

/// Handle config command
pub async fn handle_config_command<A: API>(api: &A, command: ConfigCommand) -> Result<()> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convert to a struct ConfigManager that has access to Api. So we don't need to pipe api thru all the functions.

Comment thread crates/forge_main/src/config/handler.rs Outdated
Comment on lines +139 to +145
let provider_id = ProviderId::from_str(provider_str).with_context(|| {
format!(
"Invalid provider: '{}'. Valid providers are: {}",
provider_str,
get_valid_provider_names().join(", ")
)
})?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

amitksingh1490 and others added 22 commits October 3, 2025 12:52
- 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.
@tusharmath tusharmath force-pushed the feat/config-selection branch from 6e4e637 to b765bbc Compare October 3, 2025 07:22
@openhands-ai

openhands-ai Bot commented Oct 3, 2025

Copy link
Copy Markdown

Looks like there are a few issues preventing this PR from being merged!

  • GitHub Actions are failing:
    • ci

If you'd like me to help, just leave a comment, like

@OpenHands please fix the failing actions on PR #1623 at branch `feat/config-selection`

Feel free to include any additional details that might help me get this PR into a better state.

You can manage your notification settings

@tusharmath tusharmath merged commit fe2c09c into main Oct 3, 2025
9 checks passed
@tusharmath tusharmath deleted the feat/config-selection branch October 3, 2025 07:42
Comment on lines +32 to +38
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(())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Implementing the interactive selection UI for this case, or
  2. 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.

Suggested change
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

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feature Brand new functionality, features, pages, workflows, endpoints, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants