fix: skip unauthenticated compaction candidates
(cherry picked from commit fa5f7d73ec1f169f9b9952648195057caa24657e)
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user