fix: skip unauthenticated compaction candidates

(cherry picked from commit fa5f7d73ec1f169f9b9952648195057caa24657e)
This commit is contained in:
usr-bin-roygbiv
2026-07-28 14:47:57 +00:00
committed by can1357
parent 4d3f2e32b3
commit 7fff8869a0
4 changed files with 75 additions and 9 deletions
@@ -1355,6 +1355,8 @@ export class SessionAdvisors {
});
for (const candidate of candidates) {
const apiKey = await this.#host.modelRegistry.getApiKey(candidate, advisorProviderSessionId, { signal });
if (!apiKey) continue;
if (
nativeCompactionFailure &&
(candidate.provider !== nativeCompactionFailure.provider ||
@@ -1362,8 +1364,7 @@ export class SessionAdvisors {
) {
throw nativeCompactionFailure.error;
}
const apiKey = await this.#host.modelRegistry.getApiKey(candidate, advisorProviderSessionId, { signal });
if (!apiKey) continue;
// The advisor overflow-compaction one-shot bypasses the advisor `Agent`,
// so its installed metadata resolver never runs. Emit the same
// `metadata.user_id` identity here (resolved per candidate provider,
@@ -2490,6 +2490,9 @@ export class SessionMaintenance {
for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
const candidate = candidates[candidateIndex];
const hasMoreCandidates = candidateIndex < candidates.length - 1;
const apiKey = await this.#host.modelRegistry.getApiKey(candidate, this.#host.sessionId());
if (!apiKey) continue;
if (
nativeCompactionFailure &&
(candidate.provider !== nativeCompactionFailure.provider ||
@@ -2497,9 +2500,6 @@ export class SessionMaintenance {
) {
throw nativeCompactionFailure.error;
}
const hasMoreCandidates = candidateIndex < candidates.length - 1;
const apiKey = await this.#host.modelRegistry.getApiKey(candidate, this.#host.sessionId());
if (!apiKey) continue;
let attempt = 0;
while (true) {
@@ -172,13 +172,13 @@ describe("AgentSession advisor context maintenance", () => {
const advisor = session.getAdvisorAgent();
if (!advisor) throw new Error("Expected advisor agent to be active");
advisor.setModel(nativeModel);
vi.spyOn(modelRegistry, "getApiKey").mockResolvedValue("test-key");
const apiKeySpy = vi.spyOn(modelRegistry, "getApiKey").mockResolvedValue("test-key");
vi.spyOn(modelRegistry, "getAvailable").mockReturnValue([nativeModel, sameProviderModel, crossProviderModel]);
advisor.state.messages.push(
usageAnchor(advisorMock, Date.now() - 2_000),
usageAnchor(advisorMock, Date.now() - 1_000),
);
return { advisor, crossProviderModel, nativeModel, sameProviderModel };
return { advisor, apiKeySpy, crossProviderModel, nativeModel, sameProviderModel, settings };
}
it("maintains a 371,200-token cached advisor context before the 372,000-token window", async () => {
@@ -467,6 +467,38 @@ describe("AgentSession advisor context maintenance", () => {
expect(JSON.stringify(advisor.state.messages)).toContain("same-provider native summary");
});
it("skips unauthenticated advisor candidates before enforcing the native boundary", async () => {
const { advisor, apiKeySpy, crossProviderModel, nativeModel, sameProviderModel, settings } =
createAdvisorFallbackHarness();
settings.setModelRole("smol", `${crossProviderModel.provider}/${crossProviderModel.id}`);
settings.setModelRole("slow", `${sameProviderModel.provider}/${sameProviderModel.id}`);
apiKeySpy.mockImplementation(async model =>
model.provider === crossProviderModel.provider && model.id === crossProviderModel.id ? undefined : "test-key",
);
const compactSpy = vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => {
if (model.provider === nativeModel.provider && model.id === nativeModel.id) {
throw new compactionModule.NativeCompactionError(new Error("V2 native compaction transport failed"));
}
if (model.provider === sameProviderModel.provider && model.id === sameProviderModel.id) {
return {
summary: "authenticated same-provider advisor summary",
shortSummary: "authenticated same-provider advisor",
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: 42,
};
}
throw new Error(`Unexpected advisor compaction model ${model.provider}/${model.id}`);
});
await session.prompt("small current update");
expect(compactSpy.mock.calls.map(([, model]) => `${model.provider}/${model.id}`)).toEqual([
`${nativeModel.provider}/${nativeModel.id}`,
`${sameProviderModel.provider}/${sameProviderModel.id}`,
]);
expect(JSON.stringify(advisor.state.messages)).toContain("authenticated same-provider advisor summary");
});
it("stops before a same-provider advisor candidate with native compaction disabled", async () => {
const { advisor, nativeModel, sameProviderModel } = createAdvisorFallbackHarness({
sameProviderNativeEnabled: false,
@@ -130,7 +130,7 @@ describe("issue #986 compaction auth fallback", () => {
session.sessionManager.appendMessage(assistant);
}
vi.spyOn(modelRegistry, "getAvailable").mockReturnValue([currentModel, sameProviderModel, crossProviderModel]);
vi.spyOn(modelRegistry, "getApiKey").mockResolvedValue("test-key");
const apiKeySpy = vi.spyOn(modelRegistry, "getApiKey").mockResolvedValue("test-key");
const triggerAutoCompaction = async (): Promise<void> => {
const { promise, resolve } = Promise.withResolvers<void>();
@@ -159,7 +159,7 @@ describe("issue #986 compaction auth fallback", () => {
await session.waitForIdle();
};
return { crossProviderModel, currentModel, sameProviderModel, triggerAutoCompaction };
return { apiKeySpy, crossProviderModel, currentModel, sameProviderModel, triggerAutoCompaction };
}
it("continues same-provider native candidates but stops before crossing providers on non-auth failure", async () => {
@@ -190,6 +190,39 @@ describe("issue #986 compaction auth fallback", () => {
]);
});
it("skips unauthenticated cross-provider candidates before enforcing the native boundary", async () => {
const { apiKeySpy, crossProviderModel, currentModel, sameProviderModel, triggerAutoCompaction } =
await createAutoNativeFallbackSession();
session.settings.setModelRole("smol", `${crossProviderModel.provider}/${crossProviderModel.id}`);
session.settings.setModelRole("slow", `${sameProviderModel.provider}/${sameProviderModel.id}`);
apiKeySpy.mockImplementation(async model =>
model.provider === crossProviderModel.provider ? undefined : "test-key",
);
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 compactionModule.NativeCompactionError(new Error("native compaction transport failed"));
}
if (model.provider === sameProviderModel.provider && model.id === sameProviderModel.id) {
return {
summary: "authenticated same-provider summary",
shortSummary: "authenticated same-provider",
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: 42,
};
}
throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`);
});
await triggerAutoCompaction();
expect(attemptedModels).toEqual([
`${currentModel.provider}/${currentModel.id}`,
`${sameProviderModel.provider}/${sameProviderModel.id}`,
]);
});
it("retries a transient native compaction failure on the same candidate", async () => {
const { currentModel, triggerAutoCompaction } = await createAutoNativeFallbackSession();
session.settings.set("retry.enabled", true);