Skip to content

Commit 26bb3d9

Browse files
laststylebender14forge-code-agentautofix-ci[bot]tusharmath
committed
Feat(suggest-model): add suggest model command (#2511)
Co-authored-by: ForgeCode <noreply@forgecode.dev> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Tushar Mathur <tusharmath@gmail.com>
1 parent 91f3f04 commit 26bb3d9

13 files changed

Lines changed: 173 additions & 7 deletions

File tree

crates/forge_api/src/api.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,14 @@ pub trait API: Sync + Send {
165165
/// generation).
166166
async fn set_commit_config(&self, config: forge_domain::CommitConfig) -> anyhow::Result<()>;
167167

168+
/// Gets the suggest configuration (provider and model for command
169+
/// suggestion generation).
170+
async fn get_suggest_config(&self) -> anyhow::Result<Option<forge_domain::SuggestConfig>>;
171+
172+
/// Sets the suggest configuration (provider and model for command
173+
/// suggestion generation).
174+
async fn set_suggest_config(&self, config: forge_domain::SuggestConfig) -> anyhow::Result<()>;
175+
168176
/// Refresh MCP caches by fetching fresh data
169177
async fn reload_mcp(&self) -> Result<()>;
170178

crates/forge_api/src/forge_api.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,14 @@ impl<
301301
self.services.set_commit_config(config).await
302302
}
303303

304+
async fn get_suggest_config(&self) -> anyhow::Result<Option<SuggestConfig>> {
305+
self.services.get_suggest_config().await
306+
}
307+
308+
async fn set_suggest_config(&self, config: SuggestConfig) -> anyhow::Result<()> {
309+
self.services.set_suggest_config(config).await
310+
}
311+
304312
async fn get_login_info(&self) -> Result<Option<LoginInfo>> {
305313
self.services.auth_service().get_auth_token().await
306314
}

crates/forge_app/src/command_generator.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,20 @@ where
4444
&serde_json::json!({"env": env, "files": files}),
4545
)?;
4646

47-
// Get required services and data
48-
let provider_id = self.services.get_default_provider().await?;
49-
let provider = self.services.get_provider(provider_id).await?;
50-
let model = self.services.get_provider_model(Some(&provider.id)).await?;
47+
// Get required services and data - use suggest config if available,
48+
// otherwise fall back to default provider/model
49+
let (provider, model) = match self.services.get_suggest_config().await? {
50+
Some(config) => {
51+
let provider = self.services.get_provider(config.provider).await?;
52+
(provider, config.model)
53+
}
54+
None => {
55+
let provider_id = self.services.get_default_provider().await?;
56+
let provider = self.services.get_provider(provider_id).await?;
57+
let model = self.services.get_provider_model(Some(&provider.id)).await?;
58+
(provider, model)
59+
}
60+
};
5161

5262
// Build user prompt with task and recent commands
5363
let user_content = format!("<task>{}</task>", prompt.as_str());
@@ -249,6 +259,14 @@ mod tests {
249259
async fn set_commit_config(&self, _config: forge_domain::CommitConfig) -> Result<()> {
250260
Ok(())
251261
}
262+
263+
async fn get_suggest_config(&self) -> Result<Option<forge_domain::SuggestConfig>> {
264+
Ok(None)
265+
}
266+
267+
async fn set_suggest_config(&self, _config: forge_domain::SuggestConfig) -> Result<()> {
268+
Ok(())
269+
}
252270
}
253271

254272
#[tokio::test]

crates/forge_app/src/services.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ pub trait AppConfigService: Send + Sync {
222222
/// Sets the commit configuration (provider and model for commit message
223223
/// generation).
224224
async fn set_commit_config(&self, config: forge_domain::CommitConfig) -> anyhow::Result<()>;
225+
226+
/// Gets the suggest configuration (provider and model for command
227+
/// suggestion generation).
228+
async fn get_suggest_config(&self) -> anyhow::Result<Option<forge_domain::SuggestConfig>>;
229+
230+
/// Sets the suggest configuration (provider and model for command
231+
/// suggestion generation).
232+
async fn set_suggest_config(&self, config: forge_domain::SuggestConfig) -> anyhow::Result<()>;
225233
}
226234

227235
#[async_trait::async_trait]
@@ -1048,6 +1056,14 @@ impl<I: Services> AppConfigService for I {
10481056
async fn set_commit_config(&self, config: forge_domain::CommitConfig) -> anyhow::Result<()> {
10491057
self.config_service().set_commit_config(config).await
10501058
}
1059+
1060+
async fn get_suggest_config(&self) -> anyhow::Result<Option<forge_domain::SuggestConfig>> {
1061+
self.config_service().get_suggest_config().await
1062+
}
1063+
1064+
async fn set_suggest_config(&self, config: forge_domain::SuggestConfig) -> anyhow::Result<()> {
1065+
self.config_service().set_suggest_config(config).await
1066+
}
10511067
}
10521068

10531069
#[async_trait::async_trait]

crates/forge_domain/src/app_config.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::collections::HashMap;
33
use derive_more::From;
44
use serde::{Deserialize, Serialize};
55

6-
use crate::{CommitConfig, ModelId, ProviderId};
6+
use crate::{CommitConfig, ModelId, ProviderId, SuggestConfig};
77

88
#[derive(Deserialize)]
99
#[serde(rename_all = "camelCase")]
@@ -23,6 +23,8 @@ pub struct AppConfig {
2323
pub model: HashMap<ProviderId, ModelId>,
2424
#[serde(default, skip_serializing_if = "Option::is_none")]
2525
pub commit: Option<CommitConfig>,
26+
#[serde(default, skip_serializing_if = "Option::is_none")]
27+
pub suggest: Option<SuggestConfig>,
2628
}
2729

2830
#[derive(Clone, Serialize, Deserialize, From, Debug, PartialEq)]

crates/forge_domain/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ mod session_metrics;
4141
mod shell;
4242
mod skill;
4343
mod snapshot;
44+
mod suggest_config;
4445
mod suggestion;
4546
mod system_context;
4647
mod temperature;
@@ -98,6 +99,7 @@ pub use session_metrics::*;
9899
pub use shell::*;
99100
pub use skill::*;
100101
pub use snapshot::*;
102+
pub use suggest_config::*;
101103
pub use suggestion::*;
102104
pub use system_context::*;
103105
pub use temperature::*;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use derive_setters::Setters;
2+
use schemars::JsonSchema;
3+
use serde::{Deserialize, Serialize};
4+
5+
use crate::{ModelId, ProviderId};
6+
7+
/// Configuration for shell command suggestion generation.
8+
///
9+
/// Allows specifying a dedicated provider and model for shell command
10+
/// suggestion generation, instead of using the active agent's provider and
11+
/// model. This is useful when you want to use a cheaper or faster model for
12+
/// simple command suggestions. Both provider and model must be specified
13+
/// together.
14+
#[derive(Debug, Clone, Serialize, Deserialize, Setters, JsonSchema, PartialEq)]
15+
#[setters(into)]
16+
pub struct SuggestConfig {
17+
/// Provider ID to use for command suggestion generation.
18+
pub provider: ProviderId,
19+
20+
/// Model ID to use for command suggestion generation.
21+
pub model: ModelId,
22+
}

crates/forge_main/src/built_in_commands.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
"command": "config-commit-model",
2020
"description": "Set the model used for commit message generation [alias: ccm]"
2121
},
22+
{
23+
"command": "suggest-model",
24+
"description": "Set the model used for command suggestion generation [alias: sm]"
25+
},
2226
{
2327
"command": "new",
2428
"description": "Start new conversation [alias: n]"

crates/forge_main/src/cli.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,13 @@ pub enum ConfigSetField {
553553
/// Model ID to use for commit message generation.
554554
model: ModelId,
555555
},
556+
/// Set the provider and model for command suggestion generation.
557+
Suggest {
558+
/// Provider ID to use for command suggestion generation.
559+
provider: ProviderId,
560+
/// Model ID to use for command suggestion generation.
561+
model: ModelId,
562+
},
556563
}
557564

558565
/// Type-safe subcommands for `forge config get`.
@@ -564,6 +571,8 @@ pub enum ConfigGetField {
564571
Provider,
565572
/// Get the commit message generation config.
566573
Commit,
574+
/// Get the command suggestion generation config.
575+
Suggest,
567576
}
568577

569578
/// Command group for conversation management.

crates/forge_main/src/ui.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,12 +1337,24 @@ impl<A: API + ConsoleWriter + 'static, F: Fn() -> A + Send + Sync> UI<A, F> {
13371337
.map(|m| m.as_str().to_string())
13381338
.unwrap_or_else(|| markers::EMPTY.to_string());
13391339

1340+
let suggest_config = self.api.get_suggest_config().await.ok().flatten();
1341+
let suggest_provider = suggest_config
1342+
.as_ref()
1343+
.map(|c| c.provider.to_string())
1344+
.unwrap_or_else(|| markers::EMPTY.to_string());
1345+
let suggest_model = suggest_config
1346+
.as_ref()
1347+
.map(|c| c.model.as_str().to_string())
1348+
.unwrap_or_else(|| markers::EMPTY.to_string());
1349+
13401350
let info = Info::new()
13411351
.add_title("CONFIGURATION")
13421352
.add_key_value("Default Model", model)
13431353
.add_key_value("Default Provider", provider)
13441354
.add_key_value("Commit Provider", commit_provider)
1345-
.add_key_value("Commit Model", commit_model);
1355+
.add_key_value("Commit Model", commit_model)
1356+
.add_key_value("Suggest Provider", suggest_provider)
1357+
.add_key_value("Suggest Model", suggest_model);
13461358

13471359
if porcelain {
13481360
self.writeln(
@@ -3169,6 +3181,18 @@ impl<A: API + ConsoleWriter + 'static, F: Fn() -> A + Send + Sync> UI<A, F> {
31693181
.sub_title(format!("is now the commit model for provider '{provider}'")),
31703182
)?;
31713183
}
3184+
ConfigSetField::Suggest { provider, model } => {
3185+
// Validate provider exists and model belongs to that specific provider
3186+
let validated_model = self.validate_model(model.as_str(), Some(&provider)).await?;
3187+
let suggest_config = forge_domain::SuggestConfig {
3188+
provider: provider.clone(),
3189+
model: validated_model.clone(),
3190+
};
3191+
self.api.set_suggest_config(suggest_config).await?;
3192+
self.writeln_title(TitleFormat::action(validated_model.as_str()).sub_title(
3193+
format!("is now the suggest model for provider '{provider}'"),
3194+
))?;
3195+
}
31723196
}
31733197

31743198
Ok(())
@@ -3208,7 +3232,7 @@ impl<A: API + ConsoleWriter + 'static, F: Fn() -> A + Send + Sync> UI<A, F> {
32083232
Some(config) => {
32093233
let provider = config
32103234
.provider
3211-
.map(|p| p.to_string())
3235+
.map(|p| p.as_ref().to_string())
32123236
.unwrap_or_else(|| "Not set".to_string());
32133237
let model = config
32143238
.model
@@ -3220,6 +3244,16 @@ impl<A: API + ConsoleWriter + 'static, F: Fn() -> A + Send + Sync> UI<A, F> {
32203244
None => self.writeln("Commit: Not set")?,
32213245
}
32223246
}
3247+
ConfigGetField::Suggest => {
3248+
let suggest_config = self.api.get_suggest_config().await?;
3249+
match suggest_config {
3250+
Some(config) => {
3251+
self.writeln(config.provider.as_ref())?;
3252+
self.writeln(config.model.as_str().to_string())?;
3253+
}
3254+
None => self.writeln("Suggest: Not set")?,
3255+
}
3256+
}
32233257
}
32243258

32253259
Ok(())

0 commit comments

Comments
 (0)