Files
oh-my-pi/packages/coding-agent/test/agent-session-context-promotion.test.ts
T
can1357 0bbe52dee7 feat(coding-agent): changed context promotion to trigger on overflow errors instead of threshold
- Changed context promotion to trigger on context overflow errors instead of a configurable threshold percentage.
- Removed the contextPromotion.thresholdPercent configuration setting.
- Updated context promotion to retry immediately on the promoted model without requiring compaction.
- Refactored context promotion logic to attempt promotion before compaction in the overflow handling flow.
- Updated agent session to merge context promotion checks into the compaction method for unified overflow handling.
- Updated tests to reflect overflow-based promotion triggering instead of threshold-based promotion.
2026-02-16 18:33:05 +01:00

151 lines
4.6 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
import * as path from "node:path";
import { Agent } from "@oh-my-pi/pi-agent-core";
import type { AssistantMessage, Model, ProviderSessionState } from "@oh-my-pi/pi-ai";
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session";
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
import { TempDir } from "@oh-my-pi/pi-utils";
describe("AgentSession context promotion", () => {
let tempDir: TempDir;
let session: AgentSession;
let modelRegistry: ModelRegistry;
let authStorage: AuthStorage;
beforeEach(async () => {
tempDir = TempDir.createSync("@pi-context-promotion-");
authStorage = await AuthStorage.create(path.join(tempDir.path(), "testauth.db"));
authStorage.setRuntimeApiKey("openai-codex", "test-key");
modelRegistry = new ModelRegistry(authStorage);
});
afterEach(async () => {
if (session) {
await session.dispose();
}
tempDir.removeSync();
});
function createOverflowMessage(model: Model): AssistantMessage {
return {
role: "assistant",
content: [{ type: "text", text: "" }],
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "error",
errorMessage: "context_length_exceeded: Your input exceeds the context window of this model.",
timestamp: Date.now(),
};
}
async function waitFor(predicate: () => boolean, timeoutMs = 500): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
if (predicate()) return;
await Bun.sleep(10);
}
throw new Error("Timed out waiting for condition");
}
it("promotes to a larger-context model on overflow and clears codex websocket session state", async () => {
const sparkModel = modelRegistry.find("openai-codex", "gpt-5.3-codex-spark");
const codexModel = modelRegistry.find("openai-codex", "gpt-5.3-codex");
if (!sparkModel || !codexModel) {
throw new Error("Expected codex spark and codex models to exist");
}
const settings = Settings.isolated({
"compaction.enabled": false,
"contextPromotion.enabled": true,
});
const agent = new Agent({
initialState: {
model: sparkModel,
systemPrompt: "Test",
tools: [],
messages: [],
},
});
session = new AgentSession({
agent,
sessionManager: SessionManager.inMemory(),
settings,
modelRegistry,
});
const closeSpy = vi.fn();
session.providerSessionState.set("openai-codex-responses", {
close: closeSpy,
} satisfies ProviderSessionState);
const overflowMessage = createOverflowMessage(sparkModel);
session.agent.emitExternalEvent({ type: "message_end", message: overflowMessage });
session.agent.emitExternalEvent({ type: "agent_end", messages: [overflowMessage] });
await waitFor(() => session.model?.id === codexModel.id);
expect(session.model?.provider).toBe(codexModel.provider);
expect(session.model?.id).toBe(codexModel.id);
expect(closeSpy).toHaveBeenCalledTimes(1);
expect(session.providerSessionState.size).toBe(0);
});
it("does not promote when promotion is disabled", async () => {
const sparkModel = modelRegistry.find("openai-codex", "gpt-5.3-codex-spark");
if (!sparkModel) {
throw new Error("Expected codex spark model to exist");
}
const settings = Settings.isolated({
"compaction.enabled": false,
"contextPromotion.enabled": false,
});
const agent = new Agent({
initialState: {
model: sparkModel,
systemPrompt: "Test",
tools: [],
messages: [],
},
});
session = new AgentSession({
agent,
sessionManager: SessionManager.inMemory(),
settings,
modelRegistry,
});
const closeSpy = vi.fn();
session.providerSessionState.set("openai-codex-responses", {
close: closeSpy,
} satisfies ProviderSessionState);
const overflowMessage = createOverflowMessage(sparkModel);
session.agent.emitExternalEvent({ type: "message_end", message: overflowMessage });
session.agent.emitExternalEvent({ type: "agent_end", messages: [overflowMessage] });
await Bun.sleep(30);
expect(session.model?.provider).toBe(sparkModel.provider);
expect(session.model?.id).toBe(sparkModel.id);
expect(closeSpy).not.toHaveBeenCalled();
expect(session.providerSessionState.size).toBe(1);
});
});