feat: wire context-window-recovery callers to async SDK/HTTP variants on SQLite
- empty-content-recovery: isSqliteBackend() branch delegating to extracted empty-content-recovery-sdk.ts with SDK message scanning - message-builder: sanitizeEmptyMessagesBeforeSummarize now async with SDK path using replaceEmptyTextPartsAsync/injectTextPartAsync - target-token-truncation: truncateUntilTargetTokens now async with SDK path using findToolResultsBySizeFromSDK/truncateToolResultAsync - aggressive-truncation-strategy: passes client to truncateUntilTargetTokens - summarize-retry-strategy: await sanitizeEmptyMessagesBeforeSummarize - client.ts: derive Client from PluginInput['client'] instead of manual defs - executor.test.ts: .mockReturnValue() → .mockResolvedValue() for async fns - storage.test.ts: add await for async truncateUntilTargetTokens
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
import { replaceEmptyTextPartsAsync } from "../session-recovery/storage/empty-text"
|
||||
import { injectTextPartAsync } from "../session-recovery/storage/text-part-injector"
|
||||
import type { Client } from "./client"
|
||||
|
||||
interface SDKPart {
|
||||
id?: string
|
||||
type?: string
|
||||
text?: string
|
||||
}
|
||||
|
||||
interface SDKMessage {
|
||||
info?: { id?: string }
|
||||
parts?: SDKPart[]
|
||||
}
|
||||
|
||||
const IGNORE_TYPES = new Set(["thinking", "redacted_thinking", "meta"])
|
||||
const TOOL_TYPES = new Set(["tool", "tool_use", "tool_result"])
|
||||
|
||||
function messageHasContentFromSDK(message: SDKMessage): boolean {
|
||||
const parts = message.parts
|
||||
if (!parts || parts.length === 0) return false
|
||||
|
||||
for (const part of parts) {
|
||||
const type = part.type
|
||||
if (!type) continue
|
||||
if (IGNORE_TYPES.has(type)) continue
|
||||
|
||||
if (type === "text") {
|
||||
if (part.text?.trim()) return true
|
||||
continue
|
||||
}
|
||||
|
||||
if (TOOL_TYPES.has(type)) return true
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function getSdkMessages(response: unknown): SDKMessage[] {
|
||||
if (typeof response !== "object" || response === null) return []
|
||||
const record = response as Record<string, unknown>
|
||||
const data = record["data"]
|
||||
return Array.isArray(data) ? (data as SDKMessage[]) : []
|
||||
}
|
||||
|
||||
async function findEmptyMessagesFromSDK(client: Client, sessionID: string): Promise<string[]> {
|
||||
try {
|
||||
const response = await client.session.messages({ path: { id: sessionID } })
|
||||
const messages = getSdkMessages(response)
|
||||
|
||||
const emptyIds: string[] = []
|
||||
for (const message of messages) {
|
||||
const messageID = message.info?.id
|
||||
if (!messageID) continue
|
||||
if (!messageHasContentFromSDK(message)) {
|
||||
emptyIds.push(messageID)
|
||||
}
|
||||
}
|
||||
|
||||
return emptyIds
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function findEmptyMessageByIndexFromSDK(
|
||||
client: Client,
|
||||
sessionID: string,
|
||||
targetIndex: number,
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const response = await client.session.messages({ path: { id: sessionID } })
|
||||
const messages = getSdkMessages(response)
|
||||
|
||||
const indicesToTry = [
|
||||
targetIndex,
|
||||
targetIndex - 1,
|
||||
targetIndex + 1,
|
||||
targetIndex - 2,
|
||||
targetIndex + 2,
|
||||
targetIndex - 3,
|
||||
targetIndex - 4,
|
||||
targetIndex - 5,
|
||||
]
|
||||
|
||||
for (const index of indicesToTry) {
|
||||
if (index < 0 || index >= messages.length) continue
|
||||
|
||||
const targetMessage = messages[index]
|
||||
const targetMessageId = targetMessage?.info?.id
|
||||
if (!targetMessageId) continue
|
||||
|
||||
if (!messageHasContentFromSDK(targetMessage)) {
|
||||
return targetMessageId
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function fixEmptyMessagesWithSDK(params: {
|
||||
sessionID: string
|
||||
client: Client
|
||||
placeholderText: string
|
||||
messageIndex?: number
|
||||
}): Promise<{ fixed: boolean; fixedMessageIds: string[]; scannedEmptyCount: number }> {
|
||||
let fixed = false
|
||||
const fixedMessageIds: string[] = []
|
||||
|
||||
if (params.messageIndex !== undefined) {
|
||||
const targetMessageId = await findEmptyMessageByIndexFromSDK(
|
||||
params.client,
|
||||
params.sessionID,
|
||||
params.messageIndex,
|
||||
)
|
||||
|
||||
if (targetMessageId) {
|
||||
const replaced = await replaceEmptyTextPartsAsync(
|
||||
params.client,
|
||||
params.sessionID,
|
||||
targetMessageId,
|
||||
params.placeholderText,
|
||||
)
|
||||
|
||||
if (replaced) {
|
||||
fixed = true
|
||||
fixedMessageIds.push(targetMessageId)
|
||||
} else {
|
||||
const injected = await injectTextPartAsync(
|
||||
params.client,
|
||||
params.sessionID,
|
||||
targetMessageId,
|
||||
params.placeholderText,
|
||||
)
|
||||
|
||||
if (injected) {
|
||||
fixed = true
|
||||
fixedMessageIds.push(targetMessageId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fixed) {
|
||||
return { fixed, fixedMessageIds, scannedEmptyCount: 0 }
|
||||
}
|
||||
|
||||
const emptyMessageIds = await findEmptyMessagesFromSDK(params.client, params.sessionID)
|
||||
if (emptyMessageIds.length === 0) {
|
||||
return { fixed: false, fixedMessageIds: [], scannedEmptyCount: 0 }
|
||||
}
|
||||
|
||||
for (const messageID of emptyMessageIds) {
|
||||
const replaced = await replaceEmptyTextPartsAsync(
|
||||
params.client,
|
||||
params.sessionID,
|
||||
messageID,
|
||||
params.placeholderText,
|
||||
)
|
||||
|
||||
if (replaced) {
|
||||
fixed = true
|
||||
fixedMessageIds.push(messageID)
|
||||
} else {
|
||||
const injected = await injectTextPartAsync(
|
||||
params.client,
|
||||
params.sessionID,
|
||||
messageID,
|
||||
params.placeholderText,
|
||||
)
|
||||
|
||||
if (injected) {
|
||||
fixed = true
|
||||
fixedMessageIds.push(messageID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { fixed, fixedMessageIds, scannedEmptyCount: emptyMessageIds.length }
|
||||
}
|
||||
Reference in New Issue
Block a user