fix(codex): keep fast mode durable without prompt preflight #10

Merged
eleith merged 1 commits from fix/codex-fast-no-expiry into main 2026-11-05 05:07:49 +00:00
9 changed files with 550 additions and 526 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@eleith-pi/codex": patch
---
Keep saved fast-mode choices active without expiry across reloads, tree navigation, and model reselection. Check eligibility only when explicitly enabling fast mode, avoiding fast-mode authentication and catalog preparation on prompts while preserving scope checks and requested-versus-reported tier status.
+1 -30
View File
@@ -21,40 +21,11 @@ pi install npm:@eleith-pi/codex
- `/eleith:codex context on` selects the verified local window when larger.
- `/eleith:codex context off` returns to the catalog baseline, confirming reductions.
- `/eleith:codex context toggle` switches the saved context preference.
- `/eleith:codex fast on` requests priority processing after confirmation of higher allowance use.
- `/eleith:codex fast on` checks catalog eligibility and requests priority processing after confirmation of higher allowance use.
- `/eleith:codex fast off` requests standard processing.
- `/eleith:codex fast toggle` switches the saved fast preference.
- `/eleith:codex usage` fetches account allowance and reset information. in the terminal, the cancel key (Escape by default) aborts the request.
bare `context` and `fast` show the same dashboard as the root command, without account or catalog requests.
fast controls require a direct official legacy `openai-codex` selection with Pi OAuth and no provider override.
enabling priority also requires catalog eligibility.
priority is a request, not a scheduling guarantee; reported response tiers are shown separately.
context and fast preferences persist per model on the active branch across reloads. the catalog baseline is not guessed:
missing baselines are reported, and models already at or above the verified window
get a no-extension notice rather than a smaller window.
local context preference replay only runs while Pi is idle. after tree navigation it waits for
normal user input, before Pi checks compaction. custom-message-triggered turns use
the current window without replaying pending preferences; explicit context and fast
changes also require idle.
### migrate
when migrating from `@eleith-pi/context-codex`, run its old `/eleith:context-codex restore`
command with that extension loaded before removing it:
```sh
pi remove npm:@eleith-pi/context-codex
pi install npm:@eleith-pi/codex
```
if migrating from the older `@eleith-pi/context-window` instead, first use its
`/eleith:context restore` command, then remove that package before installing codex.
branch preferences are preserved. do not load the predecessor and codex together.
## config
set a command prefix in `~/.pi/agent/extensions/eleith.json`:
+15 -2
View File
@@ -25,7 +25,6 @@ const context: ContextStatus = {
};
const inherited: FastStatus = {
preference: "inherit",
eligibility: "unknown",
requestPolicy: "untouched",
lastObserved: undefined,
conflict: false,
@@ -63,7 +62,6 @@ it("does not present requested priority as confirmed server processing", () => {
const fast: FastStatus = {
...inherited,
preference: "on",
eligibility: "supported",
requestPolicy: "priority",
lastObserved: "default",
};
@@ -73,6 +71,21 @@ it("does not present requested priority as confirmed server processing", () => {
expect(text).toContain("Context: 40k / 272k tokens (standard)");
});
it("shows a saved on choice without needing an eligibility snapshot or a new response", () => {
const fast: FastStatus = {
...inherited,
preference: "on",
requestPolicy: "priority",
};
const original = structuredClone(fast);
const text = formatCodexDashboard(context, fast, "eleith:codex");
expect(text).toContain("Fast: On (priority requested)\nNo response tier reported yet.");
expect(text).toContain("Priority requests use more allowance.");
expect(text).not.toContain("Paused");
expect(text).not.toContain("Eligibility:");
expect(fast).toEqual(original);
});
it("keeps the effective context window distinct from the selected model window", () => {
const snapshot: ContextStatus = {
...context,
+310 -8
View File
@@ -36,6 +36,7 @@ type Transport = "sse" | "websocket";
beforeEach(blockUnexpectedNetwork);
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
@@ -50,10 +51,11 @@ it.each(["sse", "websocket"] as const)(
const network = installResponseTransports(transport);
const { runtime, model, accessToken, credentialWrites, resolveAuth, modelTurns } =
await createOfflineModelRuntime(transport, network.fetch);
const { session, manager, confirm, notify, errors, closeSession } =
const { session, manager, confirm, notify, input, beforeStart, errors, closeSession } =
await openRealExtensionSession(runtime, model);
try {
const branch = structuredClone(manager.getBranch());
expect(resolveAuth).not.toHaveBeenCalled();
resolveAuth.mockClear();
let dashboard: string | undefined;
for (const command of ["/eleith:codex", "/eleith:codex fast", "/eleith:codex context"]) {
@@ -78,6 +80,8 @@ it.each(["sse", "websocket"] as const)(
await session.prompt("/eleith:codex fast on");
expect(confirm).toHaveBeenCalledOnce();
// Catalog discovery resolves OAuth once; there is no separate auth-scope lookup.
expect(resolveAuth).toHaveBeenCalledExactlyOnceWith("openai-codex");
expect(network.catalog).toHaveBeenCalledOnce();
expect(network.catalog.mock.calls[0][0]).toBe(CATALOG_URL);
const headers = new Headers(network.catalog.mock.calls[0][1]?.headers);
@@ -92,10 +96,24 @@ it.each(["sse", "websocket"] as const)(
"info",
);
const enabledBranch = structuredClone(manager.getBranch());
resolveAuth.mockClear();
// Already on is a local no-op, even if discovery has become unavailable.
network.catalog.mockRejectedValue(new Error("Catalog must not be consulted after enabling"));
await session.prompt("/eleith:codex fast on");
expect(confirm).toHaveBeenCalledOnce();
expect(network.catalog).toHaveBeenCalledOnce();
expect(resolveAuth).not.toHaveBeenCalled();
expect(manager.getBranch()).toEqual(enabledBranch);
await session.prompt("Reply with ok.");
// Ordinary input checks the cached catalog's auth scope through Pi resolution only.
expect(resolveAuth).toHaveBeenCalledWith("openai-codex");
expect(input).toHaveBeenCalledExactlyOnceWith({
text: "Reply with ok.",
streamingBehavior: undefined,
authCalls: 0,
});
expect(beforeStart).toHaveBeenCalledExactlyOnceWith({ messageType: "user", authCalls: 0 });
expectNativeAuthOnly(resolveAuth);
expect(network.catalog).toHaveBeenCalledOnce();
expect(modelTurns).toHaveBeenCalledOnce();
expect(network.payloads).toEqual([
@@ -169,16 +187,19 @@ it("SDK replays the active fast branch alongside context controls, explicit off
model,
manager,
);
// A resumed on preference does not depend on successful catalog discovery.
network.catalog.mockRejectedValue(new Error("Resumed preferences must not fetch the catalog"));
let extendedWindow: number | undefined;
const outputLimit = session.model?.maxTokens;
const thinking = session.thinkingLevel;
try {
expect(network.fetch).not.toHaveBeenCalled();
expect(resolveAuth).not.toHaveBeenCalled();
const branch = structuredClone(manager.getBranch());
resolveAuth.mockClear();
await session.prompt("/eleith:codex fast");
expect(notify).toHaveBeenLastCalledWith(
expect.stringContaining("Fast: Paused — model eligibility needs checking"),
expect.stringContaining("Fast: On (priority requested)"),
"info",
);
expect(notify).toHaveBeenLastCalledWith(expect.stringContaining("Context:"), "info");
@@ -195,9 +216,11 @@ it("SDK replays the active fast branch alongside context controls, explicit off
expect(session.thinkingLevel).toBe(thinking);
expect(network.fetch).not.toHaveBeenCalled();
// No explicit fast command fetched metadata in this resumed session.
// Saved on immediately requests priority, without an explicit fast command or discovery.
resolveAuth.mockClear();
await session.prompt("Reply with ok on the resumed branch.");
expect(network.catalog).toHaveBeenCalledOnce();
expectNativeAuthOnly(resolveAuth);
expect(network.catalog).not.toHaveBeenCalled();
expect(network.payloads[0]).toMatchObject({ model: model.id, service_tier: "priority" });
expect(fastChoices(manager)).toEqual([saved]);
expect(confirm).not.toHaveBeenCalled();
@@ -247,7 +270,7 @@ it("SDK replays the active fast branch alongside context controls, explicit off
expect(network.payloads).toHaveLength(3);
expect(legacy.session.getLastAssistantText()).toBe("ok");
expect(modelTurns).toHaveBeenCalledTimes(3);
expect(network.catalog).toHaveBeenCalledOnce();
expect(network.catalog).not.toHaveBeenCalled();
expect(legacy.session.model?.contextWindow).toBe(extendedWindow);
expect(legacy.session.model?.maxTokens).toBe(outputLimit);
expect(legacy.session.thinkingLevel).toBe(thinking);
@@ -259,6 +282,250 @@ it("SDK replays the active fast branch alongside context controls, explicit off
}
});
it("SDK discovers eligibility only after confirmation for each explicit transition to on", async () => {
const network = installResponseTransports("sse");
const { runtime, model, resolveAuth, modelTurns, credentialWrites } =
await createOfflineModelRuntime("sse", network.fetch);
const f = await openRealExtensionSession(runtime, model);
const confirming = deferred();
const decision = deferred();
let enabling: Promise<void> | undefined;
try {
f.confirm.mockResolvedValueOnce(false);
await f.session.prompt("/eleith:codex fast on");
expect(resolveAuth).not.toHaveBeenCalled();
expect(network.fetch).not.toHaveBeenCalled();
expect(fastChoices(f.manager)).toEqual([]);
f.confirm.mockImplementationOnce(async () => {
confirming.resolve();
await decision.promise;
return true;
});
enabling = f.session.prompt("/eleith:codex fast on");
await confirming.promise;
expect(resolveAuth).not.toHaveBeenCalled();
expect(network.fetch).not.toHaveBeenCalled();
expect(fastChoices(f.manager)).toEqual([]);
decision.resolve();
await enabling;
expect(resolveAuth).toHaveBeenCalledExactlyOnceWith("openai-codex");
expect(network.catalog).toHaveBeenCalledOnce();
expect(fastChoices(f.manager)).toEqual([
{ provider: "openai-codex", modelId: model.id, mode: "on" },
]);
resolveAuth.mockClear();
await f.session.prompt("/eleith:codex fast off");
expect(resolveAuth).not.toHaveBeenCalled();
expect(network.catalog).toHaveBeenCalledOnce();
await f.session.prompt("/eleith:codex fast toggle");
expect(f.confirm).toHaveBeenCalledTimes(3);
expect(resolveAuth).toHaveBeenCalledExactlyOnceWith("openai-codex");
expect(network.catalog).toHaveBeenCalledTimes(2);
expect(fastChoices(f.manager)).toEqual([
{ provider: "openai-codex", modelId: model.id, mode: "on" },
{ provider: "openai-codex", modelId: model.id, mode: "off" },
{ provider: "openai-codex", modelId: model.id, mode: "on" },
]);
expect(modelTurns).not.toHaveBeenCalled();
expect(credentialWrites).not.toHaveBeenCalled();
expect(f.errors).toEqual([]);
} finally {
decision.resolve();
await enabling;
await f.closeSession();
}
});
it.each(["sse", "websocket"] as const)(
"SDK saved priority survives expiry, reload, tree replay and model reselection on %s",
async (transport) => {
// Fake timeout clocks too: an expiry callback must not silently revoke the saved request.
// Microtasks and the provider's other scheduling primitives remain real.
vi.useFakeTimers({ toFake: ["Date", "setTimeout", "clearTimeout"] });
const network = installResponseTransports(transport);
const { runtime, model, resolveAuth, modelTurns, credentialWrites } =
await createOfflineModelRuntime(transport, network.fetch);
const f = await openRealExtensionSession(runtime, model);
try {
await f.session.prompt("/eleith:codex fast on");
expect(resolveAuth).toHaveBeenCalledExactlyOnceWith("openai-codex");
const onLeaf = f.manager.getLeafId()!;
const saved = { provider: "openai-codex", modelId: model.id, mode: "on" };
network.catalog.mockRejectedValue(new Error("Prompt/lifecycle discovery forbidden"));
resolveAuth.mockClear();
async function statusIsOn() {
resolveAuth.mockClear();
await f.session.prompt("/eleith:codex fast");
expect(f.notify).toHaveBeenLastCalledWith(
expect.stringContaining("Fast: On (priority requested)"),
"info",
);
expect(resolveAuth).not.toHaveBeenCalled();
expect(network.catalog).toHaveBeenCalledOnce();
}
async function priorityTurn(custom: boolean) {
resolveAuth.mockClear();
f.input.mockClear();
f.beforeStart.mockClear();
const turns = modelTurns.mock.calls.length;
if (custom) {
await f.session.sendCustomMessage(
{ customType: "fast-sdk", content: "Reply with ok.", display: false },
{ triggerTurn: true },
);
expect(f.input).not.toHaveBeenCalled();
} else {
await f.session.prompt("Reply with ok.");
expect(f.input).toHaveBeenCalledExactlyOnceWith({
text: "Reply with ok.",
streamingBehavior: undefined,
authCalls: 0,
});
}
expect(f.beforeStart).toHaveBeenCalledExactlyOnceWith({
messageType: custom ? "custom" : "user",
authCalls: 0,
});
expectNativeAuthOnly(resolveAuth);
expect(network.catalog).toHaveBeenCalledOnce();
expect(modelTurns).toHaveBeenCalledTimes(turns + 1);
expect(network.payloads.at(-1)).toMatchObject({
model: model.id,
service_tier: "priority",
});
expect(f.session.getLastAssistantText()).toBe("ok");
}
await vi.advanceTimersByTimeAsync(6 * 60_000);
await statusIsOn();
await priorityTurn(true); // No idle input preparation can rescue an expired request policy.
resolveAuth.mockClear();
await f.session.reload();
expect(resolveAuth).not.toHaveBeenCalled();
expect(network.catalog).toHaveBeenCalledOnce();
await statusIsOn();
await priorityTurn(false);
await f.session.prompt("/eleith:codex fast off");
expect(fastChoices(f.manager)).toEqual([saved, { ...saved, mode: "off" }]);
resolveAuth.mockClear();
expect(await f.session.navigateTree(onLeaf)).toMatchObject({ cancelled: false });
expect(resolveAuth).not.toHaveBeenCalled();
expect(fastChoices(f.manager)).toEqual([saved]);
await statusIsOn();
await priorityTurn(true); // Tree restoration also works before any ordinary input.
const other = runtime.getModel("openai-codex", "gpt-6.1-sol");
if (!other) throw new Error("Second built-in Codex model missing");
resolveAuth.mockClear();
await f.session.setModel(other);
await f.session.setModel(model);
expect(f.modelSelect).toHaveBeenCalledTimes(2); // Exercise real invalidation, not same-ID suppression.
expect(resolveAuth).not.toHaveBeenCalled();
await statusIsOn();
await priorityTurn(true);
// Also verify idempotence after replay/invalidation, but only AFTER testing the
// native requests so an on command cannot accidentally repair their policy.
resolveAuth.mockClear();
const branch = structuredClone(f.manager.getBranch());
await f.session.prompt("/eleith:codex fast on");
expect(f.manager.getBranch()).toEqual(branch);
expect(resolveAuth).not.toHaveBeenCalled();
expect(network.catalog).toHaveBeenCalledOnce();
expect(network.payloads).toHaveLength(4);
expect(fastChoices(f.manager)).toEqual([saved]);
expect(f.confirm).toHaveBeenCalledOnce();
expect(credentialWrites).not.toHaveBeenCalled();
expect(f.errors).toEqual([]);
} finally {
await f.closeSession();
}
},
);
it.each(["prompt", "queue"] as const)(
"SDK %s steering and follow-up input never adds auth or catalog work while fast is on",
async (submission) => {
const network = installResponseTransports("sse");
const { runtime, model, resolveAuth, modelTurns, credentialWrites } =
await createOfflineModelRuntime("sse", network.fetch);
const f = await openRealExtensionSession(runtime, model);
const started = deferred();
const blocked = deferred();
let running: Promise<void> | undefined;
try {
await f.session.prompt("/eleith:codex fast on");
network.catalog.mockRejectedValue(new Error("Queued prompts must not discover eligibility"));
const respond = network.sse.getMockImplementation()!;
network.sse.mockImplementationOnce(async (...args) => {
started.resolve();
await blocked.promise;
return respond(...args);
});
resolveAuth.mockClear();
running = f.session.prompt("Start with ok.");
await started.promise;
expect(f.session.isStreaming).toBe(true);
expectNativeAuthOnly(resolveAuth);
expect(f.input).toHaveBeenCalledExactlyOnceWith({
text: "Start with ok.",
streamingBehavior: undefined,
authCalls: 0,
});
resolveAuth.mockClear();
f.input.mockClear();
if (submission === "prompt") {
await f.session.prompt("Steer with ok.", { streamingBehavior: "steer" });
await f.session.prompt("Follow with ok.", { streamingBehavior: "followUp" });
} else {
expect(await f.session.steer("Steer with ok.")).toBe("queued");
expect(await f.session.followUp("Follow with ok.")).toBe("queued");
}
expect(f.input.mock.calls).toEqual([
[{ text: "Steer with ok.", streamingBehavior: "steer", authCalls: 0 }],
[{ text: "Follow with ok.", streamingBehavior: "followUp", authCalls: 0 }],
]);
expect(resolveAuth).not.toHaveBeenCalled();
expect(network.catalog).toHaveBeenCalledOnce();
expect(modelTurns).toHaveBeenCalledOnce();
blocked.resolve();
await running;
await f.session.waitForIdle();
expectNativeAuthOnly(resolveAuth);
expect(modelTurns).toHaveBeenCalledTimes(3);
expect(network.catalog).toHaveBeenCalledOnce();
expect(network.payloads).toEqual(
Array.from({ length: 3 }, () =>
expect.objectContaining({ model: model.id, service_tier: "priority" }),
),
);
expect(f.confirm).toHaveBeenCalledOnce();
expect(credentialWrites).not.toHaveBeenCalled();
expect(f.errors).toEqual([]);
} finally {
blocked.resolve();
await running;
await f.closeSession();
}
},
);
function deferred() {
let resolve!: () => void;
const promise = new Promise<void>((done) => {
resolve = done;
});
return { promise, resolve };
}
// Network policy: globals start closed; the supplied fetch accepts only these two fixed URLs.
function blockUnexpectedNetwork() {
vi.stubGlobal(
@@ -410,6 +677,9 @@ async function openRealExtensionSession(
retry: { enabled: false },
cacheWarming: "off",
});
const input = vi.fn();
const beforeStart = vi.fn();
const modelSelect = vi.fn();
const resourceLoader = new DefaultResourceLoader({
cwd: directory,
agentDir: directory,
@@ -419,7 +689,26 @@ async function openRealExtensionSession(
noPromptTemplates: true,
noThemes: true,
noContextFiles: true,
extensionFactories: [codex],
extensionFactories: [
codex,
(pi) => {
// These handlers run AFTER codex but BEFORE native request authentication.
pi.on("input", (event) => {
input({
text: event.text,
streamingBehavior: event.streamingBehavior,
authCalls: vi.mocked(runtime.getAuth).mock.calls.length,
});
});
pi.on("before_agent_start", (event) => {
beforeStart({
messageType: event.messageType,
authCalls: vi.mocked(runtime.getAuth).mock.calls.length,
});
});
pi.on("model_select", modelSelect);
},
],
});
await resourceLoader.reload();
expect(resourceLoader.getExtensions().errors).toEqual([]);
@@ -447,6 +736,9 @@ async function openRealExtensionSession(
manager,
confirm,
notify,
input,
beforeStart,
modelSelect,
errors,
async closeSession() {
await session.extensionRunner.emit({ type: "session_shutdown", reason: "quit" });
@@ -455,6 +747,16 @@ async function openRealExtensionSession(
};
}
// Native provider resolution is essential and uses a model, not the extension's provider lookup.
function expectNativeAuthOnly(
resolveAuth: Awaited<ReturnType<typeof createOfflineModelRuntime>>["resolveAuth"],
) {
expect(resolveAuth).toHaveBeenCalled();
for (const [selected] of resolveAuth.mock.calls) {
expect(selected).toEqual(expect.objectContaining({ provider: "openai-codex" }));
}
}
function fastChoices(manager: SessionManager) {
return manager
.getBranch()
+6 -28
View File
@@ -11,10 +11,9 @@ it("describes off as a standard request, not an observed processing tier", () =>
expect(formatFastStatus(fast)).toBe("Fast: Off (standard requested)");
});
it("describes on as a priority request without claiming priority was delivered", () => {
it("describes a saved on choice as priority without eligibility state or a reported tier", () => {
const fast = createFastStatusFixture({
preference: "on",
eligibility: "supported",
requestPolicy: "priority",
});
expect(formatFastStatus(fast).split("\n")).toEqual([
@@ -27,7 +26,6 @@ it("describes on as a priority request without claiming priority was delivered",
it("keeps next-request priority separate from a reported standard tier", () => {
const fast = createFastStatusFixture({
preference: "on",
eligibility: "supported",
requestPolicy: "priority",
lastObserved: "default",
});
@@ -51,34 +49,16 @@ it.each<[ObservedTier, string]>([
);
});
it("pauses a saved on choice when eligibility needs checking without claiming a current request", () => {
it("keeps a saved on choice intact outside scope and shows priority again when in scope", () => {
const fast = createFastStatusFixture({
preference: "on",
requestPolicy: "suspended",
});
expect(formatFastStatus(fast)).toBe("Fast: Paused — model eligibility needs checking");
});
it("explains when priority is not available without erasing the saved preference", () => {
const fast = createFastStatusFixture({
preference: "on",
eligibility: "unsupported",
requestPolicy: "suspended",
lastObserved: "default",
});
expect(formatFastStatus(fast)).toBe(
"Fast: Paused — priority is not available for this model\nLast reported tier: Standard",
);
expect(fast.preference).toBe("on");
});
it("gives outside scope precedence over the saved on preference and supported eligibility", () => {
const fast = createFastStatusFixture({
preference: "on",
eligibility: "supported",
requestPolicy: "outside scope",
});
expect(formatFastStatus(fast)).toBe("Fast: Unavailable for this model/provider");
expect(fast.preference).toBe("on");
expect(formatFastStatus({ ...fast, requestPolicy: "priority" })).toBe(
"Fast: On (priority requested)\nNo response tier reported yet.\nPriority requests use more allowance.",
);
});
it.each(["priority", "default"] as const)(
@@ -86,7 +66,6 @@ it.each(["priority", "default"] as const)(
(requestPolicy) => {
const fast = createFastStatusFixture({
preference: requestPolicy === "priority" ? "on" : "off",
eligibility: "supported",
requestPolicy,
lastObserved: "flex",
conflict: true,
@@ -111,7 +90,6 @@ it.each(["priority", "default"] as const)(
function createFastStatusFixture(overrides: Partial<FastStatus> = {}): FastStatus {
return {
preference: "inherit",
eligibility: "unknown",
requestPolicy: "untouched",
lastObserved: undefined,
conflict: false,
+1 -9
View File
@@ -1,13 +1,11 @@
import type { FastEligibility } from "./capabilities.ts";
import type { FastPreference } from "./fast-state.ts";
export type RequestedTier = "priority" | "default";
export type ObservedTier = RequestedTier | "auto" | "flex" | "fast";
export type FastRequestPolicy = RequestedTier | "untouched" | "suspended" | "outside scope";
export type FastRequestPolicy = RequestedTier | "untouched" | "outside scope";
export interface FastStatus {
preference: FastPreference;
eligibility: FastEligibility;
requestPolicy: FastRequestPolicy;
lastObserved: ObservedTier | undefined;
conflict: boolean;
@@ -25,12 +23,6 @@ export function formatFastStatus(status: FastStatus): string {
case "priority":
headline = "Fast: On (priority requested)";
break;
case "suspended":
headline =
status.eligibility === "unsupported"
? "Fast: Paused — priority is not available for this model"
: "Fast: Paused — model eligibility needs checking";
break;
case "outside scope":
headline = "Fast: Unavailable for this model/provider";
break;
+184 -346
View File
@@ -9,7 +9,6 @@ import {
CodexAccountError,
type CodexModelCatalog,
type fetchCodexModelCatalog,
type readCodexAuthScope,
} from "./client.ts";
import { FastController, type FastHost } from "./fast.ts";
import { FAST_ENTRY, type FastPreference } from "./fast-state.ts";
@@ -47,11 +46,9 @@ describe("commands and ownership", () => {
);
expect(fast.controller.getStatus(fast.ctx)).toMatchObject({
preference: "on",
eligibility: "unknown",
requestPolicy: "suspended",
requestPolicy: "priority",
});
expect(fast.ctx.ui.confirm).not.toHaveBeenCalled();
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.ctx.modelRegistry.getProviderAuth).not.toHaveBeenCalled();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
@@ -67,7 +64,6 @@ describe("commands and ownership", () => {
expect.stringContaining("use more of your Codex allowance"),
{ signal: expect.any(AbortSignal) },
);
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
@@ -88,17 +84,11 @@ describe("commands and ownership", () => {
model: model.id,
service_tier: "default",
});
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
});
it("rejects invalid arguments and mutations during a run without prompting or IO", async () => {
it("rejects mutations during a run without prompting or IO", async () => {
const fast = createFastControllerFixture();
await fast.controller.handle("on extra", fast.ctx);
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
"Usage: /eleith:codex fast [on|off|toggle]",
"warning",
);
fast.ctx.isIdle.mockReturnValue(false);
for (const action of ["on", "off", "toggle"]) await fast.controller.handle(action, fast.ctx);
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
@@ -106,7 +96,6 @@ describe("commands and ownership", () => {
"warning",
);
expect(fast.ctx.ui.confirm).not.toHaveBeenCalled();
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
});
@@ -117,7 +106,6 @@ describe("commands and ownership", () => {
await fast.controller.handle("on", fast.ctx);
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith("No model selected.", "warning");
expect(fast.ctx.ui.confirm).not.toHaveBeenCalled();
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
});
@@ -133,10 +121,8 @@ describe("commands and ownership", () => {
return response.promise;
});
const enabling = fast.controller.handle(" ON ", fast.ctx);
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
// A second mutating command must not compete with the confirmation.
await fast.controller.handle("off", fast.ctx);
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
expect.stringContaining("Wait for the current run"),
@@ -144,14 +130,12 @@ describe("commands and ownership", () => {
);
confirmation.resolve(true);
await started.promise;
expect(fast.readAuthScope).toHaveBeenCalledExactlyOnceWith(
fast.ctx.modelRegistry,
expect.any(AbortSignal),
);
expect(fast.fetchCatalog).toHaveBeenCalledExactlyOnceWith(
fast.ctx.modelRegistry,
expect.any(AbortSignal),
);
// No separate auth-scope preflight; the account client resolves auth for its one request.
expect(fast.ctx.modelRegistry.getProviderAuth).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
response.resolve(catalog);
@@ -172,24 +156,43 @@ describe("commands and ownership", () => {
});
it.each([
{ models: [{ slug: otherModel.id, service_tiers: [{ id: "priority" }] }] },
{ models: [{ slug: model.id, service_tiers: [{ id: "default" }] }] },
])(
"does not enable without priority support for the selected catalog model: %j",
async (data) => {
const fast = createFastControllerFixture();
fast.fetchCatalog.mockResolvedValue({ ...catalog, data });
await fast.controller.handle("on", fast.ctx);
expect(fast.appendEntry).not.toHaveBeenCalled();
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
expect.stringContaining("Preference unchanged"),
"warning",
);
{
data: { models: [{ slug: otherModel.id, service_tiers: [{ id: "priority" }] }] },
reason: "did not establish",
},
);
{
data: { models: [{ slug: model.id, service_tiers: [{ id: "default" }] }] },
reason: "does not advertise",
},
])("does not enable without established model support: %j", async ({ data, reason }) => {
const fast = createFastControllerFixture();
fast.fetchCatalog.mockResolvedValue({ ...catalog, data });
await fast.controller.handle("on", fast.ctx);
expect(fast.appendEntry).not.toHaveBeenCalled();
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(expect.stringContaining(reason), "warning");
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
expect.stringContaining("Preference unchanged"),
"warning",
);
});
it("copies a frozen payload for explicit default, preserves fields, then restores a legacy inherit entry", async () => {
it("treats an already saved on as idempotent without confirmation or discovery", async () => {
const fast = createFastControllerFixture();
fast.ctx.sessionManager.getBranch.mockReturnValue([choice("on")]);
fast.controller.restore(fast.ctx);
fast.ctx.hasUI = false;
await fast.controller.handle("on", fast.ctx);
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
expect.stringContaining("Fast: On (priority requested)"),
"info",
);
expect(fast.fetchCatalog).not.toHaveBeenCalled();
expect(fast.ctx.ui.confirm).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
});
it("copies a frozen payload for explicit default and restores a legacy inherit entry", async () => {
const fast = createFastControllerFixture();
const input = Object.freeze([{ role: "user", content: "hello" }]);
const payload = Object.freeze({
@@ -222,29 +225,8 @@ describe("commands and ownership", () => {
preference: "inherit",
requestPolicy: "untouched",
});
await fast.controller.prepare(fast.ctx);
fast.controller.restore(fast.ctx);
expect(fast.appendEntry).toHaveBeenCalledOnce();
expect(fast.ctx.ui.confirm).not.toHaveBeenCalled();
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
});
it("restores a legacy inherit entry after the selection leaves direct scope without writing", async () => {
const fast = createFastControllerFixture();
await fast.controller.handle("off", fast.ctx);
fast.ctx.modelRegistry.isUsingOAuth.mockReturnValue(false);
fast.ctx.sessionManager.getBranch.mockReturnValue([choice("off"), choice("inherit")]);
fast.controller.restore(fast.ctx);
expect(fast.controller.getStatus(fast.ctx)).toMatchObject({
preference: "inherit",
requestPolicy: "outside scope",
});
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
expect(fast.appendEntry).toHaveBeenCalledOnce();
expect(fast.ctx.ui.confirm).not.toHaveBeenCalled();
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
});
it.each(["on", "off"])(
@@ -264,17 +246,13 @@ describe("commands and ownership", () => {
model: model.id,
service_tier: tier,
});
expect(fast.controller.getStatus(fast.ctx)).toMatchObject({
conflict: false,
requestPolicy: tier,
});
expect(fast.controller.getStatus(fast.ctx)).toMatchObject({ conflict: false });
},
);
it("toggle confirms only transitions to on and leaves off intact if the next confirmation is declined", async () => {
it("toggle confirms only transitions to on and preserves off if confirmation is declined", async () => {
const fast = createFastControllerFixture();
await fast.controller.handle("toggle", fast.ctx);
expect(fast.ctx.ui.confirm).toHaveBeenCalledOnce();
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
await fast.controller.handle("toggle", fast.ctx);
expect(fast.ctx.ui.confirm).toHaveBeenCalledOnce();
@@ -289,25 +267,56 @@ describe("commands and ownership", () => {
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("default");
expect(fast.fetchCatalog).toHaveBeenCalledOnce();
});
it("honors server Retry-After only for explicit enable attempts, without automatic retries", async () => {
const fast = createFastControllerFixture();
fast.fetchCatalog.mockRejectedValueOnce(
new CodexAccountError("http", "Codex account service is rate limited.", 60_000),
);
await fast.controller.handle("on", fast.ctx);
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
"Codex account service is rate limited. Preference unchanged.",
"warning",
);
await fast.controller.handle("on", fast.ctx);
expect(fast.fetchCatalog).toHaveBeenCalledOnce();
expect(fast.ctx.ui.confirm).toHaveBeenCalledOnce();
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
expect.stringContaining("Try fast on again later"),
"warning",
);
fast.clock.now += 60_000;
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
expect(fast.fetchCatalog).toHaveBeenCalledOnce();
await fast.controller.handle("on", fast.ctx);
expect(fast.fetchCatalog).toHaveBeenCalledTimes(2);
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
});
it("does not expose unexpected discovery errors or change an existing off preference", async () => {
const fast = createFastControllerFixture();
await fast.controller.handle("off", fast.ctx);
fast.fetchCatalog.mockRejectedValueOnce(new Error("private failure details must not appear"));
await fast.controller.handle("on", fast.ctx);
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
"Could not enable Codex fast mode. Preference unchanged.",
"warning",
);
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("default");
expect(fast.appendEntry).toHaveBeenCalledOnce();
});
});
describe("branch replay and preparation", () => {
it("replays only the current branch, separates model choices, and ignores context choices", async () => {
describe("durable branch preferences", () => {
it("replays the active branch immediately without discovery, keeping model and context choices separate", () => {
const fast = createFastControllerFixture();
fast.ctx.sessionManager.getBranch.mockReturnValue([
choice("off"),
choice("on"),
choice("off", otherModel),
{
...choice("off"),
customType: CONTEXT_ENTRY,
data: { provider: model.provider, modelId: model.id, extended: true, mode: "off" },
},
{ ...choice("off"), customType: CONTEXT_ENTRY },
]);
fast.controller.restore(fast.ctx);
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
await fast.controller.prepare(fast.ctx);
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
fast.ctx.model = otherModel;
expect(fast.controller.transform({ model: otherModel.id }, fast.ctx)?.service_tier).toBe(
@@ -318,171 +327,60 @@ describe("branch replay and preparation", () => {
expect(fast.controller.transform({ model: otherModel.id }, fast.ctx)).toBeUndefined();
fast.ctx.model = model;
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
await fast.controller.prepare(fast.ctx);
expect(fast.readAuthScope).toHaveBeenCalledOnce();
expect(fast.fetchCatalog).toHaveBeenCalledOnce();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
expect(fast.ctx.modelRegistry.getProviderAuth).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
expect(fast.ctx.ui.confirm).not.toHaveBeenCalled();
});
it("lets fresh on expire at the TTL without hook IO or erasing the suspended preference", async () => {
it("keeps priority after five minutes, days, invalidation and reload without any preflight", async () => {
const fast = createFastControllerFixture();
await fast.controller.handle("on", fast.ctx);
fast.clock.now += 5 * 60_000 - 1;
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
fast.clock.now += 1;
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
fast.controller.observe(
stream({ type: "response.completed", response: { service_tier: "default" } }),
fast.ctx,
);
expect(fast.controller.getStatus(fast.ctx)).toMatchObject({
preference: "on",
eligibility: "unknown",
requestPolicy: "suspended",
});
expect(fast.readAuthScope).toHaveBeenCalledOnce();
expect(fast.fetchCatalog).toHaveBeenCalledOnce();
expect(fast.appendEntry).toHaveBeenCalledOnce();
await fast.controller.prepare(fast.ctx);
expect(fast.fetchCatalog).toHaveBeenCalledTimes(2);
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
expect(fast.appendEntry).toHaveBeenCalledOnce();
expect(fast.ctx.ui.confirm).toHaveBeenCalledOnce();
});
it("prepares only idle saved-on input, checking auth even when the catalog cache is fresh", async () => {
const fast = createFastControllerFixture();
for (const branch of [[], [choice("off")], [choice("inherit")]]) {
fast.ctx.sessionManager.getBranch.mockReturnValue(branch);
fast.controller.restore(fast.ctx);
await fast.controller.prepare(fast.ctx);
}
expect(fast.readAuthScope).not.toHaveBeenCalled();
fast.ctx.sessionManager.getBranch.mockReturnValue([choice("on")]);
fast.controller.restore(fast.ctx);
fast.fetchCatalog.mockRejectedValue(new Error("Catalog must not be needed again"));
fast.ctx.modelRegistry.getProviderAuth.mockRejectedValue(new Error("No auth preflight"));
fast.ctx.isIdle.mockReturnValue(false);
await fast.controller.prepare(fast.ctx);
expect(fast.readAuthScope).not.toHaveBeenCalled();
fast.ctx.isIdle.mockReturnValue(true);
await fast.controller.prepare(fast.ctx);
await fast.controller.prepare(fast.ctx);
expect(fast.readAuthScope).toHaveBeenCalledTimes(2);
expect(fast.fetchCatalog).toHaveBeenCalledOnce();
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
expect(fast.ctx.ui.confirm).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
});
it("invalidates a fresh catalog on auth scope change and recatalogs before resuming", async () => {
const fast = createFastControllerFixture();
fast.ctx.sessionManager.getBranch.mockReturnValue([choice("on")]);
fast.controller.restore(fast.ctx);
await fast.controller.prepare(fast.ctx);
fast.readAuthScope.mockResolvedValue("scope-b");
fast.fetchCatalog.mockResolvedValue({
authScope: "scope-b",
data: { models: [{ slug: model.id, service_tiers: [{ id: "default" }] }] },
});
await fast.controller.prepare(fast.ctx);
expect(fast.fetchCatalog).toHaveBeenCalledTimes(2);
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
expect(fast.controller.getStatus(fast.ctx)).toMatchObject({
preference: "on",
eligibility: "unsupported",
requestPolicy: "suspended",
});
fast.readAuthScope.mockResolvedValue("scope-c");
fast.fetchCatalog.mockResolvedValue({ ...catalog, authScope: "scope-c" });
await fast.controller.prepare(fast.ctx);
expect(fast.fetchCatalog).toHaveBeenCalledTimes(3);
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
expect(fast.appendEntry).not.toHaveBeenCalled();
});
it("rejects a catalog fetched under a different auth scope, preserving the preference for a retry", async () => {
const fast = createFastControllerFixture();
fast.ctx.sessionManager.getBranch.mockReturnValue([choice("on")]);
fast.controller.restore(fast.ctx);
fast.fetchCatalog.mockResolvedValueOnce({ ...catalog, authScope: "scope-b" });
await fast.controller.prepare(fast.ctx);
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
expect.stringContaining("authentication changed during discovery"),
"warning",
);
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
await fast.controller.prepare(fast.ctx);
expect(fast.fetchCatalog).toHaveBeenCalledTimes(2);
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
expect(fast.appendEntry).not.toHaveBeenCalled();
});
it("honors 429 backoff without timers or hook retries and resumes only on later input", async () => {
const fast = createFastControllerFixture();
fast.ctx.sessionManager.getBranch.mockReturnValue([choice("on")]);
fast.controller.restore(fast.ctx);
fast.fetchCatalog.mockRejectedValueOnce(
new CodexAccountError("http", "Codex account service is rate limited.", 60_000),
);
await fast.controller.prepare(fast.ctx);
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
expect.stringContaining("rate limited. Preference unchanged."),
"warning",
);
await fast.controller.prepare(fast.ctx);
expect(fast.fetchCatalog).toHaveBeenCalledOnce();
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
expect.stringContaining("Try again later"),
"warning",
);
fast.clock.now += 60_000;
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
fast.controller.observe(
stream({ type: "response.completed", response: { service_tier: "priority" } }),
fast.ctx,
);
expect(fast.controller.getStatus(fast.ctx)).toMatchObject({
preference: "on",
requestPolicy: "suspended",
lastObserved: "priority",
});
expect(fast.fetchCatalog).toHaveBeenCalledOnce();
expect(fast.readAuthScope).toHaveBeenCalledTimes(2);
await fast.controller.prepare(fast.ctx);
expect(fast.fetchCatalog).toHaveBeenCalledTimes(2);
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
expect(fast.appendEntry).not.toHaveBeenCalled();
});
it.each(["auth", "catalog"])(
"does not display unexpected %s errors or clear a saved-on preference",
async (source) => {
const fast = createFastControllerFixture();
fast.ctx.sessionManager.getBranch.mockReturnValue([choice("on")]);
fast.controller.restore(fast.ctx);
await fast.controller.prepare(fast.ctx);
const error = new Error("private failure details must not appear");
if (source === "auth") fast.readAuthScope.mockRejectedValueOnce(error);
else {
fast.clock.now += 5 * 60_000;
fast.fetchCatalog.mockRejectedValueOnce(error);
}
await fast.controller.prepare(fast.ctx);
expect(fast.ctx.ui.notify).toHaveBeenLastCalledWith(
"Fast mode suspended: Could not prepare Codex fast mode. Preference unchanged.",
"warning",
for (const elapsed of [5 * 60_000, 24 * 60 * 60_000]) {
fast.clock.now += elapsed;
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe(
"priority",
);
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
expect(fast.controller.getStatus(fast.ctx)).toMatchObject({
preference: "on",
requestPolicy: "suspended",
});
expect(fast.appendEntry).not.toHaveBeenCalled();
},
);
expect(fast.controller.getStatus(fast.ctx)).toMatchObject({ requestPolicy: "priority" });
}
fast.controller.invalidate();
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
fast.ctx.sessionManager.getBranch.mockReturnValue([choice("on")]);
const reloaded = new FastController(
{ appendEntry: fast.appendEntry },
"eleith:codex",
fast.fetchCatalog,
);
reloaded.restore(fast.ctx);
expect(reloaded.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
expect(fast.fetchCatalog).toHaveBeenCalledOnce();
expect(fast.ctx.modelRegistry.getProviderAuth).not.toHaveBeenCalled();
expect(fast.ctx.ui.confirm).toHaveBeenCalledOnce();
expect(fast.ctx.ui.notify).toHaveBeenCalledOnce();
});
it("keeps saved on while outside scope and resumes it when the direct selection returns", () => {
const fast = createFastControllerFixture();
fast.ctx.sessionManager.getBranch.mockReturnValue([choice("on")]);
fast.controller.restore(fast.ctx);
fast.ctx.modelRegistry.isUsingOAuth.mockReturnValue(false);
expect(fast.controller.getStatus(fast.ctx)).toMatchObject({
preference: "on",
requestPolicy: "outside scope",
});
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
fast.ctx.modelRegistry.isUsingOAuth.mockReturnValue(true);
fast.controller.invalidate();
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
expect(fast.fetchCatalog).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
});
});
describe("asynchronous ownership", () => {
describe("asynchronous enable ownership", () => {
it.each(["model", "session", "leaf"])(
"discards confirmation after the %s changes",
async (changed) => {
@@ -490,12 +388,9 @@ describe("asynchronous ownership", () => {
const confirmation = deferred<boolean>();
fast.ctx.ui.confirm.mockReturnValue(confirmation.promise);
const enabling = fast.controller.handle("on", fast.ctx);
if (changed === "model") fast.ctx.model = otherModel;
if (changed === "session") fast.ctx.sessionManager.getSessionId.mockReturnValue("session-b");
if (changed === "leaf") fast.ctx.sessionManager.getLeafId.mockReturnValue("leaf-b");
changeSelection(fast, changed);
confirmation.resolve(true);
await enabling;
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
expect(fast.ctx.ui.notify).not.toHaveBeenCalled();
@@ -514,9 +409,7 @@ describe("asynchronous ownership", () => {
});
const enabling = fast.controller.handle("on", fast.ctx);
await started.promise;
if (changed === "model") fast.ctx.model = otherModel;
if (changed === "session") fast.ctx.sessionManager.getSessionId.mockReturnValue("session-b");
if (changed === "leaf") fast.ctx.sessionManager.getLeafId.mockReturnValue("leaf-b");
changeSelection(fast, changed);
response.resolve(catalog);
await enabling;
expect(fast.appendEntry).not.toHaveBeenCalled();
@@ -559,113 +452,62 @@ describe("asynchronous ownership", () => {
});
});
it.each(["resolve", "reject"])(
"shutdown ignores late confirmation %s before touching expired host getters",
async (outcome) => {
const fast = createFastControllerFixture();
const confirmation = deferred<boolean>();
fast.ctx.ui.confirm.mockReturnValue(confirmation.promise);
const enabling = fast.controller.handle("on", fast.ctx);
const signal = fast.ctx.ui.confirm.mock.calls[0][2]!.signal!;
const notify = fast.ctx.ui.notify;
fast.controller.dispose();
fast.controller.dispose();
for (const key of ["model", "modelRegistry", "isIdle", "sessionManager", "ui", "hasUI"]) {
Object.defineProperty(fast.ctx, key, {
get: () => {
throw new Error(`Expired ${key}`);
},
it.each(["confirmation", "catalog"])(
"shutdown ignores late %s results before touching expired host getters",
async (stage) => {
for (const outcome of ["resolve", "reject"]) {
const fast = createFastControllerFixture();
const confirmation = deferred<boolean>();
const response = deferred<CodexModelCatalog>();
const started = deferred<void>();
if (stage === "confirmation") fast.ctx.ui.confirm.mockReturnValue(confirmation.promise);
fast.fetchCatalog.mockImplementationOnce(() => {
started.resolve();
return response.promise;
});
const enabling = fast.controller.handle("on", fast.ctx);
if (stage === "catalog") await started.promise;
const signal =
stage === "confirmation"
? fast.ctx.ui.confirm.mock.calls[0][2]!.signal!
: fast.fetchCatalog.mock.calls[0][1]!;
const notify = fast.ctx.ui.notify;
fast.controller.dispose();
fast.controller.dispose();
for (const key of ["model", "modelRegistry", "isIdle", "sessionManager", "ui", "hasUI"]) {
Object.defineProperty(fast.ctx, key, {
get: () => {
throw new Error(`Expired ${key}`);
},
});
}
if (stage === "confirmation") {
if (outcome === "resolve") confirmation.resolve(true);
else confirmation.reject(new Error("private late failure"));
} else {
if (outcome === "resolve") response.resolve(catalog);
else response.reject(new CodexAccountError("http", "private late failure", 60_000));
}
await enabling;
expect(signal.aborted).toBe(true);
expect(notify).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
expect(fast.controller.getStatus(fast.ctx)).toBeUndefined();
await fast.controller.handle("on", fast.ctx);
fast.controller.restore(fast.ctx);
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
fast.controller.observe(stream({ type: "response.completed" }), fast.ctx);
}
if (outcome === "resolve") confirmation.resolve(true);
else confirmation.reject(new Error("private late failure"));
await enabling;
expect(signal.aborted).toBe(true);
expect(notify).not.toHaveBeenCalled();
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
expect(fast.controller.getStatus(fast.ctx)).toBeUndefined();
await fast.controller.handle("on", fast.ctx);
await fast.controller.prepare(fast.ctx);
fast.controller.restore(fast.ctx);
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined();
fast.controller.observe(stream({ type: "response.completed" }), fast.ctx);
},
);
it.each(["resolve", "reject"])(
"shutdown ignores late catalog %s without accessing the expired host",
async (outcome) => {
const fast = createFastControllerFixture();
const response = deferred<CodexModelCatalog>();
const started = deferred<void>();
fast.fetchCatalog.mockImplementationOnce(() => {
started.resolve();
return response.promise;
});
const enabling = fast.controller.handle("on", fast.ctx);
await started.promise;
const signal = fast.fetchCatalog.mock.calls[0][1]!;
const notify = fast.ctx.ui.notify;
fast.controller.dispose();
for (const key of ["model", "modelRegistry", "isIdle", "sessionManager", "ui"]) {
Object.defineProperty(fast.ctx, key, {
get: () => {
throw new Error(`Expired ${key}`);
},
});
}
if (outcome === "resolve") response.resolve(catalog);
else
response.reject(
new CodexAccountError("http", "Codex account service is rate limited.", 60_000),
);
await enabling;
expect(signal.aborted).toBe(true);
expect(notify).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
},
);
it("shutdown stops a saved-on auth check before any late catalog IO or host access", async () => {
const fast = createFastControllerFixture();
fast.ctx.sessionManager.getBranch.mockReturnValue([choice("on")]);
fast.controller.restore(fast.ctx);
const scope = deferred<string>();
fast.readAuthScope.mockReturnValueOnce(scope.promise);
const preparing = fast.controller.prepare(fast.ctx);
const signal = fast.readAuthScope.mock.calls[0][1]!;
const notify = fast.ctx.ui.notify;
fast.controller.dispose();
for (const key of ["model", "modelRegistry", "isIdle", "sessionManager", "ui"]) {
Object.defineProperty(fast.ctx, key, {
get: () => {
throw new Error(`Expired ${key}`);
},
});
}
scope.resolve("scope-a");
await preparing;
expect(signal.aborted).toBe(true);
expect(fast.fetchCatalog).not.toHaveBeenCalled();
expect(notify).not.toHaveBeenCalled();
expect(fast.appendEntry).not.toHaveBeenCalled();
});
});
describe("provider stream observations", () => {
it("reports an observed default independently of the priority payload and next-request policy", async () => {
it("reports observed default independently of the priority request without disabling future requests", async () => {
const fast = createFastControllerFixture();
await fast.controller.handle("on", fast.ctx);
const payload = Object.freeze({ model: model.id, input: [], service_tier: "priority" });
const transformed = fast.controller.transform(payload, fast.ctx);
expect(transformed).toBeUndefined();
expect(transformed ?? payload).toEqual({
model: model.id,
input: [],
service_tier: "priority",
});
expect(fast.controller.transform(payload, fast.ctx)).toBeUndefined();
fast.controller.observe(
stream({ type: "response.completed", response: { service_tier: "default" } }),
fast.ctx,
@@ -677,7 +519,6 @@ describe("provider stream observations", () => {
});
expect(fast.controller.transform({ model: model.id }, fast.ctx)?.service_tier).toBe("priority");
expect(fast.fetchCatalog).toHaveBeenCalledOnce();
expect(fast.readAuthScope).toHaveBeenCalledOnce();
});
it.each([
@@ -686,10 +527,7 @@ describe("provider stream observations", () => {
])("reports missing or unrecognized terminal tier metadata as unknown: %j", async (data) => {
const fast = createFastControllerFixture();
await fast.controller.handle("off", fast.ctx);
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toEqual({
model: model.id,
service_tier: "default",
});
fast.controller.transform({ model: model.id }, fast.ctx);
fast.controller.observe(
stream({ type: "response.completed", response: { service_tier: "default" } }),
fast.ctx,
@@ -699,17 +537,13 @@ describe("provider stream observations", () => {
requestPolicy: "default",
lastObserved: undefined,
});
expect(fast.readAuthScope).not.toHaveBeenCalled();
expect(fast.fetchCatalog).not.toHaveBeenCalled();
});
it("ignores wrong-model and nonterminal events, and does not show delivery from another session", async () => {
it("ignores wrong-model and nonterminal events, and delivery from another session", async () => {
const fast = createFastControllerFixture();
await fast.controller.handle("off", fast.ctx);
expect(fast.controller.transform({ model: model.id }, fast.ctx)).toEqual({
model: model.id,
service_tier: "default",
});
fast.controller.transform({ model: model.id }, fast.ctx);
fast.controller.observe(
stream({ type: "response.completed", response: { service_tier: "default" } }),
fast.ctx,
@@ -795,15 +629,19 @@ function createFastControllerFixture() {
} satisfies FastHost;
const appendEntry = vi.fn<ExtensionAPI["appendEntry"]>();
const fetchCatalog = vi.fn<typeof fetchCodexModelCatalog>(async () => catalog);
const readAuthScope = vi.fn<typeof readCodexAuthScope>(async () => "scope-a");
const controller = new FastController(
{ appendEntry },
"eleith:codex",
fetchCatalog,
readAuthScope,
() => clock.now,
);
return { controller, ctx, appendEntry, fetchCatalog, readAuthScope, clock };
return { controller, ctx, appendEntry, fetchCatalog, clock };
}
function changeSelection(fast: ReturnType<typeof createFastControllerFixture>, changed: string) {
if (changed === "model") fast.ctx.model = otherModel;
if (changed === "session") fast.ctx.sessionManager.getSessionId.mockReturnValue("session-b");
if (changed === "leaf") fast.ctx.sessionManager.getLeafId.mockReturnValue("leaf-b");
}
function stream(data: unknown): ProviderStreamEvent {
+28 -102
View File
@@ -3,13 +3,8 @@ import type {
ExtensionContext,
ProviderStreamEvent,
} from "@earendil-works/pi-coding-agent";
import {
CodexAccountError,
fetchCodexModelCatalog,
readCodexAuthScope,
type CodexAccountRegistry,
} from "./client.ts";
import { codexFastEligibility, type FastEligibility } from "./capabilities.ts";
import { CodexAccountError, fetchCodexModelCatalog, type CodexAccountRegistry } from "./client.ts";
import { codexFastEligibility } from "./capabilities.ts";
import {
FAST_ENTRY,
readFastPreferences,
@@ -29,8 +24,6 @@ import {
type DirectCodexHost,
} from "./request-scope.ts";
const CAPABILITY_TTL_MS = 5 * 60_000;
export interface FastHost extends DirectCodexHost {
readonly modelRegistry: CodexAccountRegistry &
Pick<
@@ -55,13 +48,6 @@ interface EligibilityPreparation {
baseUrl: string;
}
interface CachedEligibility {
modelKey: string;
authScope: string;
eligibility: FastEligibility;
expiresAt: number;
}
interface ModelTierActivity {
modelKey: string;
sessionId: string;
@@ -71,7 +57,6 @@ interface ModelTierActivity {
export class FastController {
private preferences = new Map<string, FastPreference>();
private cachedEligibility: CachedEligibility | undefined;
private pendingPreparation: EligibilityPreparation | undefined;
private modelTierActivity: ModelTierActivity | undefined;
// Honor endpoint Retry-After across model/branch changes; account switching is not supported.
@@ -82,7 +67,6 @@ export class FastController {
private readonly pi: Pick<ExtensionAPI, "appendEntry">,
private readonly commandName: string,
private readonly fetchCatalog: typeof fetchCodexModelCatalog = fetchCodexModelCatalog,
private readonly readAuthScope: typeof readCodexAuthScope = readCodexAuthScope,
private readonly now: () => number = Date.now,
) {}
@@ -106,11 +90,8 @@ export class FastController {
activity.sessionId !== ctx.sessionManager.getSessionId()
)
activity = undefined;
let eligibility: FastEligibility = "unknown";
if (direct) eligibility = this.currentEligibility(selectedModelKey);
return {
preference: this.preferenceFor(selectedModelKey),
eligibility,
requestPolicy: this.requestPolicy(ctx),
lastObserved: activity?.observed,
conflict: activity?.conflict ?? false,
@@ -150,6 +131,10 @@ export class FastController {
this.report(ctx);
return;
}
if (this.preferenceFor(modelKey(model)) === "on") {
this.report(ctx);
return;
}
if (!ctx.hasUI) {
ctx.ui.notify(
"Enabling fast mode requires interactive confirmation of higher allowance consumption.",
@@ -160,22 +145,6 @@ export class FastController {
await this.enable(ctx);
}
// Only ordinary idle input prepares saved on choices. Never fetch from request/stream hooks.
async prepare(ctx: FastHost): Promise<void> {
if (this.disposed || this.pendingPreparation || !ctx.isIdle()) return;
const model = ctx.model;
if (!model || this.preferenceFor(modelKey(model)) !== "on" || !isDirectCodexSelection(ctx))
return;
const preparation = this.startPreparation(ctx);
try {
await this.refreshEligibility(ctx, preparation, false);
} catch (error) {
this.reportPreparationFailure(error, ctx, preparation);
} finally {
if (this.pendingPreparation === preparation) this.pendingPreparation = undefined;
}
}
transform(payload: unknown, ctx: FastHost): Record<string, unknown> | undefined {
if (this.disposed) return;
if (!matchesDirectCodexSelection(payload, ctx) || !ctx.model) {
@@ -224,7 +193,6 @@ export class FastController {
invalidate(): void {
this.pendingPreparation?.abortController.abort();
this.pendingPreparation = undefined;
this.cachedEligibility = undefined;
this.modelTierActivity = undefined;
}
@@ -236,6 +204,10 @@ export class FastController {
}
private async enable(ctx: FastHost): Promise<void> {
if (this.now() < this.retryAt) {
ctx.ui.notify("The catalog service is rate limited. Try fast on again later.", "warning");
return;
}
const preparation = this.startPreparation(ctx);
try {
const confirmed = await ctx.ui.confirm(
@@ -244,8 +216,8 @@ export class FastController {
{ signal: preparation.abortController.signal },
);
if (!this.canApplyPreparation(ctx, preparation) || !confirmed) return;
const eligibility = await this.refreshEligibility(ctx, preparation, true);
if (!this.canApplyPreparation(ctx, preparation) || eligibility !== "supported") return;
const supported = await this.checkEligibility(ctx, preparation);
if (!this.canApplyPreparation(ctx, preparation) || !supported) return;
this.rememberChoice(ctx, "on");
this.pendingPreparation = undefined;
this.report(ctx);
@@ -256,57 +228,21 @@ export class FastController {
}
}
private async refreshEligibility(
// Discovery belongs only to the explicit enable command, never to prompt submission or replay.
private async checkEligibility(
ctx: FastHost,
preparation: EligibilityPreparation,
force: boolean,
): Promise<FastEligibility | undefined> {
const signal = preparation.abortController.signal;
const authScope = await this.readAuthScope(ctx.modelRegistry, signal);
if (!this.canApplyPreparation(ctx, preparation)) return;
const cached = this.cachedEligibility;
if (
!force &&
cached?.modelKey === preparation.modelKey &&
cached.authScope === authScope &&
cached.expiresAt > this.now()
)
return cached.eligibility;
this.cachedEligibility = undefined;
this.modelTierActivity = undefined;
if (this.now() < this.retryAt) {
ctx.ui.notify(
"Fast mode suspended: the catalog service is rate limited. Try again later.",
"warning",
);
return;
}
const catalog = await this.fetchCatalog(ctx.modelRegistry, signal);
if (!this.canApplyPreparation(ctx, preparation)) return;
if (catalog.authScope !== authScope) {
ctx.ui.notify(
"Fast mode suspended: authentication changed during discovery. Try fast on again.",
"warning",
);
return;
}
const model = ctx.model;
if (!model) return;
const eligibility = codexFastEligibility(catalog.data, model.id);
this.cachedEligibility = {
modelKey: preparation.modelKey,
authScope,
eligibility,
expiresAt: this.now() + CAPABILITY_TTL_MS,
};
if (eligibility !== "supported") {
ctx.ui.notify(
`Fast mode suspended: model eligibility is ${eligibility}. Preference unchanged.`,
"warning",
);
}
return eligibility;
): Promise<boolean> {
const catalog = await this.fetchCatalog(ctx.modelRegistry, preparation.abortController.signal);
if (!this.canApplyPreparation(ctx, preparation) || !ctx.model) return false;
const eligibility = codexFastEligibility(catalog.data, ctx.model.id);
if (eligibility === "supported") return true;
const reason =
eligibility === "unsupported"
? "the model does not advertise priority processing"
: "the catalog did not establish this model's priority support";
ctx.ui.notify(`Could not enable fast mode: ${reason}. Preference unchanged.`, "warning");
return false;
}
private startPreparation(ctx: FastHost): EligibilityPreparation {
@@ -351,14 +287,12 @@ export class FastController {
preparation: EligibilityPreparation,
): void {
if (!this.canApplyPreparation(ctx, preparation)) return;
this.cachedEligibility = undefined;
this.modelTierActivity = undefined;
if (error instanceof CodexAccountError && error.retryAfterMs !== undefined) {
this.retryAt = this.now() + error.retryAfterMs;
}
let message = "Could not prepare Codex fast mode.";
let message = "Could not enable Codex fast mode.";
if (error instanceof CodexAccountError) message = error.message;
ctx.ui.notify(`Fast mode suspended: ${message} Preference unchanged.`, "warning");
ctx.ui.notify(`${message} Preference unchanged.`, "warning");
}
private preferenceFor(selectedModelKey: string): FastPreference {
@@ -377,21 +311,13 @@ export class FastController {
this.modelTierActivity = undefined;
}
private currentEligibility(selectedModelKey: string): FastEligibility {
const cached = this.cachedEligibility;
if (!cached || cached.modelKey !== selectedModelKey || cached.expiresAt <= this.now())
return "unknown";
return cached.eligibility;
}
private requestPolicy(ctx: FastHost): FastRequestPolicy {
if (!ctx.model || !isDirectCodexSelection(ctx)) return "outside scope";
const selectedModelKey = modelKey(ctx.model);
const preference = this.preferenceFor(selectedModelKey);
if (preference === "inherit") return "untouched";
if (preference === "off") return "default";
if (this.pendingPreparation || this.currentEligibility(selectedModelKey) !== "supported")
return "suspended";
// A saved, confirmed choice is durable intent, not an expiring capability lease.
return "priority";
}
-1
View File
@@ -63,7 +63,6 @@ export default async function codex(pi: ExtensionAPI): Promise<void> {
pi.on("input", async (event, ctx) => {
if (event.streamingBehavior === undefined && ctx.isIdle()) {
await controller.reconcile(ctx);
await fast.prepare(ctx);
}
});
pi.on("before_provider_request", (event, ctx) => fast.transform(event.payload, ctx));