diff options
| author | Ellie Huxtable <ellie@atuin.sh> | 2026-03-31 04:18:29 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-31 04:18:29 +0100 |
| commit | 97b5a6c3247299e6863b17bf0b6e125692d33766 (patch) | |
| tree | 4f452be07c75202c6b0b81cb981a083895dc5c01 /crates | |
| parent | fix(ui): make preview line breaking algorithm aware of CJK double-width chara... (diff) | |
| download | atuin-97b5a6c3247299e6863b17bf0b6e125692d33766.zip | |
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
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/atuin-ai/src/commands/inline.rs | 37 | ||||
| -rw-r--r-- | crates/atuin-client/src/settings.rs | 18 |
2 files changed, 50 insertions, 5 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<String>, messages: Vec<serde_json::Value>, send_cwd: bool, + last_command: Option<String>, ) -> std::pin::Pin<Box<dyn futures::Stream<Item = Result<ChatStreamEvent>> + 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<String>, messages: Vec<serde_json::Value>, send_cwd: bool, + last_command: Option<String>, ) { - 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<String>, + /// Deprecated: use opening.send_cwd instead. Kept for backwards compatibility. + #[serde(default)] + pub send_cwd: Option<bool>, + + /// 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<bool>, + + /// Whether or not to send the last command as context in the opening AI request. + pub send_last_command: Option<bool>, } 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