Bind sessions to their selected model

This commit is contained in:
Georg Bauer
2026-09-01 22:52:01 +02:00
parent 35f306bf2a
commit 9556b26c55
5 changed files with 163 additions and 149 deletions

View File

@@ -196,12 +196,11 @@ stored per model; the custom system prompt is shared across profiles. Other
sections control A2UI, permission defaults, endpoint settings, Git diff display, sections control A2UI, permission defaults, endpoint settings, Git diff display,
Dev Brain, extensions, checkpoint storage, and diagnostics. Dev Brain, extensions, checkpoint storage, and diagnostics.
A session remains model-independent until its first tool call. That call locks A new draft uses the currently selected model when its first turn is persisted.
the session to its model so persisted tool syntax is never mixed. Continuing a From then on the session keeps that exact model, so opening it immediately selects
locked session while another model is active asks before switching back. Legacy the same model and that model's thinking choices. Legacy sessions recover the
sessions recover the exact model from their checkpoint when available; a legacy exact model from their checkpoint when available; a legacy session whose
tool session whose exact model can no longer be identified remains viewable but exact model can no longer be identified remains viewable but cannot be continued.
cannot be continued.
### Main views, sidebar, and branches ### Main views, sidebar, and branches
@@ -232,7 +231,9 @@ restarts the local listener.
accepts `none`, `high`, or `max`; GLM 5.3 Flash accepts `low`, `high`, or `max`. accepts `none`, `high`, or `max`; GLM 5.3 Flash accepts `low`, `high`, or `max`.
Omitting it uses the upstream model default: `low` for DeepSeek and `max` for Omitting it uses the upstream model default: `low` for DeepSeek and `max` for
both GLM models. Unsupported values and conflicting thinking controls return a both GLM models. Unsupported values and conflicting thinking controls return a
400 error instead of being converted to another effort. 400 error instead of being converted to another effort. This per-model thinking
selection is intentionally part of the DS4Server agent harness, not a DS4 parity
surface; do not replace it with mapped, hidden, or shared choices.
### Data and recovery ### Data and recovery

View File

@@ -105,8 +105,6 @@ pub(crate) struct App {
session_menu: Option<i32>, session_menu: Option<i32>,
/// Session waiting for explicit confirmation before deletion. /// Session waiting for explicit confirmation before deletion.
pending_session_delete: Option<i32>, pending_session_delete: Option<i32>,
/// Locked session model waiting to replace the current global selection.
pending_session_model_switch: Option<ModelChoice>,
/// Session being renamed, with the in-progress title. /// Session being renamed, with the in-progress title.
session_rename: Option<(i32, String)>, session_rename: Option<(i32, String)>,
/// Projects whose archived sessions are expanded in the sidebar. /// Projects whose archived sessions are expanded in the sidebar.
@@ -502,8 +500,6 @@ pub(crate) enum Message {
AllowToolOnce, AllowToolOnce,
DenyTool, DenyTool,
SubmitPrompt, SubmitPrompt,
ConfirmSessionModelSwitch,
CancelSessionModelSwitch,
StopGeneration, StopGeneration,
GenerationTick, GenerationTick,
ChatScrolled(scrollable::Viewport), ChatScrolled(scrollable::Viewport),
@@ -649,7 +645,6 @@ impl App {
background_chats: HashMap::new(), background_chats: HashMap::new(),
session_menu: None, session_menu: None,
pending_session_delete: None, pending_session_delete: None,
pending_session_model_switch: None,
session_rename: None, session_rename: None,
expanded_archives: HashSet::new(), expanded_archives: HashSet::new(),
sidebar_drag: false, sidebar_drag: false,
@@ -815,7 +810,6 @@ impl App {
background_chats: HashMap::new(), background_chats: HashMap::new(),
session_menu: None, session_menu: None,
pending_session_delete: None, pending_session_delete: None,
pending_session_model_switch: None,
session_rename: None, session_rename: None,
expanded_archives: HashSet::new(), expanded_archives: HashSet::new(),
sidebar_drag: false, sidebar_drag: false,
@@ -1247,8 +1241,6 @@ impl App {
Message::DismissPanel => { Message::DismissPanel => {
if self.quit_confirmation { if self.quit_confirmation {
self.quit_confirmation = false; self.quit_confirmation = false;
} else if self.pending_session_model_switch.is_some() {
self.pending_session_model_switch = None;
} else if self.git_diff.is_some() { } else if self.git_diff.is_some() {
self.git_diff = None; self.git_diff = None;
} else if self.git_commit_all_confirmation { } else if self.git_commit_all_confirmation {
@@ -1568,20 +1560,21 @@ impl App {
} }
Message::SubmitPrompt => { Message::SubmitPrompt => {
if !self.generating && self.selected_session.is_some() { if !self.generating && self.selected_session.is_some() {
match required_session_model_switch( match required_session_model(
self.config.model,
self.selected_session_model(), self.selected_session_model(),
self.chat_tool_protocol_model(), !self.conversation.is_empty(),
) { ) {
Ok(Some(model)) => { Ok(Some(model)) => {
self.pending_session_model_switch = Some(model); if let Err(error) = self.activate_model(model) {
return Task::none(); self.error = Some(error);
return Task::none();
}
} }
Err(protocol) => { Err(()) => {
self.error = Some(format!( self.error = Some(
"This legacy session contains {} tool calls, but its exact model was not recorded and no compatible checkpoint remains. Start a new session to continue safely.", "This legacy session has chat history, but its exact model was not recorded and no compatible checkpoint remains. Start a new session to continue safely."
if protocol.is_glm() { "GLM" } else { "DeepSeek" } .into(),
)); );
return Task::none(); return Task::none();
} }
Ok(None) => {} Ok(None) => {}
@@ -1591,33 +1584,6 @@ impl App {
self.start_generation(); self.start_generation();
return scroll_chat_to_end(); return scroll_chat_to_end();
} }
Message::ConfirmSessionModelSwitch => {
let Some(model) = self.pending_session_model_switch.take() else {
return Task::none();
};
let mut config = self.config.clone();
config.model = model;
if let Err(error) = config.save(&config_path()) {
self.error = Some(error);
return Task::none();
}
self.config = config;
self.preference_draft = PreferenceDraft::from_saved(&self.config);
self.context_limit = self.config.active_generation().context_tokens.max(0) as u32;
if model != ModelChoice::Glm53Flash {
self.pending_vision_image = None;
}
#[cfg(target_os = "macos")]
{
self.agent_tools = None;
preferences::update_runtime_config(&self.runtime_config, &self.config);
}
self.error = None;
self.chat_follow_tail = true;
self.start_generation();
return scroll_chat_to_end();
}
Message::CancelSessionModelSwitch => self.pending_session_model_switch = None,
Message::StopGeneration => { Message::StopGeneration => {
self.stop_requested = true; self.stop_requested = true;
self.activity = Some("Stopping…".into()); self.activity = Some("Stopping…".into());
@@ -1993,7 +1959,18 @@ impl App {
if self.selected_session == Some(session_id) { if self.selected_session == Some(session_id) {
return Task::none(); return Task::none();
} }
self.pending_session_model_switch = None; let stored_model = self
.projects
.iter()
.flat_map(|project| &project.sessions)
.find(|session| session.id == session_id)
.and_then(|session| session.model());
if let Some(model) = stored_model
&& let Err(error) = self.activate_model(model)
{
self.error = Some(error);
return Task::none();
}
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
self.leave_current_chat(); self.leave_current_chat();
@@ -2037,26 +2014,28 @@ impl App {
self.error = Some(format!("Could not update the session: {error}")); self.error = Some(format!("Could not update the session: {error}"));
return Task::none(); return Task::none();
} }
let stored_model = saved_context
.as_ref()
.and_then(|(_, _, _, _, model)| *model);
let protocol_model = messages.iter().find_map(|message| { let protocol_model = messages.iter().find_map(|message| {
crate::agent::tool_protocol_model(&message.content) crate::agent::tool_protocol_model(&message.content)
}); });
if stored_model.is_none() if stored_model.is_none()
&& let (Some(protocol_model), Some(checkpoint_model)) = ( && let Some(checkpoint_model) = crate::engine::checkpoint_model(
protocol_model, &session_checkpoint_path(session_id),
crate::engine::checkpoint_model(&session_checkpoint_path(
session_id,
)),
) )
&& protocol_model.is_glm() == checkpoint_model.is_glm() && protocol_model.is_none_or(|protocol| {
&& let Err(error) = protocol.is_glm() == checkpoint_model.is_glm()
database.set_session_model(session_id, checkpoint_model) })
{ {
self.error = if let Err(error) =
Some(format!("Could not restore the session model: {error}")); database.set_session_model(session_id, checkpoint_model)
return Task::none(); {
self.error =
Some(format!("Could not restore the session model: {error}"));
return Task::none();
}
if let Err(error) = self.activate_model(checkpoint_model) {
self.error = Some(error);
return Task::none();
}
} }
self.conversation = messages.into_iter().map(ChatMessage::from).collect(); self.conversation = messages.into_iter().map(ChatMessage::from).collect();
self.chat_follow_tail = true; self.chat_follow_tail = true;
@@ -3232,15 +3211,14 @@ fn session_checkpoint_path(session_id: i32) -> PathBuf {
kv_cache_path().join(format!("{session_id}.bin")) kv_cache_path().join(format!("{session_id}.bin"))
} }
fn required_session_model_switch( fn required_session_model(
current: ModelChoice,
locked: Option<ModelChoice>, locked: Option<ModelChoice>,
legacy_protocol: Option<ModelChoice>, has_history: bool,
) -> Result<Option<ModelChoice>, ModelChoice> { ) -> Result<Option<ModelChoice>, ()> {
match locked { match locked {
Some(model) if model != current => Ok(Some(model)), Some(model) => Ok(Some(model)),
Some(_) => Ok(None), None if has_history => Err(()),
None => legacy_protocol.map_or(Ok(None), Err), None => Ok(None),
} }
} }
@@ -3295,22 +3273,11 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn tool_calls_lock_continuation_to_the_session_model() { fn stored_session_always_selects_its_model() {
let flash = ModelChoice::DeepSeekV4Flash0731; let flash = ModelChoice::DeepSeekV4Flash0731;
let glm = ModelChoice::Glm53Flash; assert_eq!(required_session_model(Some(flash), true), Ok(Some(flash)));
assert_eq!( assert_eq!(required_session_model(None, true), Err(()));
required_session_model_switch(flash, Some(flash), None), assert_eq!(required_session_model(None, false), Ok(None));
Ok(None)
);
assert_eq!(
required_session_model_switch(glm, Some(flash), None),
Ok(Some(flash))
);
assert_eq!(
required_session_model_switch(glm, None, Some(flash)),
Err(flash)
);
assert_eq!(required_session_model_switch(glm, None, None), Ok(None));
} }
#[test] #[test]

View File

@@ -1,6 +1,27 @@
use super::*; use super::*;
impl App { impl App {
pub(super) fn activate_model(&mut self, model: ModelChoice) -> Result<(), String> {
if self.config.model == model {
return Ok(());
}
let mut config = self.config.clone();
config.model = model;
config.save(&config_path())?;
self.config = config;
self.preference_draft = PreferenceDraft::from_saved(&self.config);
self.context_limit = self.config.active_generation().context_tokens.max(0) as u32;
if model != ModelChoice::Glm53Flash {
self.pending_vision_image = None;
}
#[cfg(target_os = "macos")]
{
self.agent_tools = None;
preferences::update_runtime_config(&self.runtime_config, &self.config);
}
Ok(())
}
pub(super) fn selected_session_model(&self) -> Option<ModelChoice> { pub(super) fn selected_session_model(&self) -> Option<ModelChoice> {
let session_id = self.selected_session?; let session_id = self.selected_session?;
self.projects self.projects
@@ -192,7 +213,6 @@ impl App {
self.drafts.entry(project_id).or_insert(title); self.drafts.entry(project_id).or_insert(title);
self.remember_project(project_id); self.remember_project(project_id);
self.selected_session = None; self.selected_session = None;
self.pending_session_model_switch = None;
self.permission_mode = self.config.default_permission_mode; self.permission_mode = self.config.default_permission_mode;
self.conversation.clear(); self.conversation.clear();
self.chat_follow_tail = true; self.chat_follow_tail = true;
@@ -234,7 +254,8 @@ impl App {
.database .database
.as_mut() .as_mut()
.ok_or_else(|| "The project database is unavailable.".to_owned())?; .ok_or_else(|| "The project database is unavailable.".to_owned())?;
let session = database.create_session(project_id, &title, self.permission_mode)?; let session =
database.create_session(project_id, &title, self.permission_mode, self.config.model)?;
self.drafts.remove(&project_id); self.drafts.remove(&project_id);
self.remember_project(project_id); self.remember_project(project_id);
self.selected_session = Some(session.id); self.selected_session = Some(session.id);

View File

@@ -110,7 +110,6 @@ impl App {
/// tree, so the layers below have to stay out of the dialog's field order. /// tree, so the layers below have to stay out of the dialog's field order.
pub(super) fn modal_open(&self) -> bool { pub(super) fn modal_open(&self) -> bool {
self.quit_confirmation self.quit_confirmation
|| self.pending_session_model_switch.is_some()
|| self.git_diff.is_some() || self.git_diff.is_some()
|| self.git_commit_all_confirmation || self.git_commit_all_confirmation
|| self.pending_project_path.is_some() || self.pending_project_path.is_some()
@@ -175,8 +174,6 @@ impl App {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
if self.quit_confirmation { if self.quit_confirmation {
layers.push(self.quit_confirmation_panel()); layers.push(self.quit_confirmation_panel());
} else if let Some(model) = self.pending_session_model_switch {
layers.push(self.session_model_switch_panel(model));
} else if let Some((prompt, _)) = &self.pending_tool_approval { } else if let Some((prompt, _)) = &self.pending_tool_approval {
layers.push(self.tool_approval_panel(prompt)); layers.push(self.tool_approval_panel(prompt));
} else if let Some(path) = &self.pending_project_path { } else if let Some(path) = &self.pending_project_path {
@@ -199,8 +196,6 @@ impl App {
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
if self.quit_confirmation { if self.quit_confirmation {
layers.push(self.quit_confirmation_panel()); layers.push(self.quit_confirmation_panel());
} else if let Some(model) = self.pending_session_model_switch {
layers.push(self.session_model_switch_panel(model));
} else if let Some(path) = &self.pending_project_path { } else if let Some(path) = &self.pending_project_path {
layers.push(self.project_dialog(path)); layers.push(self.project_dialog(path));
} else if let Some((_, title)) = &self.session_rename { } else if let Some((_, title)) = &self.session_rename {
@@ -257,39 +252,6 @@ impl App {
) )
} }
fn session_model_switch_panel(&self, model: ModelChoice) -> Element<'_, Message> {
let title = self.active_chat_title().unwrap_or("This session");
let dialog = container(
column![
text("Switch model to continue?").size(22),
text(format!(
"{title}” is locked to {model} because it contains that model's tool calls. Continuing will switch the active model from {} to {model}.",
self.config.model
))
.size(13),
row![
Space::new().width(Length::Fill),
action_button("Cancel").on_press(Message::CancelSessionModelSwitch),
action_button("Switch and continue")
.on_press(Message::ConfirmSessionModelSwitch),
]
.spacing(8),
]
.spacing(12),
)
.padding(22)
.width(520)
.style(overview_style);
opaque(
container(dialog)
.center_x(Length::Fill)
.center_y(Length::Fill)
.style(|_| {
container::Style::default().background(Color::from_rgba8(0, 0, 0, 0.68))
}),
)
}
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn tool_approval_panel<'a>( fn tool_approval_panel<'a>(
&'a self, &'a self,

View File

@@ -45,7 +45,7 @@ pub struct Session {
last_used: i64, last_used: i64,
/// Raw column value; read it through [`Session::permission_mode`]. /// Raw column value; read it through [`Session::permission_mode`].
permission_mode: String, permission_mode: String,
/// Locked after the first tool call; read it through [`Session::model`]. /// Locked when the first turn is persisted; read it through [`Session::model`].
model: Option<String>, model: Option<String>,
} }
@@ -127,6 +127,7 @@ struct NewSession<'a> {
title: &'a str, title: &'a str,
last_used: i64, last_used: i64,
permission_mode: &'a str, permission_mode: &'a str,
model: &'a str,
} }
#[derive(Clone, Debug, Identifiable, Queryable, Selectable)] #[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
@@ -395,6 +396,7 @@ impl Database {
project_id: i32, project_id: i32,
title: &str, title: &str,
permission_mode: PermissionMode, permission_mode: PermissionMode,
model: crate::model::ModelChoice,
) -> Result<Session, String> { ) -> Result<Session, String> {
diesel::insert_into(sessions::table) diesel::insert_into(sessions::table)
.values(NewSession { .values(NewSession {
@@ -402,6 +404,7 @@ impl Database {
title, title,
last_used: OffsetDateTime::now_utc().unix_timestamp(), last_used: OffsetDateTime::now_utc().unix_timestamp(),
permission_mode: permission_mode.as_id(), permission_mode: permission_mode.as_id(),
model: model.id(),
}) })
.returning(Session::as_returning()) .returning(Session::as_returning())
.get_result(&mut self.connection) .get_result(&mut self.connection)
@@ -838,29 +841,39 @@ mod tests {
let project = database.create_project("DS4", "/tmp/ds4").unwrap(); let project = database.create_project("DS4", "/tmp/ds4").unwrap();
let first = database let first = database
.create_session(project.id, "First session", PermissionMode::Heuristic) .create_session(
project.id,
"First session",
PermissionMode::Heuristic,
crate::model::ModelChoice::DeepSeekV4Flash0731,
)
.unwrap(); .unwrap();
let second = database let second = database
.create_session(project.id, "Second session", PermissionMode::Ai) .create_session(
project.id,
"Second session",
PermissionMode::Ai,
crate::model::ModelChoice::Glm53Flash,
)
.unwrap(); .unwrap();
database.delete_session(first.id).unwrap(); database.delete_session(first.id).unwrap();
let loaded = database.load_projects().unwrap(); let loaded = database.load_projects().unwrap();
assert_eq!(loaded[0].sessions[0].title, "Second session"); assert_eq!(loaded[0].sessions[0].title, "Second session");
assert_eq!(loaded[0].sessions[0].state(), SessionState::Normal); assert_eq!(loaded[0].sessions[0].state(), SessionState::Normal);
assert_eq!(loaded[0].sessions[0].permission_mode(), PermissionMode::Ai); assert_eq!(loaded[0].sessions[0].permission_mode(), PermissionMode::Ai);
assert_eq!(loaded[0].sessions[0].model(), None); assert_eq!(
loaded[0].sessions[0].model(),
Some(crate::model::ModelChoice::Glm53Flash)
);
database database
.set_session_permission_mode(second.id, PermissionMode::Heuristic) .set_session_permission_mode(second.id, PermissionMode::Heuristic)
.unwrap(); .unwrap();
database database
.set_session_model(second.id, crate::model::ModelChoice::DeepSeekV4Flash0731) .set_session_model(second.id, crate::model::ModelChoice::Glm53Flash)
.unwrap();
database
.set_session_model(second.id, crate::model::ModelChoice::DeepSeekV4Flash0731)
.unwrap(); .unwrap();
assert!( assert!(
database database
.set_session_model(second.id, crate::model::ModelChoice::Glm53Flash) .set_session_model(second.id, crate::model::ModelChoice::DeepSeekV4Flash0731)
.is_err() .is_err()
); );
assert_eq!( assert_eq!(
@@ -869,15 +882,25 @@ mod tests {
); );
assert_eq!( assert_eq!(
database.load_projects().unwrap()[0].sessions[0].model(), database.load_projects().unwrap()[0].sessions[0].model(),
Some(crate::model::ModelChoice::DeepSeekV4Flash0731) Some(crate::model::ModelChoice::Glm53Flash)
); );
let ordinary = loaded[0].sessions[0].id; let ordinary = loaded[0].sessions[0].id;
let pinned = database let pinned = database
.create_session(project.id, "Pinned", PermissionMode::Heuristic) .create_session(
project.id,
"Pinned",
PermissionMode::Heuristic,
crate::model::ModelChoice::DeepSeekV4Flash0731,
)
.unwrap(); .unwrap();
let archived = database let archived = database
.create_session(project.id, "Archived", PermissionMode::Heuristic) .create_session(
project.id,
"Archived",
PermissionMode::Heuristic,
crate::model::ModelChoice::DeepSeekV4Flash0731,
)
.unwrap(); .unwrap();
database database
.set_session_state(pinned.id, SessionState::Pinned) .set_session_state(pinned.id, SessionState::Pinned)
@@ -934,13 +957,28 @@ mod tests {
.create_project("DS4", "/tmp/ds4-session-order") .create_project("DS4", "/tmp/ds4-session-order")
.unwrap(); .unwrap();
let older = database let older = database
.create_session(project.id, "Older", PermissionMode::Heuristic) .create_session(
project.id,
"Older",
PermissionMode::Heuristic,
crate::model::ModelChoice::DeepSeekV4Flash0731,
)
.unwrap(); .unwrap();
let newer = database let newer = database
.create_session(project.id, "Newer", PermissionMode::Heuristic) .create_session(
project.id,
"Newer",
PermissionMode::Heuristic,
crate::model::ModelChoice::DeepSeekV4Flash0731,
)
.unwrap(); .unwrap();
let pinned = database let pinned = database
.create_session(project.id, "Pinned", PermissionMode::Heuristic) .create_session(
project.id,
"Pinned",
PermissionMode::Heuristic,
crate::model::ModelChoice::DeepSeekV4Flash0731,
)
.unwrap(); .unwrap();
database database
.set_session_state(pinned.id, SessionState::Pinned) .set_session_state(pinned.id, SessionState::Pinned)
@@ -980,7 +1018,12 @@ mod tests {
.create_project("DS4", "/tmp/ds4-reactivate") .create_project("DS4", "/tmp/ds4-reactivate")
.unwrap(); .unwrap();
let session = database let session = database
.create_session(project.id, "Archived", PermissionMode::Heuristic) .create_session(
project.id,
"Archived",
PermissionMode::Heuristic,
crate::model::ModelChoice::DeepSeekV4Flash0731,
)
.unwrap(); .unwrap();
database database
.set_session_state(session.id, SessionState::Archived) .set_session_state(session.id, SessionState::Archived)
@@ -1020,7 +1063,12 @@ mod tests {
.create_project("DS4", "/tmp/ds4-a2ui-dismiss") .create_project("DS4", "/tmp/ds4-a2ui-dismiss")
.unwrap(); .unwrap();
let session = database let session = database
.create_session(project.id, "A2UI", PermissionMode::Heuristic) .create_session(
project.id,
"A2UI",
PermissionMode::Heuristic,
crate::model::ModelChoice::DeepSeekV4Flash0731,
)
.unwrap(); .unwrap();
let first = database let first = database
.start_chat_turn(session.id, "First", None, &[], false) .start_chat_turn(session.id, "First", None, &[], false)
@@ -1100,7 +1148,12 @@ mod tests {
let mut database = Database::open(&path).unwrap(); let mut database = Database::open(&path).unwrap();
let project = database.create_project("DS4", "/tmp/ds4-chat").unwrap(); let project = database.create_project("DS4", "/tmp/ds4-chat").unwrap();
let session = database let session = database
.create_session(project.id, "Chat", PermissionMode::Heuristic) .create_session(
project.id,
"Chat",
PermissionMode::Heuristic,
crate::model::ModelChoice::DeepSeekV4Flash0731,
)
.unwrap(); .unwrap();
let mut opening = database let mut opening = database
.start_chat_turn( .start_chat_turn(
@@ -1309,10 +1362,20 @@ mod tests {
let mut database = Database::open(&path).unwrap(); let mut database = Database::open(&path).unwrap();
let project = database.create_project("DS4", "/tmp/ds4-svg-chat").unwrap(); let project = database.create_project("DS4", "/tmp/ds4-svg-chat").unwrap();
let session = database let session = database
.create_session(project.id, "SVG", PermissionMode::Heuristic) .create_session(
project.id,
"SVG",
PermissionMode::Heuristic,
crate::model::ModelChoice::DeepSeekV4Flash0731,
)
.unwrap(); .unwrap();
let other = database let other = database
.create_session(project.id, "Other", PermissionMode::Heuristic) .create_session(
project.id,
"Other",
PermissionMode::Heuristic,
crate::model::ModelChoice::Glm53Flash,
)
.unwrap(); .unwrap();
let assistant = database let assistant = database
.start_chat_turn(session.id, "Draw it", None, &[], false) .start_chat_turn(session.id, "Draw it", None, &[], false)