修复并行命令的执行结果没有对应到命令块
This commit is contained in:
@@ -558,7 +558,8 @@ impl ClaudeParser {
|
||||
let folder_path = cwd.clone();
|
||||
let folder_name = folder_path.as_ref().map(|p| folder_name_from_path(p));
|
||||
|
||||
let turns = group_into_turns(messages);
|
||||
let mut turns = group_into_turns(messages);
|
||||
super::relocate_orphaned_tool_results(&mut turns);
|
||||
let context_window_used_tokens = latest_claude_context_window_used_tokens(&turns);
|
||||
let context_window_max_tokens =
|
||||
claude_context_window_max_tokens_for_model(model.as_deref());
|
||||
|
||||
@@ -771,7 +771,8 @@ impl CodexParser {
|
||||
let folder_path = cwd.clone();
|
||||
let folder_name = folder_path.as_ref().map(|p| folder_name_from_path(p));
|
||||
|
||||
let turns = group_into_turns(messages);
|
||||
let mut turns = group_into_turns(messages);
|
||||
super::relocate_orphaned_tool_results(&mut turns);
|
||||
let mut session_stats = super::compute_session_stats(&turns);
|
||||
session_stats =
|
||||
merge_codex_total_usage_stats(session_stats, latest_total_usage, latest_total_tokens);
|
||||
|
||||
@@ -554,7 +554,8 @@ impl GeminiParser {
|
||||
}
|
||||
}
|
||||
|
||||
let turns = group_into_turns(messages);
|
||||
let mut turns = group_into_turns(messages);
|
||||
super::relocate_orphaned_tool_results(&mut turns);
|
||||
summary.message_count = turns.len() as u32;
|
||||
summary.id = conversation_id.to_string();
|
||||
let context_window_used_tokens = super::latest_turn_total_usage_tokens(&turns);
|
||||
|
||||
@@ -4,12 +4,13 @@ pub mod gemini;
|
||||
pub mod openclaw;
|
||||
pub mod opencode;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::models::{
|
||||
ConversationDetail, ConversationSummary, MessageTurn, SessionStats, TurnUsage,
|
||||
ContentBlock, ConversationDetail, ConversationSummary, MessageTurn, SessionStats, TurnUsage,
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -206,6 +207,91 @@ pub fn merge_context_window_stats(
|
||||
}
|
||||
}
|
||||
|
||||
/// Relocate orphaned tool_result blocks to the turn that contains their matching tool_use.
|
||||
///
|
||||
/// After `group_into_turns` splits assistant rounds, async tool execution can cause
|
||||
/// a tool_result to land in a later turn than its corresponding tool_use.
|
||||
/// This post-processing step moves such orphaned results back.
|
||||
pub fn relocate_orphaned_tool_results(turns: &mut Vec<MessageTurn>) {
|
||||
// Build map: tool_use_id → turn index
|
||||
let mut tool_use_turn: HashMap<String, usize> = HashMap::new();
|
||||
for (idx, turn) in turns.iter().enumerate() {
|
||||
for block in &turn.blocks {
|
||||
if let ContentBlock::ToolUse {
|
||||
tool_use_id: Some(ref id),
|
||||
..
|
||||
} = block
|
||||
{
|
||||
tool_use_turn.insert(id.clone(), idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tool_use_turn.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect (source_turn, target_turn, block) for orphaned results
|
||||
let mut relocations: Vec<(usize, usize, ContentBlock)> = Vec::new();
|
||||
for (turn_idx, turn) in turns.iter().enumerate() {
|
||||
for block in &turn.blocks {
|
||||
if let ContentBlock::ToolResult {
|
||||
tool_use_id: Some(ref id),
|
||||
..
|
||||
} = block
|
||||
{
|
||||
if let Some(&target) = tool_use_turn.get(id) {
|
||||
if target != turn_idx {
|
||||
relocations.push((turn_idx, target, block.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if relocations.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build set of (turn_idx, tool_use_id) to remove
|
||||
let remove_set: HashMap<usize, Vec<String>> = {
|
||||
let mut map: HashMap<usize, Vec<String>> = HashMap::new();
|
||||
for (from, _, block) in &relocations {
|
||||
if let ContentBlock::ToolResult {
|
||||
tool_use_id: Some(ref id),
|
||||
..
|
||||
} = block
|
||||
{
|
||||
map.entry(*from).or_default().push(id.clone());
|
||||
}
|
||||
}
|
||||
map
|
||||
};
|
||||
|
||||
// Remove from source turns
|
||||
for (&turn_idx, ids) in &remove_set {
|
||||
turns[turn_idx].blocks.retain(|block| {
|
||||
if let ContentBlock::ToolResult {
|
||||
tool_use_id: Some(ref id),
|
||||
..
|
||||
} = block
|
||||
{
|
||||
!ids.contains(id)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Append to target turns
|
||||
for (_, target, block) in relocations {
|
||||
turns[target].blocks.push(block);
|
||||
}
|
||||
|
||||
// Remove turns that became empty after relocation
|
||||
turns.retain(|turn| !turn.blocks.is_empty());
|
||||
}
|
||||
|
||||
/// Extract the last path component as the folder name.
|
||||
pub fn folder_name_from_path(path: &str) -> String {
|
||||
path.rsplit(['/', '\\']).next().unwrap_or(path).to_string()
|
||||
|
||||
@@ -650,7 +650,8 @@ impl OpenClawParser {
|
||||
|
||||
let folder_path = cwd.clone();
|
||||
let folder_name = folder_path.as_ref().map(|p| folder_name_from_path(p));
|
||||
let turns = group_into_turns(messages);
|
||||
let mut turns = group_into_turns(messages);
|
||||
super::relocate_orphaned_tool_results(&mut turns);
|
||||
|
||||
let context_window_used_tokens = latest_turn_total_usage_tokens(&turns);
|
||||
let context_window_max_tokens = session_meta
|
||||
|
||||
@@ -187,7 +187,8 @@ impl OpenCodeParser {
|
||||
.ok_or_else(|| ParseError::ConversationNotFound(conversation_id.to_string()))?;
|
||||
|
||||
let messages = self.load_sqlite_messages(&conn, conversation_id).await?;
|
||||
let turns = group_into_turns(messages);
|
||||
let mut turns = group_into_turns(messages);
|
||||
super::relocate_orphaned_tool_results(&mut turns);
|
||||
let context_window_used_tokens = super::latest_turn_total_usage_tokens(&turns);
|
||||
let context_window_max_tokens =
|
||||
super::infer_context_window_max_tokens(summary.model.as_deref());
|
||||
|
||||
Reference in New Issue
Block a user