use super::*;
#[cfg(test)]
thread_local! {
static PROJECT_SPAN_VISITS: Cell<usize> = const { Cell::new(0) };
}
#[cfg(test)]
pub(crate) fn take_project_span_visits_for_test() -> usize {
PROJECT_SPAN_VISITS.with(|count| count.replace(0))
}
pub(crate) struct ProjectedTranscriptCard {
pub(crate) card: Arc<TranscriptCard>,
pub(crate) first_entry_index: usize,
pub(crate) content_marker: u64,
}
fn project_card_span(
state: &MissionControlState,
first: usize,
end: usize,
) -> ProjectedTranscriptCard {
#[cfg(test)]
PROJECT_SPAN_VISITS.with(|count| count.set(count.get() + 1));
let first_entry = state.transcript.get(first).expect("transcript entry");
let mut content_marker = transcript_card_content_marker(state, first, first_entry);
if first_entry.kind().is_thinking() && end > first + 1 {
for index in first + 1..end {
content_marker = hash_combine(content_marker, state.transcript_entry_revision(index));
}
}
content_marker = hash_combine(content_marker, end.saturating_sub(first) as u64);
let card = cached_project_entry(state, first, end, first_entry, content_marker);
ProjectedTranscriptCard {
card,
first_entry_index: first,
content_marker,
}
}
fn next_forward_span(state: &MissionControlState, first: usize) -> usize {
if !state
.transcript
.get(first)
.is_some_and(|entry| entry.kind().is_thinking())
{
return first + 1;
}
let mut end = first + 1;
while state
.transcript
.get(end)
.is_some_and(|entry| entry.kind().is_thinking())
{
end += 1;
}
end
}
pub(crate) fn project_card_spans(
state: &MissionControlState,
) -> impl Iterator<Item = ProjectedTranscriptCard> + '_ {
let mut index = 0;
std::iter::from_fn(move || {
if index >= state.transcript.len() {
return None;
}
let end = next_forward_span(state, index);
let span = project_card_span(state, index, end);
index = end;
Some(span)
})
}
#[cfg(test)]
pub(crate) fn project_card_spans_reverse(
state: &MissionControlState,
) -> impl Iterator<Item = ProjectedTranscriptCard> + '_ {
project_card_spans_reverse_from(state, state.transcript.len())
}
pub(crate) fn project_card_spans_reverse_from(
state: &MissionControlState,
mut end: usize,
) -> impl Iterator<Item = ProjectedTranscriptCard> + '_ {
std::iter::from_fn(move || {
if end == 0 {
return None;
}
let mut first = end - 1;
while first > 0
&& state
.transcript
.get(first - 1)
.is_some_and(|entry| entry.kind().is_thinking())
&& state
.transcript
.get(first)
.is_some_and(|entry| entry.kind().is_thinking())
{
first -= 1;
}
let span = project_card_span(state, first, end);
end = first;
Some(span)
})
}
fn cached_project_entry(
state: &MissionControlState,
entry_index: usize,
end_entry_index: usize,
entry: &TranscriptEntry,
content_marker: u64,
) -> Arc<TranscriptCard> {
let cache_index = state
.transcript_absolute_index(entry_index)
.unwrap_or(entry_index);
if let Some(cached) = state.transcript_cache.borrow().cards.get(&cache_index)
&& cached.content_marker == content_marker
{
return Arc::clone(&cached.card);
}
let mut card = normalize_projected_card(project_entry(state, entry_index, entry));
if card.role == TranscriptCardRole::Reasoning && end_entry_index > entry_index + 1 {
card.body = (entry_index..end_entry_index)
.filter_map(|index| state.transcript.get(index))
.map(|entry| entry.body().trim_end_matches('\n'))
.collect::<Vec<_>>()
.join("\n\n");
}
let card = Arc::new(card);
state.transcript_cache.borrow_mut().cards.insert(
cache_index,
transcript::CachedTranscriptCard {
content_marker,
card: Arc::clone(&card),
},
);
card
}
#[cfg(test)]
pub(crate) fn cached_card_ptr_for_test(
state: &MissionControlState,
entry_index: usize,
) -> *const TranscriptCard {
Arc::as_ptr(&project_card_span(state, entry_index, next_forward_span(state, entry_index)).card)
}
fn transcript_card_content_marker(
state: &MissionControlState,
entry_index: usize,
_entry: &TranscriptEntry,
) -> u64 {
let mut hash = hash_combine(
0x6a09_e667_f3bc_c909,
state.transcript_entry_revision(entry_index),
);
if state.active_assistant_transcript_index() == Some(entry_index) {
hash ^= 0x9e37_79b9_7f4a_7c15;
}
let plain_first_marker = state.final_assistant_plain_first_marker(entry_index);
match plain_first_marker {
1 => {
hash ^= 0x517c_c1b7_dae5_8a2d;
if state.transcript_selection_active() {
hash ^= 0x3c8b_a73d_142f_e9b6;
}
}
2 => hash ^= 0xa24f_9c3e_65b1_d47a,
_ => {}
}
if let Some(timestamp) = state.transcript_timestamp(entry_index) {
hash = hash_combine(hash, timestamp.timestamp() as u64);
hash = hash_combine(hash, u64::from(timestamp.timestamp_subsec_nanos()));
}
if let Some(absolute_index) = state.transcript_absolute_index(entry_index)
&& let Some(link) = state.transcript.activity_links().get(&absolute_index)
{
hash = hash_combine(hash, stable_hash(link.activity_id.as_str().as_bytes()));
hash = hash_combine(hash, stable_hash(link.tool_name.as_bytes()));
let tool_capability = ToolCapability::from_dispatch_name(&link.tool_name);
if tool_capability == Some(ToolCapability::Bash) {
let bash_mode_response =
is_bash_mode_response_card(state, entry_index, tool_capability);
hash = hash_combine(hash, if bash_mode_response { 1 } else { 0 });
}
}
hash
}
fn hash_combine(left: u64, right: u64) -> u64 {
left ^ right
.wrapping_add(0x9e37_79b9_7f4a_7c15)
.wrapping_add(left << 6)
.wrapping_add(left >> 2)
}