fix(tui): refresh prompt on model switch

This commit is contained in:
cyq
2026-06-02 02:54:32 +08:00
committed by Hunter Bown
parent 1605d8de44
commit 46de1a9b2d
5 changed files with 73 additions and 8 deletions
+3 -1
View File
@@ -789,10 +789,12 @@ impl Engine {
.send(Event::status(format!("Mode changed to: {mode:?}")))
.await;
}
Op::SetModel { model } => {
Op::SetModel { model, mode } => {
self.session.auto_model = model.trim().eq_ignore_ascii_case("auto");
self.session.model = model;
self.config.model.clone_from(&self.session.model);
self.refresh_system_prompt(mode);
self.emit_session_updated().await;
let _ = self
.tx_event
.send(Event::status(format!(
+55
View File
@@ -1238,6 +1238,61 @@ async fn session_update_preserves_reasoning_tool_only_turn() {
assert_eq!(messages, vec![assistant]);
}
#[tokio::test]
async fn set_model_reloads_instruction_sources_and_updates_session_prompt() {
let tmp = tempdir().expect("tempdir");
let instructions = tmp.path().join("instructions.md");
fs::write(&instructions, "FLASH_INSTRUCTIONS_MARKER").expect("write instructions");
let config = EngineConfig {
workspace: tmp.path().to_path_buf(),
model: "deepseek-v4-flash".to_string(),
instructions: vec![instructions.clone().into()],
..Default::default()
};
let (engine, handle) = Engine::new(config, &Config::default());
fs::write(&instructions, "PRO_INSTRUCTIONS_MARKER").expect("rewrite instructions");
let run = tokio::spawn(engine.run());
handle
.send(Op::SetModel {
model: "deepseek-v4-pro".to_string(),
mode: AppMode::Agent,
})
.await
.expect("send set model");
let (model, prompt) = {
let mut rx = handle.rx_event.write().await;
loop {
let event = tokio::time::timeout(std::time::Duration::from_secs(1), rx.recv())
.await
.expect("session update after model switch")
.expect("event");
if let Event::SessionUpdated {
model,
system_prompt,
..
} = event
{
let prompt = match system_prompt.expect("system prompt") {
SystemPrompt::Text(text) => text,
SystemPrompt::Blocks(blocks) => blocks
.into_iter()
.map(|block| block.text)
.collect::<Vec<_>>()
.join("\n"),
};
break (model, prompt);
}
}
};
run.abort();
assert_eq!(model, "deepseek-v4-pro");
assert!(prompt.contains("PRO_INSTRUCTIONS_MARKER"));
assert!(!prompt.contains("FLASH_INSTRUCTIONS_MARKER"));
}
#[test]
fn detects_context_length_errors_from_provider_payloads() {
let msg = r#"SSE stream request failed: HTTP 400 Bad Request: {"error":{"message":"This model's maximum context length is 131072 tokens. However, you requested 153056 tokens (148960 in the messages, 4096 in the completion).","type":"invalid_request_error"}}"#;
+2 -2
View File
@@ -63,9 +63,9 @@ pub enum Op {
#[allow(dead_code)]
ChangeMode { mode: AppMode },
/// Update the model being used
/// Update the model being used and refresh the prompt for the current mode.
#[allow(dead_code)]
SetModel { model: String },
SetModel { model: String, mode: AppMode },
/// Update auto-compaction settings
SetCompaction { config: CompactionConfig },
+10 -3
View File
@@ -3437,6 +3437,7 @@ async fn run_event_loop(
let _ = engine_handle
.send(Op::SetModel {
model: app.model.clone(),
mode: app.mode,
})
.await;
}
@@ -4748,10 +4749,12 @@ async fn dispatch_user_message(
async fn apply_model_and_compaction_update(
engine_handle: &EngineHandle,
compaction: crate::compaction::CompactionConfig,
mode: AppMode,
) {
let _ = engine_handle
.send(Op::SetModel {
model: compaction.model.clone(),
mode,
})
.await;
let _ = engine_handle
@@ -4779,6 +4782,7 @@ async fn drain_web_config_events(
apply_model_and_compaction_update(
engine_handle,
app.compaction_config(),
app.mode,
)
.await;
}
@@ -4803,6 +4807,7 @@ async fn drain_web_config_events(
apply_model_and_compaction_update(
engine_handle,
app.compaction_config(),
app.mode,
)
.await;
}
@@ -4888,7 +4893,7 @@ async fn apply_model_picker_choice(
}
if model_changed {
apply_model_and_compaction_update(engine_handle, app.compaction_config()).await;
apply_model_and_compaction_update(engine_handle, app.compaction_config(), app.mode).await;
}
let model_summary = if model_is_auto {
@@ -5237,7 +5242,7 @@ async fn apply_command_result(
}
}
AppAction::UpdateCompaction(compaction) => {
apply_model_and_compaction_update(engine_handle, compaction).await;
apply_model_and_compaction_update(engine_handle, compaction, app.mode).await;
}
AppAction::OpenConfigEditor(mode) => match mode {
ConfigUiMode::Native => {
@@ -5267,6 +5272,7 @@ async fn apply_command_result(
apply_model_and_compaction_update(
engine_handle,
app.compaction_config(),
app.mode,
)
.await;
}
@@ -6743,7 +6749,8 @@ async fn handle_view_events(
if let Some(action) = result.action {
match action {
AppAction::UpdateCompaction(compaction) => {
apply_model_and_compaction_update(engine_handle, compaction).await;
apply_model_and_compaction_update(engine_handle, compaction, app.mode)
.await;
}
AppAction::OpenConfigView => {}
_ => {}
+3 -2
View File
@@ -2021,11 +2021,12 @@ async fn model_change_update_syncs_engine_model_before_compaction() {
let compaction = app.compaction_config();
let mut engine = crate::core::engine::mock_engine_handle();
apply_model_and_compaction_update(&engine.handle, compaction).await;
apply_model_and_compaction_update(&engine.handle, compaction, app.mode).await;
match engine.rx_op.recv().await.expect("set model op") {
crate::core::ops::Op::SetModel { model } => {
crate::core::ops::Op::SetModel { model, mode } => {
assert_eq!(model, "deepseek-v4-flash");
assert_eq!(mode, app.mode);
}
other => panic!("expected SetModel, got {other:?}"),
}