fix(compaction): retry transient native failures
(cherry picked from commit 293c96dfa479de13995d581caec33b3c90b5b3ca)
This commit is contained in:
@@ -2537,16 +2537,14 @@ export class SessionMaintenance {
|
||||
}
|
||||
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const id = AIError.classify(error, candidate.api);
|
||||
const id = AIError.classify(
|
||||
error instanceof NativeCompactionError ? error.cause : error,
|
||||
candidate.api,
|
||||
);
|
||||
if (AIError.is(id, AIError.Flag.AuthFailed)) {
|
||||
lastError = this.#buildCompactionAuthError();
|
||||
break;
|
||||
}
|
||||
if (error instanceof NativeCompactionError) {
|
||||
nativeCompactionFailure ??= { error, provider: candidate.provider };
|
||||
lastError = nativeCompactionFailure.error;
|
||||
break;
|
||||
}
|
||||
if (AIError.is(id, AIError.Flag.Timeout)) {
|
||||
logger.warn(
|
||||
hasMoreCandidates
|
||||
@@ -2569,7 +2567,12 @@ export class SessionMaintenance {
|
||||
AIError.is(id, AIError.Flag.Transient) ||
|
||||
AIError.is(id, AIError.Flag.UsageLimit));
|
||||
if (!shouldRetry) {
|
||||
lastError = error;
|
||||
if (error instanceof NativeCompactionError) {
|
||||
nativeCompactionFailure ??= { error, provider: candidate.provider };
|
||||
lastError = nativeCompactionFailure.error;
|
||||
} else {
|
||||
lastError = error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2579,6 +2582,11 @@ export class SessionMaintenance {
|
||||
// If retry delay is too long (>30s), try next candidate instead of waiting
|
||||
const maxAcceptableDelayMs = 30_000;
|
||||
if (delayMs > maxAcceptableDelayMs && hasMoreCandidates) {
|
||||
if (error instanceof NativeCompactionError) {
|
||||
nativeCompactionFailure ??= { error, provider: candidate.provider };
|
||||
lastError = nativeCompactionFailure.error;
|
||||
break;
|
||||
}
|
||||
logger.warn("Auto-compaction retry delay too long, trying next model", {
|
||||
delayMs,
|
||||
retryAfterMs,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
|
||||
import * as path from "node:path";
|
||||
import { scheduler } from "node:timers/promises";
|
||||
import { Agent } from "@oh-my-pi/pi-agent-core";
|
||||
import * as compactionModule from "@oh-my-pi/pi-agent-core/compaction";
|
||||
import * as AIError from "@oh-my-pi/pi-ai/error";
|
||||
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
|
||||
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
|
||||
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
||||
@@ -188,6 +190,39 @@ describe("issue #986 compaction auth fallback", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("retries a transient native compaction failure on the same candidate", async () => {
|
||||
const { currentModel, triggerAutoCompaction } = await createAutoNativeFallbackSession();
|
||||
session.settings.set("retry.enabled", true);
|
||||
session.settings.set("retry.baseDelayMs", 1);
|
||||
session.settings.set("retry.maxRetries", 1);
|
||||
const waitSpy = vi.spyOn(scheduler, "wait").mockResolvedValue(undefined);
|
||||
const attemptedModels: string[] = [];
|
||||
vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => {
|
||||
attemptedModels.push(`${model.provider}/${model.id}`);
|
||||
if (model.provider !== currentModel.provider || model.id !== currentModel.id) {
|
||||
throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`);
|
||||
}
|
||||
if (attemptedModels.length === 1) {
|
||||
throw new compactionModule.NativeCompactionError(
|
||||
new AIError.ProviderHttpError("native compaction temporarily unavailable", 503),
|
||||
);
|
||||
}
|
||||
return {
|
||||
summary: "native retry summary",
|
||||
shortSummary: "native retry",
|
||||
firstKeptEntryId: preparation.firstKeptEntryId,
|
||||
tokensBefore: 42,
|
||||
};
|
||||
});
|
||||
|
||||
await triggerAutoCompaction();
|
||||
|
||||
expect(attemptedModels).toEqual([
|
||||
`${currentModel.provider}/${currentModel.id}`,
|
||||
`${currentModel.provider}/${currentModel.id}`,
|
||||
]);
|
||||
expect(waitSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it("stops auto-compaction before a same-provider candidate with native compaction disabled", async () => {
|
||||
const { currentModel, sameProviderModel, triggerAutoCompaction } = await createAutoNativeFallbackSession({
|
||||
sameProviderNativeEnabled: false,
|
||||
|
||||
Reference in New Issue
Block a user