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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -271,6 +271,55 @@ export function stripThinkingParts(messageID: string): boolean {
|
||||
return anyRemoved
|
||||
}
|
||||
|
||||
export function replaceEmptyTextParts(messageID: string, replacementText: string): boolean {
|
||||
const partDir = join(PART_STORAGE, messageID)
|
||||
if (!existsSync(partDir)) return false
|
||||
|
||||
let anyReplaced = false
|
||||
for (const file of readdirSync(partDir)) {
|
||||
if (!file.endsWith(".json")) continue
|
||||
try {
|
||||
const filePath = join(partDir, file)
|
||||
const content = readFileSync(filePath, "utf-8")
|
||||
const part = JSON.parse(content) as StoredPart
|
||||
|
||||
if (part.type === "text") {
|
||||
const textPart = part as StoredTextPart
|
||||
if (!textPart.text?.trim()) {
|
||||
textPart.text = replacementText
|
||||
textPart.synthetic = true
|
||||
writeFileSync(filePath, JSON.stringify(textPart, null, 2))
|
||||
anyReplaced = true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return anyReplaced
|
||||
}
|
||||
|
||||
export function findMessagesWithEmptyTextParts(sessionID: string): string[] {
|
||||
const messages = readMessages(sessionID)
|
||||
const result: string[] = []
|
||||
|
||||
for (const msg of messages) {
|
||||
const parts = readParts(msg.id)
|
||||
const hasEmptyTextPart = parts.some((p) => {
|
||||
if (p.type !== "text") return false
|
||||
const textPart = p as StoredTextPart
|
||||
return !textPart.text?.trim()
|
||||
})
|
||||
|
||||
if (hasEmptyTextPart) {
|
||||
result.push(msg.id)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function findMessageByIndexNeedingThinking(sessionID: string, targetIndex: number): string | null {
|
||||
const messages = readMessages(sessionID)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user