From 97b5a6c3247299e6863b17bf0b6e125692d33766 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 31 Mar 2026 04:18:29 +0100 Subject: feat: opt-in to sharing last command with ai (#3367) This enables it to perform more effectively and give better suggestions. Same as send_cwd, disabled by default, opt in. ## Checks - [ ] I am happy for maintainers to push small adjustments to this PR, to speed up the review cycle - [ ] I have checked that there are no existing pull requests for the same thing --- crates/atuin-ai/src/commands/inline.rs | 37 ++++++++++++++++++++++++++++++---- crates/atuin-client/src/settings.rs | 18 ++++++++++++++++- docs/docs/ai/settings.md | 19 ++++++++++++++++- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/crates/atuin-ai/src/commands/inline.rs b/crates/atuin-ai/src/commands/inline.rs index c16e3dac..aeb414fb 100644 --- a/crates/atuin-ai/src/commands/inline.rs +++ b/crates/atuin-ai/src/commands/inline.rs @@ -1,9 +1,11 @@ +use std::path::PathBuf; use std::sync::mpsc; use crate::commands::detect_shell; use crate::tui::events::AiTuiEvent; use crate::tui::state::{AppState, ExitAction}; use crate::tui::view::ai_view; +use atuin_client::database::{Database, Sqlite}; use atuin_client::distro::detect_linux_distribution; use atuin_common::tls::ensure_crypto_provider; use eventsource_stream::Eventsource; @@ -138,6 +140,7 @@ fn create_chat_stream( session_id: Option, messages: Vec, send_cwd: bool, + last_command: Option, ) -> std::pin::Pin> + Send>> { Box::pin(async_stream::stream! { ensure_crypto_provider(); @@ -160,6 +163,7 @@ fn create_chat_stream( "pwd": if send_cwd { std::env::current_dir() .ok() .map(|path| path.to_string_lossy().into_owned()) } else { None }, + "last_command": last_command, }); if os == "linux" { @@ -294,8 +298,16 @@ async fn run_chat_stream( session_id: Option, messages: Vec, send_cwd: bool, + last_command: Option, ) { - let stream = create_chat_stream(endpoint, token, session_id, messages, send_cwd); + let stream = create_chat_stream( + endpoint, + token, + session_id, + messages, + send_cwd, + last_command, + ); futures::pin_mut!(stream); while let Some(event) = stream.next().await { @@ -388,7 +400,22 @@ async fn run_inline_tui( .extra_newlines_at_exit(1) .build()?; - let send_cwd = settings.ai.send_cwd; + // Support both legacy [ai] send_cwd and new [ai.opening] send_cwd + let send_cwd = + settings.ai.opening.send_cwd.unwrap_or(false) || settings.ai.send_cwd.unwrap_or(false); + + let last_command = if settings.ai.opening.send_last_command.unwrap_or(false) { + let db_path = PathBuf::from(settings.db_path.as_str()); + match Sqlite::new(db_path, settings.local_timeout).await { + Ok(db) => db.last().await.ok().flatten().map(|h| h.command), + Err(e) => { + debug!("Failed to open history database for read_history: {e}"); + None + } + } + } else { + None + }; // Event loop: receives AiTuiEvent from components, mutates state via Handle. let h = handle.clone(); @@ -433,6 +460,7 @@ async fn run_inline_tui( let ep = ep.clone(); let tk = tk.clone(); let h2 = h.clone(); + let lc = last_command.clone(); h.update(move |state| { state.start_generating(input); state.start_streaming(); @@ -440,7 +468,7 @@ async fn run_inline_tui( let messages = state.events_to_messages(); let sid = state.session_id.clone(); let task = tokio::spawn(async move { - run_chat_stream(h2, ep, tk, sid, messages, send_cwd).await; + run_chat_stream(h2, ep, tk, sid, messages, send_cwd, lc).await; }); state.stream_abort = Some(task.abort_handle()); }); @@ -502,13 +530,14 @@ async fn run_inline_tui( let ep = ep.clone(); let tk = tk.clone(); let h2 = h.clone(); + let lc = last_command.clone(); h.update(move |state| { state.retry(); state.start_streaming(); let messages = state.events_to_messages(); let sid = state.session_id.clone(); let task = tokio::spawn(async move { - run_chat_stream(h2, ep, tk, sid, messages, send_cwd).await; + run_chat_stream(h2, ep, tk, sid, messages, send_cwd, lc).await; }); state.stream_abort = Some(task.abort_handle()); }); diff --git a/crates/atuin-client/src/settings.rs b/crates/atuin-client/src/settings.rs index 5b18d9ea..b3359d19 100644 --- a/crates/atuin-client/src/settings.rs +++ b/crates/atuin-client/src/settings.rs @@ -664,8 +664,22 @@ pub struct Ai { /// Only necessary for custom AI endpoints. pub api_token: Option, + /// Deprecated: use opening.send_cwd instead. Kept for backwards compatibility. + #[serde(default)] + pub send_cwd: Option, + + /// Configuration for what context is sent in the opening AI request. + #[serde(default)] + pub opening: AiOpening, +} + +#[derive(Default, Clone, Debug, Deserialize, Serialize)] +pub struct AiOpening { /// Whether or not to send the current working directory to the AI endpoint. - pub send_cwd: bool, + pub send_cwd: Option, + + /// Whether or not to send the last command as context in the opening AI request. + pub send_last_command: Option, } impl Default for Preview { @@ -1524,6 +1538,8 @@ impl Settings { .set_default("search.frecency_score_multiplier", 1.0)? .set_default("meta.db_path", meta_path.to_str())? .set_default("ai.send_cwd", false)? + .set_default("ai.opening.send_cwd", false)? + .set_default("ai.opening.send_last_command", false)? .set_default( "search.filters", vec![ diff --git a/docs/docs/ai/settings.md b/docs/docs/ai/settings.md index be27261f..ad7c7af7 100644 --- a/docs/docs/ai/settings.md +++ b/docs/docs/ai/settings.md @@ -8,6 +8,10 @@ Default: `false` Whether or not the AI feature are enabled. When set to `false`, the question mark keybinding will output a message with instructions to run `atuin setup` to enable the feature. +## Opening context + +Settings that control what context is sent in the opening AI request. These are specified under `[ai.opening]`. + ### send_cwd Default: `false` @@ -17,10 +21,23 @@ Whether or not to include your current working directory in the context sent to **Example config** ```toml -[ai] +[ai.opening] send_cwd = true ``` +### send_last_command + +Default: `false` + +Whether or not to send your previous command as context in the initial request, allowing the AI to provide more relevant suggestions. + +**Example config** + +```toml +[ai.opening] +send_last_command = true +``` + ### endpoint Default: `null` -- cgit v1.3.1