aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-ai/src/stream.rs
diff options
context:
space:
mode:
authorMichelle Tilley <michelle@michelletilley.net>2026-04-14 16:03:08 -0700
committerGitHub <noreply@github.com>2026-04-15 00:03:08 +0100
commitfd188da879d977ca847f10708c39dd4801a204c4 (patch)
tree592bfe2644f8bd9be3563f176eabf29e55fa9a9b /crates/atuin-ai/src/stream.rs
parentfix: dependency fix (#3414) (diff)
downloadatuin-fd188da879d977ca847f10708c39dd4801a204c4.zip
feat: Allow resuming previous AI sessions (#3407)
This PR introduces session continuation to Atuin AI. * Conversations with Atuin AI are stored in a local SQLite database * Upon startup, Atuin AI tries to find a session to resume based on its directory/workspace and the time since the last event * If found, Atuin AI will show a note that the session has been resumed, and an event is added to help the LLM know where the invocation boundaries are * If not, Atuin AI will create a new conversation * The user can create a new conversation with `/new` * The new setting `ai.session_continue_minutes`, which defaults to `60`, controls how old the last event in a session can be before it's no longer considered for automatic resuming. <img width="1055" height="593" alt="image" src="https://github.com/user-attachments/assets/3f9ff01a-ef64-44a9-b0e2-3a4252c5746f" /> ## Architecture A new `SessionService` trait defines an API contract for a service that can manage session data. `LocalSessionService` implements this, with `DaemonSessionService` a possible future extension point. `SessionManager` owns a `dyn SessionService` and delegates as appropriate.
Diffstat (limited to 'crates/atuin-ai/src/stream.rs')
-rw-r--r--crates/atuin-ai/src/stream.rs10
1 files changed, 9 insertions, 1 deletions
diff --git a/crates/atuin-ai/src/stream.rs b/crates/atuin-ai/src/stream.rs
index 9c21fc05..f4f4d704 100644
--- a/crates/atuin-ai/src/stream.rs
+++ b/crates/atuin-ai/src/stream.rs
@@ -12,6 +12,7 @@ use eye_declare::Handle;
use eyre::{Context, Result};
use futures::StreamExt;
use reqwest::Url;
+use reqwest::header::USER_AGENT;
use crate::{
context::{AppContext, ClientContext},
@@ -19,6 +20,8 @@ use crate::{
tui::{Session, events::AiTuiEvent},
};
+static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"));
+
/// Frames that alter the stream lifecycle — terminal or state-changing.
#[derive(Debug, Clone)]
pub(crate) enum StreamControl {
@@ -57,6 +60,7 @@ pub(crate) struct ChatRequest {
pub messages: Vec<serde_json::Value>,
pub session_id: Option<String>,
pub capabilities: Vec<String>,
+ pub invocation_id: String,
}
impl ChatRequest {
@@ -64,8 +68,9 @@ impl ChatRequest {
messages: Vec<serde_json::Value>,
session_id: Option<String>,
capabilities: &AiCapabilities,
+ invocation_id: String,
) -> Self {
- let mut caps = vec![];
+ let mut caps = vec!["client_invocations".to_string()];
if capabilities.enable_history_search.unwrap_or(true) {
caps.push("client_v1_atuin_history".to_string());
}
@@ -82,6 +87,7 @@ impl ChatRequest {
messages,
session_id,
capabilities: caps,
+ invocation_id,
}
}
}
@@ -112,6 +118,7 @@ fn create_chat_stream(
"messages": request.messages,
"context": context,
"capabilities": request.capabilities,
+ "invocation_id": request.invocation_id
});
if let Some(ref sid) = request.session_id {
@@ -123,6 +130,7 @@ fn create_chat_stream(
let response = match client
.post(endpoint.clone())
.header("Accept", "text/event-stream")
+ .header(USER_AGENT, APP_USER_AGENT)
.bearer_auth(&token)
.json(&request_body)
.send()