219 lines
6.7 KiB
TypeScript
219 lines
6.7 KiB
TypeScript
import type { AutoCompactState } from "./types"
|
|
import type { OhMyOpenCodeConfig } from "../../config"
|
|
import { RETRY_CONFIG } from "./types"
|
|
import type { Client } from "./client"
|
|
import {
|
|
clearRetryTimer,
|
|
clearSessionState,
|
|
getEmptyContentAttempt,
|
|
getOrCreateRetryState,
|
|
setRetryTimer,
|
|
} from "./state"
|
|
import { sanitizeEmptyMessagesBeforeSummarize } from "./message-builder"
|
|
import { fixEmptyMessages } from "./empty-content-recovery"
|
|
|
|
import { resolveCompactionModel } from "../shared/compaction-model-resolver"
|
|
import { log } from "../../shared/logger"
|
|
|
|
const SUMMARIZE_RETRY_TOTAL_TIMEOUT_MS = 120_000
|
|
|
|
|
|
async function showToastSafely(
|
|
client: Client,
|
|
body: {
|
|
title: string
|
|
message: string
|
|
variant: "error" | "warning" | "success"
|
|
duration: number
|
|
},
|
|
failureContext: string,
|
|
): Promise<void> {
|
|
try {
|
|
await client.tui.showToast({ body })
|
|
} catch (error) {
|
|
log(`[auto-compact] failed to show toast: ${failureContext}`, {
|
|
title: body.title,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
})
|
|
}
|
|
}
|
|
|
|
export async function runSummarizeRetryStrategy(params: {
|
|
sessionID: string
|
|
msg: Record<string, unknown>
|
|
autoCompactState: AutoCompactState
|
|
client: Client
|
|
directory: string
|
|
pluginConfig: OhMyOpenCodeConfig
|
|
errorType?: string
|
|
messageIndex?: number
|
|
}): Promise<void> {
|
|
if (!params.autoCompactState.pendingCompact.has(params.sessionID)) {
|
|
clearRetryTimer(params.autoCompactState, params.sessionID)
|
|
return
|
|
}
|
|
|
|
const retryState = getOrCreateRetryState(params.autoCompactState, params.sessionID)
|
|
const now = Date.now()
|
|
|
|
if (retryState.firstAttemptTime === 0) {
|
|
retryState.firstAttemptTime = now
|
|
}
|
|
|
|
const elapsedTimeMs = now - retryState.firstAttemptTime
|
|
if (elapsedTimeMs >= SUMMARIZE_RETRY_TOTAL_TIMEOUT_MS) {
|
|
clearSessionState(params.autoCompactState, params.sessionID)
|
|
await showToastSafely(
|
|
params.client,
|
|
{
|
|
title: "Auto Compact Timed Out",
|
|
message: "Compaction retries exceeded the timeout window. Please start a new session.",
|
|
variant: "error",
|
|
duration: 5000,
|
|
},
|
|
"retry timeout",
|
|
)
|
|
return
|
|
}
|
|
|
|
clearRetryTimer(params.autoCompactState, params.sessionID)
|
|
|
|
if (params.errorType?.includes("non-empty content")) {
|
|
const attempt = getEmptyContentAttempt(params.autoCompactState, params.sessionID)
|
|
if (attempt < 3) {
|
|
const fixed = await fixEmptyMessages({
|
|
sessionID: params.sessionID,
|
|
autoCompactState: params.autoCompactState,
|
|
client: params.client,
|
|
messageIndex: params.messageIndex,
|
|
})
|
|
if (fixed) {
|
|
const timeout = setTimeout(() => {
|
|
params.autoCompactState.retryTimerBySession.delete(params.sessionID)
|
|
void runSummarizeRetryStrategy(params)
|
|
}, 500)
|
|
setRetryTimer(params.autoCompactState, params.sessionID, timeout)
|
|
return
|
|
}
|
|
} else {
|
|
clearSessionState(params.autoCompactState, params.sessionID)
|
|
await showToastSafely(
|
|
params.client,
|
|
{
|
|
title: "Recovery Failed",
|
|
message:
|
|
"Max recovery attempts (3) reached for empty content error. Please start a new session.",
|
|
variant: "error",
|
|
duration: 10000,
|
|
},
|
|
"empty content recovery exhausted",
|
|
)
|
|
return
|
|
}
|
|
}
|
|
|
|
if (Date.now() - retryState.lastAttemptTime > 300000) {
|
|
retryState.attempt = 0
|
|
retryState.firstAttemptTime = Date.now()
|
|
params.autoCompactState.truncateStateBySession.delete(params.sessionID)
|
|
}
|
|
|
|
if (retryState.attempt < RETRY_CONFIG.maxAttempts) {
|
|
retryState.attempt++
|
|
retryState.lastAttemptTime = Date.now()
|
|
|
|
const providerID = params.msg.providerID as string | undefined
|
|
const modelID = params.msg.modelID as string | undefined
|
|
|
|
if (providerID && modelID) {
|
|
try {
|
|
await sanitizeEmptyMessagesBeforeSummarize(params.sessionID, params.client)
|
|
|
|
await showToastSafely(
|
|
params.client,
|
|
{
|
|
title: "Auto Compact",
|
|
message: `Summarizing session (attempt ${retryState.attempt}/${RETRY_CONFIG.maxAttempts})...`,
|
|
variant: "warning",
|
|
duration: 3000,
|
|
},
|
|
"summarize retry attempt",
|
|
)
|
|
|
|
const { providerID: targetProviderID, modelID: targetModelID } = resolveCompactionModel(
|
|
params.pluginConfig,
|
|
params.sessionID,
|
|
providerID,
|
|
modelID
|
|
)
|
|
|
|
const summarizeBody = { providerID: targetProviderID, modelID: targetModelID, auto: true }
|
|
await params.client.session.summarize({
|
|
path: { id: params.sessionID },
|
|
body: summarizeBody as never,
|
|
query: { directory: params.directory },
|
|
})
|
|
clearSessionState(params.autoCompactState, params.sessionID)
|
|
return
|
|
} catch (error) {
|
|
log("[auto-compact] summarize retry attempt failed", {
|
|
sessionID: params.sessionID,
|
|
attempt: retryState.attempt,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
})
|
|
|
|
const remainingTimeMs = SUMMARIZE_RETRY_TOTAL_TIMEOUT_MS - (Date.now() - retryState.firstAttemptTime)
|
|
if (remainingTimeMs <= 0) {
|
|
clearSessionState(params.autoCompactState, params.sessionID)
|
|
await showToastSafely(
|
|
params.client,
|
|
{
|
|
title: "Auto Compact Timed Out",
|
|
message: "Compaction retries exceeded the timeout window. Please start a new session.",
|
|
variant: "error",
|
|
duration: 5000,
|
|
},
|
|
"summarize retry timeout after failure",
|
|
)
|
|
return
|
|
}
|
|
|
|
const delay =
|
|
RETRY_CONFIG.initialDelayMs *
|
|
Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1)
|
|
const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs, remainingTimeMs)
|
|
|
|
const timeout = setTimeout(() => {
|
|
params.autoCompactState.retryTimerBySession.delete(params.sessionID)
|
|
void runSummarizeRetryStrategy(params)
|
|
}, cappedDelay)
|
|
setRetryTimer(params.autoCompactState, params.sessionID, timeout)
|
|
return
|
|
}
|
|
} else {
|
|
await showToastSafely(
|
|
params.client,
|
|
{
|
|
title: "Summarize Skipped",
|
|
message: "Missing providerID or modelID.",
|
|
variant: "warning",
|
|
duration: 3000,
|
|
},
|
|
"missing summarize model info",
|
|
)
|
|
}
|
|
}
|
|
|
|
clearSessionState(params.autoCompactState, params.sessionID)
|
|
await showToastSafely(
|
|
params.client,
|
|
{
|
|
title: "Auto Compact Failed",
|
|
message: "All recovery attempts failed. Please start a new session.",
|
|
variant: "error",
|
|
duration: 5000,
|
|
},
|
|
"summarize retry failed",
|
|
)
|
|
}
|