mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-06-21 04:33:36 +08:00
fix: keep tool-use/result pairs intact during compaction
Preserve assistant tool_use / user tool_result adjacency when compaction trims message history. Fixes #325. Squashed original PR commits: - Fix compaction breaking tool-use/result pairs - Simplify compaction boundary fix
This commit is contained in:
@@ -1060,6 +1060,26 @@ def spawn_subagent(description: str) -> str:
|
||||
def estimate_size(messages: list) -> int:
|
||||
return len(json.dumps(messages, default=str))
|
||||
|
||||
def block_type(block):
|
||||
return getattr(block, "type", None) if not isinstance(block, dict) else block.get("type")
|
||||
|
||||
def has_tool_use(message: dict) -> bool:
|
||||
if message.get("role") != "assistant":
|
||||
return False
|
||||
content = message.get("content")
|
||||
if not isinstance(content, list):
|
||||
return False
|
||||
return any(block_type(block) == "tool_use" for block in content)
|
||||
|
||||
def is_tool_result_message(message: dict) -> bool:
|
||||
if message.get("role") != "user":
|
||||
return False
|
||||
content = message.get("content")
|
||||
if not isinstance(content, list):
|
||||
return False
|
||||
return any(isinstance(block, dict) and block.get("type") == "tool_result"
|
||||
for block in content)
|
||||
|
||||
|
||||
def collect_tool_results(messages: list):
|
||||
found = []
|
||||
@@ -1111,11 +1131,18 @@ def tool_result_budget(messages: list, max_bytes: int = 200_000) -> list:
|
||||
def snip_compact(messages: list, max_messages: int = 50) -> list:
|
||||
if len(messages) <= max_messages:
|
||||
return messages
|
||||
keep_head, keep_tail = 3, max_messages - 3
|
||||
snipped = len(messages) - keep_head - keep_tail
|
||||
return (messages[:keep_head]
|
||||
head_end, tail_start = 3, len(messages) - (max_messages - 3)
|
||||
if head_end > 0 and has_tool_use(messages[head_end - 1]):
|
||||
while head_end < len(messages) and is_tool_result_message(messages[head_end]):
|
||||
head_end += 1
|
||||
if tail_start > 0 and tail_start < len(messages) and is_tool_result_message(messages[tail_start]) and has_tool_use(messages[tail_start - 1]):
|
||||
tail_start -= 1
|
||||
if head_end >= tail_start:
|
||||
return messages
|
||||
snipped = tail_start - head_end
|
||||
return (messages[:head_end]
|
||||
+ [{"role": "user", "content": f"[snipped {snipped} messages]"}]
|
||||
+ messages[-keep_tail:])
|
||||
+ messages[tail_start:])
|
||||
|
||||
|
||||
def micro_compact(messages: list) -> list:
|
||||
@@ -1163,8 +1190,11 @@ def reactive_compact(messages: list) -> list:
|
||||
summary = summarize_history(messages)
|
||||
except Exception:
|
||||
summary = "Earlier conversation was trimmed after a prompt-too-long error."
|
||||
tail_start = max(0, len(messages) - 5)
|
||||
if tail_start > 0 and tail_start < len(messages) and is_tool_result_message(messages[tail_start]) and has_tool_use(messages[tail_start - 1]):
|
||||
tail_start -= 1
|
||||
return [{"role": "user", "content": f"[Reactive compact]\n\n{summary}"},
|
||||
*messages[-5:]]
|
||||
*messages[tail_start:]]
|
||||
|
||||
|
||||
# ── Error Recovery ──
|
||||
|
||||
Reference in New Issue
Block a user