fix(exec): preserve auto model handoff

Harvests the narrow CLI/TUI exec model propagation fix from PR #3148 while honoring the CodeWhale model env alias before the legacy DeepSeek model handoff.

Co-authored-by: hongchen1993 <269377208+hongchen1993@users.noreply.github.com>
This commit is contained in:
Hunter B
2026-06-12 01:38:39 -07:00
parent 18bbbed2db
commit 862cb2e394
3 changed files with 69 additions and 8 deletions
+4
View File
@@ -46,6 +46,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
providers now report whether the value came from `--provider`, environment,
or config. Config-sourced unsupported providers fall back to DeepSeek without
forwarding stale keyring secrets. Thanks @cyq1017 for the PR.
- **Exec auto-model handoff (#3148).** `codewhale exec --model auto` now
survives the CLI/TUI boundary by honoring the CodeWhale model env alias and
legacy DeepSeek model handoff before falling back to provider defaults.
Thanks @hongchen1993 for the PR.
- **TUI mouse-report leak (#3063/#3067).** Strip raw SGR mouse coordinate
tails from the composer even when `use_mouse_capture` is false, covering
orphaned terminal reporting state after crashes or focus races.
+4
View File
@@ -46,6 +46,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
providers now report whether the value came from `--provider`, environment,
or config. Config-sourced unsupported providers fall back to DeepSeek without
forwarding stale keyring secrets. Thanks @cyq1017 for the PR.
- **Exec auto-model handoff (#3148).** `codewhale exec --model auto` now
survives the CLI/TUI boundary by honoring the CodeWhale model env alias and
legacy DeepSeek model handoff before falling back to provider defaults.
Thanks @hongchen1993 for the PR.
- **TUI mouse-report leak (#3063/#3067).** Strip raw SGR mouse coordinate
tails from the composer even when `use_mouse_capture` is false, covering
orphaned terminal reporting state after crashes or focus races.
+61 -8
View File
@@ -307,14 +307,6 @@ Plain `codewhale exec` is a one-shot model response. Use `--auto` for
non-interactive filesystem/shell tool use.
")]
struct ExecArgs {
/// Prompt to send to the model
#[arg(
value_name = "PROMPT",
required = true,
trailing_var_arg = true,
allow_hyphen_values = true
)]
prompt: Vec<String>,
/// Override model for this run
#[arg(long)]
model: Option<String>,
@@ -349,6 +341,14 @@ struct ExecArgs {
/// Extra text appended to the system prompt for this run.
#[arg(long)]
append_system_prompt: Option<String>,
/// Prompt to send to the model
#[arg(
value_name = "PROMPT",
required = true,
trailing_var_arg = true,
allow_hyphen_values = true
)]
prompt: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
@@ -464,9 +464,21 @@ fn resolve_exec_model(config: &Config, explicit_model: Option<&str>) -> String {
.map(str::trim)
.filter(|model| !model.is_empty())
.map(ToOwned::to_owned)
.or_else(exec_model_env_override)
.unwrap_or_else(|| config.default_model())
}
fn exec_model_env_override() -> Option<String> {
["CODEWHALE_MODEL", "DEEPSEEK_MODEL"]
.into_iter()
.find_map(|key| {
std::env::var(key)
.ok()
.map(|model| model.trim().to_string())
.filter(|model| !model.is_empty())
})
}
fn top_level_prompt_initial_input(parts: &[String]) -> Option<tui::InitialInput> {
(!parts.is_empty()).then(|| tui::InitialInput::Submit(join_prompt_parts(parts)))
}
@@ -6647,6 +6659,9 @@ mod terminal_mode_tests {
#[test]
fn exec_model_resolution_uses_provider_scoped_default() {
let _env_lock = crate::test_support::lock_test_env();
let _codewhale_model = crate::test_support::EnvVarGuard::remove("CODEWHALE_MODEL");
let _deepseek_model = crate::test_support::EnvVarGuard::remove("DEEPSEEK_MODEL");
let config = Config {
provider: Some("openrouter".to_string()),
default_text_model: Some("deepseek/deepseek-v4-pro".to_string()),
@@ -6670,6 +6685,33 @@ mod terminal_mode_tests {
);
}
#[test]
fn exec_model_resolution_prefers_codewhale_model_env_override() {
let _env_lock = crate::test_support::lock_test_env();
let _codewhale_model = crate::test_support::EnvVarGuard::set("CODEWHALE_MODEL", " auto ");
let _deepseek_model =
crate::test_support::EnvVarGuard::set("DEEPSEEK_MODEL", "stale-deepseek-model");
let config = Config {
default_text_model: Some("deepseek/deepseek-v4-pro".to_string()),
..Default::default()
};
assert_eq!(resolve_exec_model(&config, None), "auto");
}
#[test]
fn exec_model_resolution_uses_legacy_deepseek_model_env_override() {
let _env_lock = crate::test_support::lock_test_env();
let _codewhale_model = crate::test_support::EnvVarGuard::remove("CODEWHALE_MODEL");
let _deepseek_model = crate::test_support::EnvVarGuard::set("DEEPSEEK_MODEL", " auto ");
let config = Config {
default_text_model: Some("deepseek/deepseek-v4-pro".to_string()),
..Default::default()
};
assert_eq!(resolve_exec_model(&config, None), "auto");
}
#[test]
fn exec_accepts_split_prompt_words_for_windows_cmd_shims() {
let cli = parse_cli(&["codewhale", "exec", "hello", "world"]);
@@ -6680,6 +6722,17 @@ mod terminal_mode_tests {
assert_eq!(args.prompt, vec!["hello", "world"]);
}
#[test]
fn exec_keeps_model_flag_before_split_prompt_words() {
let cli = parse_cli(&["codewhale", "exec", "--model", "auto", "hello", "world"]);
let Some(Commands::Exec(args)) = cli.command else {
panic!("expected exec command");
};
assert_eq!(args.model.as_deref(), Some("auto"));
assert_eq!(args.prompt, vec!["hello", "world"]);
}
#[test]
fn exec_keeps_flags_before_split_prompt_words() {
let cli = parse_cli(&["codewhale", "exec", "--json", "hello", "world"]);