fix(session-recovery): Replace empty text parts before injecting new ones

Directly modify empty text parts in storage files before attempting
to inject new parts. This ensures that existing empty text parts are
replaced with placeholder text, fixing the issue where Anthropic API
returns 'messages.X: all messages must have non-empty content' error
even after recovery.

- Added replaceEmptyTextParts function to directly replace empty text parts
- Added findMessagesWithEmptyTextParts function to identify affected messages
- Modified recoverEmptyContentMessage to prioritize replacing existing empty parts

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2025-12-16 21:02:38 +09:00
parent cb360e0d05
commit 838f49bc42
2 changed files with 74 additions and 3 deletions

View File

@@ -4,12 +4,14 @@ import {
findEmptyMessages,
findEmptyMessageByIndex,
findMessageByIndexNeedingThinking,
findMessagesWithEmptyTextParts,
findMessagesWithOrphanThinking,
findMessagesWithThinkingBlocks,
findMessagesWithThinkingOnly,
injectTextPart,
prependThinkingPart,
readParts,
replaceEmptyTextParts,
stripThinkingParts,
} from "./storage"
import type { MessageData } from "./types"
@@ -222,28 +224,48 @@ async function recoverEmptyContentMessage(
): Promise<boolean> {
const targetIndex = extractMessageIndex(error)
const failedID = failedAssistantMsg.info?.id
let anySuccess = false
const messagesWithEmptyText = findMessagesWithEmptyTextParts(sessionID)
for (const messageID of messagesWithEmptyText) {
if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) {
anySuccess = true
}
}
const thinkingOnlyIDs = findMessagesWithThinkingOnly(sessionID)
for (const messageID of thinkingOnlyIDs) {
injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)
if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
anySuccess = true
}
}
if (targetIndex !== null) {
const targetMessageID = findEmptyMessageByIndex(sessionID, targetIndex)
if (targetMessageID) {
return injectTextPart(sessionID, targetMessageID, PLACEHOLDER_TEXT)
if (replaceEmptyTextParts(targetMessageID, PLACEHOLDER_TEXT)) {
return true
}
if (injectTextPart(sessionID, targetMessageID, PLACEHOLDER_TEXT)) {
return true
}
}
}
if (failedID) {
if (replaceEmptyTextParts(failedID, PLACEHOLDER_TEXT)) {
return true
}
if (injectTextPart(sessionID, failedID, PLACEHOLDER_TEXT)) {
return true
}
}
const emptyMessageIDs = findEmptyMessages(sessionID)
let anySuccess = thinkingOnlyIDs.length > 0
for (const messageID of emptyMessageIDs) {
if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) {
anySuccess = true
}
if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
anySuccess = true
}