test: cover timeout compaction retry cap

This commit is contained in:
Joey Krug 2026-03-14 18:37:49 -04:00
parent d1b7e5ae9a
commit b80bba8e12
3 changed files with 116 additions and 31 deletions

View File

@ -136,6 +136,15 @@ export const mockedIsLikelyContextOverflowError = vi.fn((msg?: string) => {
export const mockedPickFallbackThinkingLevel = vi.fn<(params?: unknown) => ThinkLevel | null>(
() => null,
);
export const mockedGetApiKeyForModel = vi.fn(
async ({ profileId }: { profileId?: string } = {}) => ({
apiKey: "test-key",
profileId: profileId ?? "test-profile",
source: "test",
mode: "api-key" as const,
}),
);
export const mockedResolveAuthProfileOrder = vi.fn(() => [] as string[]);
export const overflowBaseRunParams = {
sessionId: "test-session",
@ -223,6 +232,17 @@ export function resetRunOverflowCompactionHarnessMocks(): void {
});
mockedPickFallbackThinkingLevel.mockReset();
mockedPickFallbackThinkingLevel.mockReturnValue(null);
mockedGetApiKeyForModel.mockReset();
mockedGetApiKeyForModel.mockImplementation(
async ({ profileId }: { profileId?: string } = {}) => ({
apiKey: "test-key",
profileId: profileId ?? "test-profile",
source: "test",
mode: "api-key",
}),
);
mockedResolveAuthProfileOrder.mockReset();
mockedResolveAuthProfileOrder.mockReturnValue([]);
}
export async function loadRunOverflowCompactionHarness(): Promise<{
@ -322,12 +342,8 @@ export async function loadRunOverflowCompactionHarness(): Promise<{
vi.doMock("../model-auth.js", () => ({
applyLocalNoAuthHeaderOverride: vi.fn((model: unknown) => model),
ensureAuthProfileStore: vi.fn(() => ({})),
getApiKeyForModel: vi.fn(async () => ({
apiKey: "test-key",
profileId: "test-profile",
source: "test",
})),
resolveAuthProfileOrder: vi.fn(() => []),
getApiKeyForModel: mockedGetApiKeyForModel,
resolveAuthProfileOrder: mockedResolveAuthProfileOrder,
}));
vi.doMock("../models-config.js", () => ({

View File

@ -9,7 +9,9 @@ import {
mockedCompactDirect,
mockedContextEngine,
mockedRunEmbeddedAttempt,
mockedGetApiKeyForModel,
mockedPickFallbackThinkingLevel,
mockedResolveAuthProfileOrder,
resetRunOverflowCompactionHarnessMocks,
mockedSessionLikelyHasOversizedToolResults,
mockedTruncateOversizedToolResultsInSession,
@ -18,6 +20,16 @@ import {
let runEmbeddedPiAgent: typeof import("./run.js").runEmbeddedPiAgent;
const useTwoAuthProfiles = () => {
mockedResolveAuthProfileOrder.mockReturnValue(["profile-a", "profile-b"]);
mockedGetApiKeyForModel.mockImplementation(async ({ profileId } = {}) => ({
apiKey: `test-key-${profileId ?? "profile-a"}`,
profileId: profileId ?? "profile-a",
source: "test",
mode: "api-key",
}));
};
describe("timeout-triggered compaction", () => {
beforeAll(async () => {
({ runEmbeddedPiAgent } = await loadRunOverflowCompactionHarness());
@ -57,6 +69,13 @@ describe("timeout-triggered compaction", () => {
});
mockedPickFallbackThinkingLevel.mockReturnValue(undefined);
mockedGlobalHookRunner.hasHooks.mockImplementation(() => false);
mockedGetApiKeyForModel.mockImplementation(async ({ profileId }) => ({
apiKey: "test-key",
profileId: profileId ?? "test-profile",
source: "test",
mode: "api-key",
}));
mockedResolveAuthProfileOrder.mockReturnValue([]);
});
it("attempts compaction when LLM times out with high prompt token usage (>65%)", async () => {
@ -346,45 +365,95 @@ describe("timeout-triggered compaction", () => {
);
});
it("increments attempt counter even when compaction returns compacted:false", async () => {
// First timeout: high prompt usage, compaction fails (compacted:false)
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({
timedOut: true,
lastAssistant: {
usage: { input: 150000 },
} as never,
}),
);
it("counts compacted:false timeout compactions against the retry cap across profile rotation", async () => {
useTwoAuthProfiles();
mockedRunEmbeddedAttempt
.mockResolvedValueOnce(
makeAttemptResult({
timedOut: true,
aborted: true,
lastAssistant: {
usage: { input: 150000 },
} as never,
}),
)
.mockResolvedValueOnce(
makeAttemptResult({
timedOut: true,
aborted: true,
lastAssistant: {
usage: { input: 150000 },
} as never,
}),
);
mockedCompactDirect.mockResolvedValueOnce({
ok: false,
compacted: false,
reason: "nothing to compact",
});
// The failed compaction falls through to timeout error; the runner
// returns with an error payload (no retry because compacted was false).
const result = await runEmbeddedPiAgent(overflowBaseRunParams);
expect(mockedCompactDirect).toHaveBeenCalledTimes(1);
expect(result.payloads?.[0]?.isError).toBe(true);
});
it("increments attempt counter when compact() throws, blocking subsequent attempts", async () => {
// First timeout: high prompt usage, compact() throws
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({
timedOut: true,
lastAssistant: {
usage: { input: 150000 },
} as never,
expect(mockedCompactDirect).toHaveBeenCalledWith(
expect.objectContaining({
runtimeContext: expect.objectContaining({
authProfileId: "profile-a",
attempt: 1,
maxAttempts: 1,
}),
}),
);
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
expect(mockedRunEmbeddedAttempt).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ authProfileId: "profile-a" }),
);
expect(mockedRunEmbeddedAttempt).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ authProfileId: "profile-b" }),
);
expect(result.payloads?.[0]?.isError).toBe(true);
expect(result.payloads?.[0]?.text).toContain("timed out");
});
it("counts thrown timeout compactions against the retry cap across profile rotation", async () => {
useTwoAuthProfiles();
mockedRunEmbeddedAttempt
.mockResolvedValueOnce(
makeAttemptResult({
timedOut: true,
aborted: true,
lastAssistant: {
usage: { input: 150000 },
} as never,
}),
)
.mockResolvedValueOnce(
makeAttemptResult({
timedOut: true,
aborted: true,
lastAssistant: {
usage: { input: 150000 },
} as never,
}),
);
mockedCompactDirect.mockRejectedValueOnce(new Error("engine crashed"));
// Falls through to timeout error on first attempt
const result = await runEmbeddedPiAgent(overflowBaseRunParams);
expect(mockedCompactDirect).toHaveBeenCalledTimes(1);
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
expect(mockedRunEmbeddedAttempt).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ authProfileId: "profile-a" }),
);
expect(mockedRunEmbeddedAttempt).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ authProfileId: "profile-b" }),
);
expect(result.payloads?.[0]?.isError).toBe(true);
expect(result.payloads?.[0]?.text).toContain("timed out");
});
it("uses prompt/input tokens for ratio, not total tokens", async () => {

View File

@ -1108,7 +1108,7 @@ export async function runEmbeddedPiAgent(
: 0;
if (timeoutCompactionAttempts >= MAX_TIMEOUT_COMPACTION_ATTEMPTS) {
log.warn(
`[timeout-compaction] already compacted ${timeoutCompactionAttempts} time(s) for timeouts; falling through to failover rotation`,
`[timeout-compaction] already attempted timeout compaction ${timeoutCompactionAttempts} time(s); falling through to failover rotation`,
);
} else if (tokenUsedRatio > 0.65) {
const timeoutDiagId = createCompactionDiagId();