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:
YeonGyu-Kim
2026-02-15 15:53:29 +09:00
parent dff3a551d8
commit 62e4e57455
9 changed files with 436 additions and 27 deletions
@@ -1,5 +1,24 @@
import type { PluginInput } from "@opencode-ai/plugin"
import type { AggressiveTruncateResult } from "./tool-part-types"
import { findToolResultsBySize, truncateToolResult } from "./tool-result-storage"
import { findToolResultsBySizeFromSDK, truncateToolResultAsync } from "./tool-result-storage-sdk"
import { isSqliteBackend } from "../../shared/opencode-storage-detection"
type OpencodeClient = PluginInput["client"]
interface SDKToolPart {
id: string
type: string
tool?: string
state?: { output?: string }
truncated?: boolean
originalSize?: number
}
interface SDKMessage {
info?: { id?: string }
parts?: SDKToolPart[]
}
function calculateTargetBytesToRemove(
currentTokens: number,
@@ -13,13 +32,14 @@ function calculateTargetBytesToRemove(
return { tokensToReduce, targetBytesToRemove }
}
export function truncateUntilTargetTokens(
export async function truncateUntilTargetTokens(
sessionID: string,
currentTokens: number,
maxTokens: number,
targetRatio: number = 0.8,
charsPerToken: number = 4
): AggressiveTruncateResult {
charsPerToken: number = 4,
client?: OpencodeClient
): Promise<AggressiveTruncateResult> {
const { tokensToReduce, targetBytesToRemove } = calculateTargetBytesToRemove(
currentTokens,
maxTokens,
@@ -38,6 +58,82 @@ export function truncateUntilTargetTokens(
}
}
if (client && isSqliteBackend()) {
let toolPartsByKey = new Map<string, SDKToolPart>()
try {
const response = (await client.session.messages({
path: { id: sessionID },
})) as { data?: SDKMessage[] }
const messages = response.data ?? []
toolPartsByKey = new Map<string, SDKToolPart>()
for (const message of messages) {
const messageID = message.info?.id
if (!messageID || !message.parts) continue
for (const part of message.parts) {
if (part.type !== "tool") continue
toolPartsByKey.set(`${messageID}:${part.id}`, part)
}
}
} catch {
toolPartsByKey = new Map<string, SDKToolPart>()
}
const results = await findToolResultsBySizeFromSDK(client, sessionID)
if (results.length === 0) {
return {
success: false,
sufficient: false,
truncatedCount: 0,
totalBytesRemoved: 0,
targetBytesToRemove,
truncatedTools: [],
}
}
let totalRemoved = 0
let truncatedCount = 0
const truncatedTools: Array<{ toolName: string; originalSize: number }> = []
for (const result of results) {
const part = toolPartsByKey.get(`${result.messageID}:${result.partId}`)
if (!part) continue
const truncateResult = await truncateToolResultAsync(
client,
sessionID,
result.messageID,
result.partId,
part
)
if (truncateResult.success) {
truncatedCount++
const removedSize = truncateResult.originalSize ?? result.outputSize
totalRemoved += removedSize
truncatedTools.push({
toolName: truncateResult.toolName ?? result.toolName,
originalSize: removedSize,
})
if (totalRemoved >= targetBytesToRemove) {
break
}
}
}
const sufficient = totalRemoved >= targetBytesToRemove
return {
success: truncatedCount > 0,
sufficient,
truncatedCount,
totalBytesRemoved: totalRemoved,
targetBytesToRemove,
truncatedTools,
}
}
const results = findToolResultsBySize(sessionID)
if (results.length === 0) {