Skip to content

Commit a1922db

Browse files
authored
Merge pull request #486 from stakpak/perf/async-session-title-generation
perf(session): async session title generation for faster startup
2 parents d865739 + 91831c0 commit a1922db

3 files changed

Lines changed: 53 additions & 21 deletions

File tree

cli/src/commands/agent/run/mode_interactive.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,16 @@ pub async fn run_interactive(
941941
Ok(response) => {
942942
messages.push(response.choices[0].message.clone());
943943

944+
if let Some(session_id) = response
945+
.metadata
946+
.as_ref()
947+
.and_then(|meta| meta.get("session_id"))
948+
.and_then(|value| value.as_str())
949+
.and_then(|value| Uuid::parse_str(value).ok())
950+
{
951+
current_session_id = Some(session_id);
952+
}
953+
944954
// Accumulate usage from response
945955
total_session_usage.prompt_tokens += response.usage.prompt_tokens;
946956
total_session_usage.completion_tokens += response.usage.completion_tokens;

cli/src/commands/agent/run/stream.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ pub async fn process_responses_stream(
122122
system_fingerprint: None,
123123
metadata: None,
124124
};
125+
let mut response_metadata: Option<serde_json::Value> = None;
125126

126127
let mut llm_model: Option<LLMModel> = None;
127128

@@ -154,6 +155,9 @@ pub async fn process_responses_stream(
154155
// Send usage to TUI for display immediately when we receive it
155156
send_input_event(input_tx, InputEvent::StreamUsage(usage.clone())).await?;
156157
}
158+
if let Some(metadata) = &response.metadata {
159+
response_metadata = Some(metadata.clone());
160+
}
157161

158162
// Skip chunks with no choices (e.g., usage-only events)
159163
if response.choices.is_empty() {
@@ -239,6 +243,7 @@ pub async fn process_responses_stream(
239243
finish_reason: FinishReason::Stop,
240244
logprobs: None,
241245
});
246+
chat_completion_response.metadata = response_metadata;
242247

243248
// End stream processing loading when stream completes
244249
send_input_event(

libs/api/src/client/provider.rs

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::models::*;
1010
use crate::storage::{
1111
CreateCheckpointRequest as StorageCreateCheckpointRequest,
1212
CreateSessionRequest as StorageCreateSessionRequest,
13+
UpdateSessionRequest as StorageUpdateSessionRequest,
1314
};
1415
use async_trait::async_trait;
1516
use futures_util::Stream;
@@ -664,34 +665,17 @@ impl AgentClient {
664665
});
665666
}
666667

667-
// Create new session
668-
// Generate title with fallback - don't fail session creation if title generation fails
669-
let title = match self.generate_session_title(messages).await {
670-
Ok(title) => title,
671-
Err(_) => {
672-
// Extract first few words from user message as fallback title
673-
messages
674-
.iter()
675-
.find(|m| m.role == Role::User)
676-
.and_then(|m| m.content.as_ref())
677-
.map(|c| {
678-
let text = c.to_string();
679-
text.split_whitespace()
680-
.take(5)
681-
.collect::<Vec<_>>()
682-
.join(" ")
683-
})
684-
.unwrap_or_else(|| "New Session".to_string())
685-
}
686-
};
668+
// Create new session with a fast local title.
669+
let fallback_title = Self::fallback_session_title(messages);
687670

688671
// Get current working directory
689672
let cwd = std::env::current_dir()
690673
.ok()
691674
.map(|p| p.to_string_lossy().to_string());
692675

693676
// Create session via storage trait
694-
let mut session_request = StorageCreateSessionRequest::new(title, messages.to_vec());
677+
let mut session_request =
678+
StorageCreateSessionRequest::new(fallback_title.clone(), messages.to_vec());
695679
if let Some(cwd) = cwd {
696680
session_request = session_request.with_cwd(cwd);
697681
}
@@ -702,13 +686,46 @@ impl AgentClient {
702686
.await
703687
.map_err(|e| e.to_string())?;
704688

689+
// Generate a better title asynchronously and update the session when ready.
690+
let client = self.clone();
691+
let messages_for_title = messages.to_vec();
692+
let session_id = result.session_id;
693+
tokio::spawn(async move {
694+
if let Ok(title) = client.generate_session_title(&messages_for_title).await {
695+
let trimmed = title.trim();
696+
if !trimmed.is_empty() && trimmed != fallback_title {
697+
let request =
698+
StorageUpdateSessionRequest::new().with_title(trimmed.to_string());
699+
let _ = client
700+
.session_storage
701+
.update_session(session_id, &request)
702+
.await;
703+
}
704+
}
705+
});
706+
705707
Ok(SessionInfo {
706708
session_id: result.session_id,
707709
checkpoint_id: result.checkpoint.id,
708710
checkpoint_created_at: result.checkpoint.created_at,
709711
})
710712
}
711713

714+
fn fallback_session_title(messages: &[ChatMessage]) -> String {
715+
messages
716+
.iter()
717+
.find(|m| m.role == Role::User)
718+
.and_then(|m| m.content.as_ref())
719+
.map(|c| {
720+
let text = c.to_string();
721+
text.split_whitespace()
722+
.take(5)
723+
.collect::<Vec<_>>()
724+
.join(" ")
725+
})
726+
.unwrap_or_else(|| "New Session".to_string())
727+
}
728+
712729
/// Save a new checkpoint for the current session
713730
pub(crate) async fn save_checkpoint(
714731
&self,

0 commit comments

Comments
 (0)