fix(runtime-fallback): scope visible-assistant check to current turn and cleanup retry dedupe keys
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import type { HookDeps, RuntimeFallbackPluginInput } from "./types"
|
||||
import type { AutoRetryHelpers } from "./auto-retry"
|
||||
import { createFallbackState } from "./fallback-state"
|
||||
|
||||
type MessageUpdateHandlerModule = typeof import("./message-update-handler")
|
||||
|
||||
async function importFreshMessageUpdateHandlerModule(): Promise<MessageUpdateHandlerModule> {
|
||||
return import(`./message-update-handler?success-retry-key-${Date.now()}-${Math.random()}`)
|
||||
}
|
||||
|
||||
function createContext(messagesResponse: unknown): RuntimeFallbackPluginInput {
|
||||
return {
|
||||
client: {
|
||||
session: {
|
||||
abort: async () => ({}),
|
||||
messages: async () => messagesResponse,
|
||||
promptAsync: async () => ({}),
|
||||
},
|
||||
tui: {
|
||||
showToast: async () => ({}),
|
||||
},
|
||||
},
|
||||
directory: "/test/dir",
|
||||
}
|
||||
}
|
||||
|
||||
function createDeps(messagesResponse: unknown): HookDeps {
|
||||
return {
|
||||
ctx: createContext(messagesResponse),
|
||||
config: {
|
||||
enabled: true,
|
||||
retry_on_errors: [429, 503, 529],
|
||||
max_fallback_attempts: 3,
|
||||
cooldown_seconds: 60,
|
||||
timeout_seconds: 30,
|
||||
notify_on_fallback: false,
|
||||
},
|
||||
options: undefined,
|
||||
pluginConfig: {},
|
||||
sessionStates: new Map(),
|
||||
sessionLastAccess: new Map(),
|
||||
sessionRetryInFlight: new Set(),
|
||||
sessionAwaitingFallbackResult: new Set(),
|
||||
sessionFallbackTimeouts: new Map(),
|
||||
sessionStatusRetryKeys: new Map(),
|
||||
}
|
||||
}
|
||||
|
||||
function createHelpers(clearCalls: string[]): AutoRetryHelpers {
|
||||
return {
|
||||
abortSessionRequest: async () => {},
|
||||
clearSessionFallbackTimeout: (sessionID: string) => {
|
||||
clearCalls.push(sessionID)
|
||||
},
|
||||
scheduleSessionFallbackTimeout: () => {},
|
||||
autoRetryWithFallback: async () => {},
|
||||
resolveAgentForSessionFromContext: async () => undefined,
|
||||
cleanupStaleSessions: () => {},
|
||||
}
|
||||
}
|
||||
|
||||
describe("createMessageUpdateHandler retry-key cleanup", () => {
|
||||
it("#given a visible assistant reply after the latest user turn #when a non-error assistant update arrives #then the retry dedupe key is cleared with the fallback watchdog", async () => {
|
||||
// given
|
||||
const { createMessageUpdateHandler } = await importFreshMessageUpdateHandlerModule()
|
||||
const sessionID = "session-visible-assistant"
|
||||
const clearCalls: string[] = []
|
||||
const deps = createDeps({
|
||||
data: [
|
||||
{ info: { role: "user" }, parts: [{ type: "text", text: "latest question" }] },
|
||||
{ info: { role: "assistant" }, parts: [{ type: "text", text: "visible answer" }] },
|
||||
],
|
||||
})
|
||||
const state = createFallbackState("google/gemini-2.5-pro")
|
||||
state.pendingFallbackModel = "openai/gpt-5.4"
|
||||
deps.sessionStates.set(sessionID, state)
|
||||
deps.sessionAwaitingFallbackResult.add(sessionID)
|
||||
deps.sessionStatusRetryKeys.set(sessionID, "retry:1")
|
||||
const handler = createMessageUpdateHandler(deps, createHelpers(clearCalls))
|
||||
|
||||
// when
|
||||
await handler({
|
||||
info: {
|
||||
sessionID,
|
||||
role: "assistant",
|
||||
model: "openai/gpt-5.4",
|
||||
},
|
||||
})
|
||||
|
||||
// then
|
||||
expect(deps.sessionAwaitingFallbackResult.has(sessionID)).toBe(false)
|
||||
expect(deps.sessionStatusRetryKeys.has(sessionID)).toBe(false)
|
||||
expect(state.pendingFallbackModel).toBe(undefined)
|
||||
expect(clearCalls).toEqual([sessionID])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user