Merge pull request #2668 from MoerAI/fix/session-degradation-detection
fix(session): detect post-compaction no-text degradation and trigger recovery (fixes #2232)
This commit is contained in:
@@ -0,0 +1,188 @@
|
|||||||
|
import type { OhMyOpenCodeConfig } from "../config"
|
||||||
|
import { log } from "../shared/logger"
|
||||||
|
import { resolveNoTextTailFromSession } from "./preemptive-compaction-no-text-tail"
|
||||||
|
import { resolveCompactionModel } from "./shared/compaction-model-resolver"
|
||||||
|
|
||||||
|
const PREEMPTIVE_COMPACTION_TIMEOUT_MS = 120_000
|
||||||
|
const POST_COMPACTION_MONITOR_COUNT = 5
|
||||||
|
const POST_COMPACTION_NO_TEXT_THRESHOLD = 3
|
||||||
|
|
||||||
|
declare function setTimeout(handler: () => void, timeout?: number): unknown
|
||||||
|
declare function clearTimeout(timeoutID: unknown): void
|
||||||
|
|
||||||
|
interface CompactionTargetState {
|
||||||
|
providerID: string
|
||||||
|
modelID: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientLike {
|
||||||
|
session: {
|
||||||
|
summarize: (input: {
|
||||||
|
path: { id: string }
|
||||||
|
body: { providerID: string; modelID: string }
|
||||||
|
query: { directory: string }
|
||||||
|
}) => Promise<unknown>
|
||||||
|
messages: (input: {
|
||||||
|
path: { id: string }
|
||||||
|
query?: { directory: string }
|
||||||
|
}) => Promise<unknown>
|
||||||
|
}
|
||||||
|
tui: {
|
||||||
|
showToast: (input: {
|
||||||
|
body: {
|
||||||
|
title: string
|
||||||
|
message: string
|
||||||
|
variant: "warning"
|
||||||
|
duration: number
|
||||||
|
}
|
||||||
|
}) => Promise<unknown>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssistantCompactionMessageInfo {
|
||||||
|
sessionID: string
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withTimeout<TValue>(
|
||||||
|
promise: Promise<TValue>,
|
||||||
|
timeoutMs: number,
|
||||||
|
errorMessage: string,
|
||||||
|
): Promise<TValue> {
|
||||||
|
let timeoutID: unknown
|
||||||
|
|
||||||
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||||
|
timeoutID = setTimeout(() => {
|
||||||
|
reject(new Error(errorMessage))
|
||||||
|
}, timeoutMs)
|
||||||
|
})
|
||||||
|
|
||||||
|
return await Promise.race([promise, timeoutPromise]).finally(() => {
|
||||||
|
clearTimeout(timeoutID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPostCompactionDegradationMonitor(args: {
|
||||||
|
client: ClientLike
|
||||||
|
directory: string
|
||||||
|
pluginConfig: OhMyOpenCodeConfig
|
||||||
|
tokenCache: Map<string, CompactionTargetState>
|
||||||
|
compactionInProgress: Set<string>
|
||||||
|
}) {
|
||||||
|
const { client, directory, pluginConfig, tokenCache, compactionInProgress } = args
|
||||||
|
const postCompactionRemaining = new Map<string, number>()
|
||||||
|
const postCompactionNoTextStreak = new Map<string, number>()
|
||||||
|
const postCompactionRecoveryTriggered = new Set<string>()
|
||||||
|
const postCompactionEpoch = new Map<string, number>()
|
||||||
|
|
||||||
|
const clear = (sessionID: string): void => {
|
||||||
|
postCompactionRemaining.delete(sessionID)
|
||||||
|
postCompactionNoTextStreak.delete(sessionID)
|
||||||
|
postCompactionRecoveryTriggered.delete(sessionID)
|
||||||
|
postCompactionEpoch.delete(sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSessionCompacted = (sessionID: string): void => {
|
||||||
|
const nextEpoch = (postCompactionEpoch.get(sessionID) ?? 0) + 1
|
||||||
|
postCompactionEpoch.set(sessionID, nextEpoch)
|
||||||
|
postCompactionRemaining.set(sessionID, POST_COMPACTION_MONITOR_COUNT)
|
||||||
|
postCompactionNoTextStreak.set(sessionID, 0)
|
||||||
|
postCompactionRecoveryTriggered.delete(sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerRecovery = async (sessionID: string): Promise<void> => {
|
||||||
|
if (postCompactionRecoveryTriggered.has(sessionID) || compactionInProgress.has(sessionID)) return
|
||||||
|
|
||||||
|
const cached = tokenCache.get(sessionID)
|
||||||
|
if (!cached?.modelID) {
|
||||||
|
log("[preemptive-compaction] No-text tail detected but compaction model is unavailable", { sessionID })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
postCompactionRecoveryTriggered.add(sessionID)
|
||||||
|
compactionInProgress.add(sessionID)
|
||||||
|
const recoveryEpoch = postCompactionEpoch.get(sessionID) ?? 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { providerID: targetProviderID, modelID: targetModelID } = resolveCompactionModel(
|
||||||
|
pluginConfig,
|
||||||
|
sessionID,
|
||||||
|
cached.providerID,
|
||||||
|
cached.modelID,
|
||||||
|
)
|
||||||
|
|
||||||
|
await client.tui
|
||||||
|
.showToast({
|
||||||
|
body: {
|
||||||
|
title: "Session Degradation Detected",
|
||||||
|
message: "Detected repeated no-text assistant responses after compaction. Retrying compaction recovery.",
|
||||||
|
variant: "warning",
|
||||||
|
duration: 5000,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
|
||||||
|
await withTimeout(
|
||||||
|
client.session.summarize({
|
||||||
|
path: { id: sessionID },
|
||||||
|
body: { providerID: targetProviderID, modelID: targetModelID },
|
||||||
|
query: { directory },
|
||||||
|
}),
|
||||||
|
PREEMPTIVE_COMPACTION_TIMEOUT_MS,
|
||||||
|
`Compaction recovery summarize timed out after ${PREEMPTIVE_COMPACTION_TIMEOUT_MS}ms`,
|
||||||
|
)
|
||||||
|
|
||||||
|
log("[preemptive-compaction] Triggered recovery after post-compaction no-text tail", { sessionID })
|
||||||
|
} catch (error) {
|
||||||
|
log("[preemptive-compaction] Failed to recover post-compaction no-text tail", {
|
||||||
|
sessionID,
|
||||||
|
error: String(error),
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
compactionInProgress.delete(sessionID)
|
||||||
|
if ((postCompactionEpoch.get(sessionID) ?? 0) === recoveryEpoch) {
|
||||||
|
clear(sessionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAssistantMessageUpdated = async (info: AssistantCompactionMessageInfo): Promise<void> => {
|
||||||
|
const remaining = postCompactionRemaining.get(info.sessionID)
|
||||||
|
if (!remaining || remaining <= 0) return
|
||||||
|
|
||||||
|
if (remaining === 1) {
|
||||||
|
postCompactionRemaining.delete(info.sessionID)
|
||||||
|
} else {
|
||||||
|
postCompactionRemaining.set(info.sessionID, remaining - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNoTextTail = await resolveNoTextTailFromSession({
|
||||||
|
client,
|
||||||
|
sessionID: info.sessionID,
|
||||||
|
messageID: info.id,
|
||||||
|
directory,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!isNoTextTail) {
|
||||||
|
postCompactionNoTextStreak.set(info.sessionID, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextStreak = (postCompactionNoTextStreak.get(info.sessionID) ?? 0) + 1
|
||||||
|
postCompactionNoTextStreak.set(info.sessionID, nextStreak)
|
||||||
|
|
||||||
|
if (nextStreak >= POST_COMPACTION_NO_TEXT_THRESHOLD) {
|
||||||
|
log("[preemptive-compaction] Detected post-compaction no-text tail pattern", {
|
||||||
|
sessionID: info.sessionID,
|
||||||
|
streak: nextStreak,
|
||||||
|
})
|
||||||
|
await triggerRecovery(info.sessionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
clear,
|
||||||
|
onSessionCompacted,
|
||||||
|
onAssistantMessageUpdated,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { normalizeSDKResponse } from "../shared/normalize-sdk-response"
|
||||||
|
|
||||||
|
const STEP_ONLY_TYPES = new Set(["step-start", "step-finish"])
|
||||||
|
|
||||||
|
interface MessagePart {
|
||||||
|
type?: unknown
|
||||||
|
text?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SessionMessage {
|
||||||
|
info?: {
|
||||||
|
id?: string
|
||||||
|
role?: string
|
||||||
|
}
|
||||||
|
parts?: MessagePart[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isStepOnlyNoTextParts(parts: unknown): boolean {
|
||||||
|
if (!Array.isArray(parts) || parts.length === 0) return false
|
||||||
|
|
||||||
|
return parts.every((part) => {
|
||||||
|
const type = (part as MessagePart | undefined)?.type
|
||||||
|
if (typeof type !== "string") return false
|
||||||
|
if (!STEP_ONLY_TYPES.has(type)) return false
|
||||||
|
|
||||||
|
const text = (part as MessagePart | undefined)?.text
|
||||||
|
if (typeof text === "string" && text.trim().length > 0) return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMessageByID(messages: SessionMessage[], messageID?: string): SessionMessage | undefined {
|
||||||
|
if (!messageID) return undefined
|
||||||
|
return messages.find((message) => message.info?.id === messageID)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveNoTextTailFromSession(args: {
|
||||||
|
client: {
|
||||||
|
session: {
|
||||||
|
messages: (input: {
|
||||||
|
path: { id: string }
|
||||||
|
query?: { directory: string }
|
||||||
|
}) => Promise<unknown>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessionID: string
|
||||||
|
messageID?: string
|
||||||
|
directory: string
|
||||||
|
}): Promise<boolean> {
|
||||||
|
const { client, sessionID, messageID, directory } = args
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client.session.messages({
|
||||||
|
path: { id: sessionID },
|
||||||
|
query: { directory },
|
||||||
|
})
|
||||||
|
|
||||||
|
const messages = normalizeSDKResponse(response, [] as SessionMessage[], {
|
||||||
|
preferResponseOnMissingData: true,
|
||||||
|
})
|
||||||
|
if (!Array.isArray(messages) || messages.length === 0) return false
|
||||||
|
|
||||||
|
const target = findMessageByID(messages, messageID) ?? messages[messages.length - 1]
|
||||||
|
if (target.info?.role !== "assistant") return false
|
||||||
|
|
||||||
|
return isStepOnlyNoTextParts(target.parts)
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import { beforeEach, describe, expect, it, mock } from "bun:test"
|
||||||
|
|
||||||
|
const logMock = mock(() => {})
|
||||||
|
|
||||||
|
mock.module("../shared/logger", () => ({
|
||||||
|
log: logMock,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { createPreemptiveCompactionHook } = await import("./preemptive-compaction")
|
||||||
|
|
||||||
|
function createMockCtx() {
|
||||||
|
return {
|
||||||
|
client: {
|
||||||
|
session: {
|
||||||
|
messages: mock(() => Promise.resolve({ data: [] })),
|
||||||
|
summarize: mock(() => Promise.resolve({})),
|
||||||
|
},
|
||||||
|
tui: {
|
||||||
|
showToast: mock(() => Promise.resolve({})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
directory: "/tmp/test",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAssistantUpdate(input: {
|
||||||
|
sessionID: string
|
||||||
|
id: string
|
||||||
|
parts: unknown[]
|
||||||
|
}): {
|
||||||
|
event: {
|
||||||
|
type: string
|
||||||
|
properties: {
|
||||||
|
info: {
|
||||||
|
id: string
|
||||||
|
role: string
|
||||||
|
sessionID: string
|
||||||
|
providerID: string
|
||||||
|
modelID: string
|
||||||
|
finish: boolean
|
||||||
|
tokens: { input: number; output: number; reasoning: number; cache: { read: number; write: number } }
|
||||||
|
parts: unknown[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
event: {
|
||||||
|
type: "message.updated",
|
||||||
|
properties: {
|
||||||
|
info: {
|
||||||
|
id: input.id,
|
||||||
|
role: "assistant",
|
||||||
|
sessionID: input.sessionID,
|
||||||
|
providerID: "anthropic",
|
||||||
|
modelID: "claude-sonnet-4-6",
|
||||||
|
finish: true,
|
||||||
|
tokens: { input: 1000, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||||
|
parts: input.parts,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("preemptive-compaction post-compaction degradation monitor", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
logMock.mockClear()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("triggers recovery summarize after three consecutive no-text tail messages", async () => {
|
||||||
|
const ctx = createMockCtx()
|
||||||
|
const hook = createPreemptiveCompactionHook(ctx as never, {} as never)
|
||||||
|
const sessionID = "ses_tail_recovery"
|
||||||
|
|
||||||
|
await hook.event({
|
||||||
|
event: {
|
||||||
|
type: "session.compacted",
|
||||||
|
properties: { sessionID },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const stepOnlyParts = [{ type: "step-start" }, { type: "step-finish" }]
|
||||||
|
|
||||||
|
await hook.event(buildAssistantUpdate({ sessionID, id: "msg_1", parts: stepOnlyParts }))
|
||||||
|
await hook.event(buildAssistantUpdate({ sessionID, id: "msg_2", parts: stepOnlyParts }))
|
||||||
|
await hook.event(buildAssistantUpdate({ sessionID, id: "msg_3", parts: stepOnlyParts }))
|
||||||
|
|
||||||
|
expect(ctx.client.session.summarize).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ctx.client.tui.showToast).toHaveBeenCalledTimes(1)
|
||||||
|
expect(logMock).toHaveBeenCalledWith(
|
||||||
|
"[preemptive-compaction] Detected post-compaction no-text tail pattern",
|
||||||
|
{
|
||||||
|
sessionID,
|
||||||
|
streak: 3,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("resets no-text streak when assistant emits text content", async () => {
|
||||||
|
const ctx = createMockCtx()
|
||||||
|
const hook = createPreemptiveCompactionHook(ctx as never, {} as never)
|
||||||
|
const sessionID = "ses_tail_reset"
|
||||||
|
|
||||||
|
await hook.event({
|
||||||
|
event: {
|
||||||
|
type: "session.compacted",
|
||||||
|
properties: { sessionID },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await hook.event(buildAssistantUpdate({
|
||||||
|
sessionID,
|
||||||
|
id: "msg_1",
|
||||||
|
parts: [{ type: "step-start" }, { type: "step-finish" }],
|
||||||
|
}))
|
||||||
|
|
||||||
|
await hook.event(buildAssistantUpdate({
|
||||||
|
sessionID,
|
||||||
|
id: "msg_2",
|
||||||
|
parts: [{ type: "text", text: "Recovered response" }],
|
||||||
|
}))
|
||||||
|
|
||||||
|
await hook.event(buildAssistantUpdate({
|
||||||
|
sessionID,
|
||||||
|
id: "msg_3",
|
||||||
|
parts: [{ type: "step-start" }, { type: "step-finish" }],
|
||||||
|
}))
|
||||||
|
|
||||||
|
await hook.event(buildAssistantUpdate({
|
||||||
|
sessionID,
|
||||||
|
id: "msg_4",
|
||||||
|
parts: [{ type: "step-start" }, { type: "step-finish" }],
|
||||||
|
}))
|
||||||
|
|
||||||
|
expect(ctx.client.session.summarize).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -6,10 +6,14 @@ import {
|
|||||||
} from "../shared/context-limit-resolver"
|
} from "../shared/context-limit-resolver"
|
||||||
|
|
||||||
import { resolveCompactionModel } from "./shared/compaction-model-resolver"
|
import { resolveCompactionModel } from "./shared/compaction-model-resolver"
|
||||||
const PREEMPTIVE_COMPACTION_TIMEOUT_MS = 120_000
|
import { createPostCompactionDegradationMonitor } from "./preemptive-compaction-degradation-monitor"
|
||||||
|
|
||||||
|
const PREEMPTIVE_COMPACTION_TIMEOUT_MS = 120_000
|
||||||
const PREEMPTIVE_COMPACTION_THRESHOLD = 0.78
|
const PREEMPTIVE_COMPACTION_THRESHOLD = 0.78
|
||||||
|
|
||||||
|
declare function setTimeout(handler: () => void, timeout?: number): unknown
|
||||||
|
declare function clearTimeout(timeoutID: unknown): void
|
||||||
|
|
||||||
interface TokenInfo {
|
interface TokenInfo {
|
||||||
input: number
|
input: number
|
||||||
output: number
|
output: number
|
||||||
@@ -28,7 +32,7 @@ async function withTimeout<TValue>(
|
|||||||
timeoutMs: number,
|
timeoutMs: number,
|
||||||
errorMessage: string,
|
errorMessage: string,
|
||||||
): Promise<TValue> {
|
): Promise<TValue> {
|
||||||
let timeoutID: ReturnType<typeof setTimeout> | undefined
|
let timeoutID: unknown
|
||||||
|
|
||||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||||
timeoutID = setTimeout(() => {
|
timeoutID = setTimeout(() => {
|
||||||
@@ -37,9 +41,7 @@ async function withTimeout<TValue>(
|
|||||||
})
|
})
|
||||||
|
|
||||||
return await Promise.race([promise, timeoutPromise]).finally(() => {
|
return await Promise.race([promise, timeoutPromise]).finally(() => {
|
||||||
if (timeoutID !== undefined) {
|
clearTimeout(timeoutID)
|
||||||
clearTimeout(timeoutID)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +70,14 @@ export function createPreemptiveCompactionHook(
|
|||||||
const compactedSessions = new Set<string>()
|
const compactedSessions = new Set<string>()
|
||||||
const tokenCache = new Map<string, CachedCompactionState>()
|
const tokenCache = new Map<string, CachedCompactionState>()
|
||||||
|
|
||||||
|
const postCompactionMonitor = createPostCompactionDegradationMonitor({
|
||||||
|
client: ctx.client,
|
||||||
|
directory: ctx.directory,
|
||||||
|
pluginConfig,
|
||||||
|
tokenCache,
|
||||||
|
compactionInProgress,
|
||||||
|
})
|
||||||
|
|
||||||
const toolExecuteAfter = async (
|
const toolExecuteAfter = async (
|
||||||
input: { tool: string; sessionID: string; callID: string },
|
input: { tool: string; sessionID: string; callID: string },
|
||||||
_output: { title: string; output: string; metadata: unknown }
|
_output: { title: string; output: string; metadata: unknown }
|
||||||
@@ -92,14 +102,9 @@ export function createPreemptiveCompactionHook(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastTokens = cached.tokens
|
const totalInputTokens = (cached.tokens.input ?? 0) + (cached.tokens.cache?.read ?? 0)
|
||||||
const totalInputTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0)
|
|
||||||
const usageRatio = totalInputTokens / actualLimit
|
const usageRatio = totalInputTokens / actualLimit
|
||||||
|
if (usageRatio < PREEMPTIVE_COMPACTION_THRESHOLD || !cached.modelID) return
|
||||||
if (usageRatio < PREEMPTIVE_COMPACTION_THRESHOLD) return
|
|
||||||
|
|
||||||
const modelID = cached.modelID
|
|
||||||
if (!modelID) return
|
|
||||||
|
|
||||||
compactionInProgress.add(sessionID)
|
compactionInProgress.add(sessionID)
|
||||||
|
|
||||||
@@ -108,7 +113,7 @@ export function createPreemptiveCompactionHook(
|
|||||||
pluginConfig,
|
pluginConfig,
|
||||||
sessionID,
|
sessionID,
|
||||||
cached.providerID,
|
cached.providerID,
|
||||||
modelID
|
cached.modelID,
|
||||||
)
|
)
|
||||||
|
|
||||||
await withTimeout(
|
await withTimeout(
|
||||||
@@ -133,17 +138,28 @@ export function createPreemptiveCompactionHook(
|
|||||||
const props = event.properties as Record<string, unknown> | undefined
|
const props = event.properties as Record<string, unknown> | undefined
|
||||||
|
|
||||||
if (event.type === "session.deleted") {
|
if (event.type === "session.deleted") {
|
||||||
const sessionInfo = props?.info as { id?: string } | undefined
|
const sessionID = (props?.info as { id?: string } | undefined)?.id
|
||||||
if (sessionInfo?.id) {
|
if (sessionID) {
|
||||||
compactionInProgress.delete(sessionInfo.id)
|
compactionInProgress.delete(sessionID)
|
||||||
compactedSessions.delete(sessionInfo.id)
|
compactedSessions.delete(sessionID)
|
||||||
tokenCache.delete(sessionInfo.id)
|
tokenCache.delete(sessionID)
|
||||||
|
postCompactionMonitor.clear(sessionID)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === "session.compacted") {
|
||||||
|
const sessionID = (props?.sessionID as string | undefined)
|
||||||
|
?? (props?.info as { id?: string } | undefined)?.id
|
||||||
|
if (sessionID) {
|
||||||
|
postCompactionMonitor.onSessionCompacted(sessionID)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === "message.updated") {
|
if (event.type === "message.updated") {
|
||||||
const info = props?.info as {
|
const info = props?.info as {
|
||||||
|
id?: string
|
||||||
role?: string
|
role?: string
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
providerID?: string
|
providerID?: string
|
||||||
@@ -152,15 +168,21 @@ export function createPreemptiveCompactionHook(
|
|||||||
tokens?: TokenInfo
|
tokens?: TokenInfo
|
||||||
} | undefined
|
} | undefined
|
||||||
|
|
||||||
if (!info || info.role !== "assistant" || !info.finish) return
|
if (!info || info.role !== "assistant" || !info.finish || !info.sessionID) return
|
||||||
if (!info.sessionID || !info.providerID || !info.tokens) return
|
|
||||||
|
|
||||||
tokenCache.set(info.sessionID, {
|
if (info.providerID && info.tokens) {
|
||||||
providerID: info.providerID,
|
tokenCache.set(info.sessionID, {
|
||||||
modelID: info.modelID ?? "",
|
providerID: info.providerID,
|
||||||
tokens: info.tokens,
|
modelID: info.modelID ?? "",
|
||||||
})
|
tokens: info.tokens,
|
||||||
|
})
|
||||||
|
}
|
||||||
compactedSessions.delete(info.sessionID)
|
compactedSessions.delete(info.sessionID)
|
||||||
|
|
||||||
|
await postCompactionMonitor.onAssistantMessageUpdated({
|
||||||
|
sessionID: info.sessionID,
|
||||||
|
id: info.id,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user