use super::*;
const RECORD_KEY: &str = "__supercode_record";
pub(super) fn stamp_message_records(messages: &mut [ChatMessage], record: usize) {
for message in messages.iter_mut().rev() {
if message.metadata.contains_key(RECORD_KEY) {
break;
}
message
.metadata
.insert(RECORD_KEY.to_string(), record.to_string());
}
}
pub(super) fn take_message_records(messages: &mut [ChatMessage]) -> Vec<Option<usize>> {
messages
.iter_mut()
.map(|message| {
message
.metadata
.remove(RECORD_KEY)
.and_then(|record| record.parse().ok())
})
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResidueSegment {
pub lines: Vec<String>,
pub end_of_source: bool,
pub trailing_newline: bool,
#[serde(default)]
pub digest: String,
#[serde(default)]
pub source: String,
#[serde(default)]
pub source_session: Option<String>,
}
impl ResidueSegment {
fn new(
lines: Vec<String>,
end_of_source: bool,
trailing_newline: bool,
source: &str,
source_session: Option<&str>,
) -> Self {
let digest = segment_digest(&lines);
Self {
lines,
end_of_source,
trailing_newline,
digest,
source: source.to_string(),
source_session: source_session.map(str::to_string),
}
}
pub fn verified(&self) -> bool {
self.digest == segment_digest(&self.lines)
}
}
fn segment_digest(lines: &[String]) -> String {
let mut hasher = blake3::Hasher::new();
for line in lines {
hasher.update(&(line.len() as u64).to_le_bytes());
hasher.update(line.as_bytes());
}
hasher.finalize().to_hex().to_string()
}
#[derive(Debug, Clone)]
pub struct KeyedSegment {
pub key: String,
pub format: SessionFormat,
pub segment: ResidueSegment,
}
fn message_groups(messages: &[ChatMessage]) -> Vec<std::ops::Range<usize>> {
let mut groups: Vec<std::ops::Range<usize>> = Vec::new();
for (index, message) in messages.iter().enumerate() {
match groups.last_mut() {
Some(group) if message.role == Role::Tool => group.end = index + 1,
_ => groups.push(index..index + 1),
}
}
groups
}
fn message_text(message: &ChatMessage) -> String {
match &message.content_parts {
Some(parts) => parts
.iter()
.filter(|part| part.get("type").and_then(Value::as_str) == Some("text"))
.filter_map(|part| part.get("text").and_then(Value::as_str))
.collect(),
None => message.content.clone().unwrap_or_default(),
}
}
fn normalized_arguments(arguments: &str) -> Value {
serde_json::from_str(arguments).unwrap_or_else(|_| Value::String(arguments.to_string()))
}
fn canonical_group(messages: &[ChatMessage]) -> Value {
let head = &messages[0];
let calls = head.tool_calls();
let mut results: Vec<&ChatMessage> = messages[1..].iter().collect();
results.sort_by_key(|result| {
calls
.iter()
.position(|call| Some(call.id.as_str()) == result.tool_call_id.as_deref())
.unwrap_or(usize::MAX)
});
serde_json::json!([
serde_json::to_value(head.role).unwrap_or(Value::Null),
message_text(head),
calls
.iter()
.map(|call| serde_json::json!([
call.function.name,
normalized_arguments(&call.function.arguments)
]))
.collect::<Vec<_>>(),
results
.iter()
.map(|result| message_text(result))
.collect::<Vec<_>>(),
])
}
fn empty_conversation_key() -> String {
blake3::hash(b"supercode-residue-v1").to_hex().to_string()
}
pub fn residue_chain_keys(messages: &[ChatMessage]) -> Vec<(std::ops::Range<usize>, String)> {
let mut previous = empty_conversation_key();
message_groups(messages)
.into_iter()
.map(|group| {
let mut hasher = blake3::Hasher::new();
hasher.update(previous.as_bytes());
hasher.update(
canonical_group(&messages[group.clone()])
.to_string()
.as_bytes(),
);
previous = hasher.finalize().to_hex().to_string();
(group, previous.clone())
})
.collect()
}
impl Session {
pub fn residue_segments(&self) -> Vec<KeyedSegment> {
let Some(format) = SessionFormat::for_source(self.meta.source) else {
return Vec::new();
};
if !self.raw_is_verbatim || self.meta.message_records.len() != self.messages.len() {
return Vec::new();
}
let keyed = residue_chain_keys(&self.messages);
let source = {
let mut hasher = blake3::Hasher::new();
hasher.update(format!("{format:?}").as_bytes());
hasher.update(self.meta.session_id.as_deref().unwrap_or("").as_bytes());
hasher.update(
self.raw
.first()
.map(String::as_str)
.unwrap_or("")
.as_bytes(),
);
hasher.finalize().to_hex().to_string()
};
if keyed.is_empty() {
if self.raw.is_empty() {
return Vec::new();
}
return vec![KeyedSegment {
key: empty_conversation_key(),
format,
segment: ResidueSegment::new(
self.raw.clone(),
true,
self.raw_trailing_newline,
&source,
self.meta.session_id.as_deref(),
),
}];
}
let mut starts = Vec::new();
for (group, _) in &keyed {
match self.meta.message_records[group.start] {
Some(record) => starts.push(record),
None => break,
}
}
let mut segments = Vec::new();
for (index, (_, key)) in keyed.iter().take(starts.len()).enumerate() {
let from = if index == 0 { 0 } else { starts[index] };
let end_of_source = index + 1 == keyed.len();
let to = if end_of_source {
self.raw.len()
} else {
starts.get(index + 1).copied().unwrap_or(self.raw.len())
};
if from > to || to > self.raw.len() {
break;
}
segments.push(KeyedSegment {
key: key.clone(),
format,
segment: ResidueSegment::new(
self.raw[from..to].to_vec(),
end_of_source,
self.raw_trailing_newline,
&source,
self.meta.session_id.as_deref(),
),
});
}
segments
}
pub fn restore_residue(
&self,
format: SessionFormat,
lookup: impl Fn(SessionFormat, &str) -> Vec<ResidueSegment>,
) -> Result<Option<(String, ResidueRestore)>> {
let keyed = residue_chain_keys(&self.messages);
if keyed.is_empty() {
return Ok(self.restore_empty_conversation(format, &lookup));
}
let Some(source) = self.residue_source(format, &lookup, &keyed) else {
return Ok(None);
};
let lookup = |format: SessionFormat, key: &str| {
lookup(format, key)
.into_iter()
.find(|segment| segment.source == source && segment.verified())
};
let mut limit = keyed.len();
loop {
match self.restore_prefix(format, &lookup, &keyed, limit)? {
Restored::Sound(text, report) => return Ok(Some((text, report))),
Restored::Nothing => return Ok(None),
Restored::Unsound {
first_mismatch,
restored_groups,
} => {
let shorter = first_mismatch.min(restored_groups.saturating_sub(1));
if shorter == 0 || shorter >= limit {
return Ok(None);
}
limit = shorter;
}
}
}
}
fn residue_source(
&self,
format: SessionFormat,
lookup: &impl Fn(SessionFormat, &str) -> Vec<ResidueSegment>,
keyed: &[(std::ops::Range<usize>, String)],
) -> Option<String> {
let first = keyed.first()?;
let own = self.meta.session_id.as_deref();
let own_recorded = own.filter(|_| self.meta.source != SessionSource::Grok);
let mut best: Vec<(String, Option<String>, usize, Vec<String>)> = Vec::new();
let candidates: Vec<ResidueSegment> = lookup(format, &first.1)
.into_iter()
.filter(ResidueSegment::verified)
.filter(|candidate| {
own_recorded.is_none()
|| candidate.source_session.is_none()
|| candidate.source_session.as_deref() == own_recorded
})
.collect();
for candidate in &candidates {
let mut matched = 0;
let mut lines = Vec::new();
for (_, key) in keyed {
let Some(segment) = lookup(format, key)
.into_iter()
.find(|segment| segment.source == candidate.source && segment.verified())
else {
break;
};
matched += 1;
let end = segment.end_of_source;
lines.extend(segment.lines);
if end {
break;
}
}
best.push((
candidate.source.clone(),
candidate.source_session.clone(),
matched,
lines,
));
}
let longest = best.iter().map(|(_, _, matched, _)| *matched).max()?;
best.retain(|(_, _, matched, _)| *matched == longest);
if best.len() > 1 {
if own.is_some()
&& best
.iter()
.any(|(_, session, _, _)| session.as_deref() == own)
{
best.retain(|(_, session, _, _)| session.as_deref() == own);
}
}
let (source, _, _, lines) = best.first()?;
best.iter()
.all(|(_, _, _, other)| other == lines)
.then(|| source.clone())
}
fn restore_empty_conversation(
&self,
format: SessionFormat,
lookup: &impl Fn(SessionFormat, &str) -> Vec<ResidueSegment>,
) -> Option<(String, ResidueRestore)> {
let mut candidates: Vec<ResidueSegment> = lookup(format, &empty_conversation_key())
.into_iter()
.filter(ResidueSegment::verified)
.collect();
let own = self.meta.session_id.as_deref();
if candidates.len() > 1 && own.is_some() {
candidates.retain(|segment| segment.source_session.as_deref() == own);
}
let [segment] = candidates.as_slice() else {
return None;
};
let mut text = segment.lines.join("\n");
if segment.trailing_newline {
text.push('\n');
}
let written = Session::load_str(&text, format).ok()?;
written.messages.is_empty().then_some((
text,
ResidueRestore {
restored_messages: 0,
rendered_messages: 0,
},
))
}
fn restore_prefix(
&self,
format: SessionFormat,
lookup: &impl Fn(SessionFormat, &str) -> Option<ResidueSegment>,
keyed: &[(std::ops::Range<usize>, String)],
limit: usize,
) -> Result<Restored> {
let mut lines = Vec::new();
let mut restored_groups = 0;
let mut restored_messages = 0;
let mut whole_source = None;
for (group, key) in keyed.iter().take(limit) {
let Some(segment) = lookup(format, key) else {
break;
};
lines.extend(segment.lines);
restored_groups += 1;
restored_messages = group.end;
if segment.end_of_source {
whole_source = Some(segment.trailing_newline);
break;
}
}
if restored_groups == 0 {
return Ok(Restored::Nothing);
}
let report = ResidueRestore {
restored_messages,
rendered_messages: self.messages.len() - restored_messages,
};
let text = if let (Some(trailing_newline), 0) = (whole_source, report.rendered_messages) {
let mut text = lines.join("\n");
if trailing_newline {
text.push('\n');
}
text
} else {
let mut spliced = self.clone();
spliced.meta.source = format.source();
lines.extend(std::iter::repeat_n(String::new(), report.rendered_messages));
spliced.raw = lines;
spliced.raw_is_verbatim = true;
spliced.imported_message_count = Some(restored_messages);
spliced.to_jsonl_spliced(format, None)?
};
let Ok(written) = Session::load_str(&text, format) else {
return Ok(Restored::Unsound {
first_mismatch: 0,
restored_groups,
});
};
let actual = residue_chain_keys(&written.messages);
let first_mismatch = keyed
.iter()
.zip(actual.iter())
.position(|((_, expected), (_, actual))| expected != actual)
.unwrap_or_else(|| keyed.len().min(actual.len()));
if first_mismatch < keyed.len() || actual.len() != keyed.len() {
return Ok(Restored::Unsound {
first_mismatch,
restored_groups,
});
}
Ok(Restored::Sound(text, report))
}
}
enum Restored {
Sound(String, ResidueRestore),
Unsound {
first_mismatch: usize,
restored_groups: usize,
},
Nothing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResidueRestore {
pub restored_messages: usize,
pub rendered_messages: usize,
}