mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 16:45:22 +00:00
300 lines
13 KiB
TypeScript
300 lines
13 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { chmodSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import test from "node:test";
|
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
import codexFastModeExtension, {
|
|
FAST_MODE_SERVICE_TIER,
|
|
FAST_MODE_STATE_ENTRY_TYPE,
|
|
FAST_MODE_STATUS_KEY,
|
|
fastModeArgumentCompletions,
|
|
getFastModeConfigPath,
|
|
isFastModeEligibleModel,
|
|
isPlainObject,
|
|
loadGlobalFastModeState,
|
|
parseFastModeCommand,
|
|
reconstructFastModeState,
|
|
saveGlobalFastModeState,
|
|
transformFastModeRequest,
|
|
type FastModeState,
|
|
} from "../index.ts";
|
|
|
|
type StatusUpdate = { key: string; value: string | undefined };
|
|
type Notification = { message: string; level: string };
|
|
type Handler = (event: any, ctx: any) => unknown;
|
|
|
|
function createHarness(options: {
|
|
entries?: unknown[];
|
|
busy?: boolean;
|
|
pending?: boolean;
|
|
globalEnabled?: boolean;
|
|
saveError?: string;
|
|
} = {}) {
|
|
const handlers = new Map<string, Handler>();
|
|
const commands = new Map<string, any>();
|
|
const entries = options.entries ?? [];
|
|
const statusUpdates: StatusUpdate[] = [];
|
|
const notifications: Notification[] = [];
|
|
const appendCalls: Array<{ customType: string; data: unknown }> = [];
|
|
const loadCalls: string[] = [];
|
|
const saveCalls: Array<{ path: string; state: FastModeState }> = [];
|
|
let busy = options.busy ?? false;
|
|
let pending = options.pending ?? false;
|
|
let globalEnabled = options.globalEnabled ?? false;
|
|
|
|
const context = {
|
|
model: { provider: "openai-codex", api: "openai-codex-responses" },
|
|
sessionManager: { getBranch: () => entries },
|
|
isIdle: () => !busy,
|
|
hasPendingMessages: () => pending,
|
|
ui: {
|
|
setStatus(key: string, value: string | undefined) {
|
|
statusUpdates.push({ key, value });
|
|
},
|
|
notify(message: string, level: string) {
|
|
notifications.push({ message, level });
|
|
},
|
|
},
|
|
} as unknown as ExtensionCommandContext;
|
|
|
|
codexFastModeExtension({
|
|
on(event: string, handler: Handler) {
|
|
handlers.set(event, handler);
|
|
},
|
|
registerCommand(name: string, command: unknown) {
|
|
commands.set(name, command);
|
|
},
|
|
appendEntry(customType: string, data: unknown) {
|
|
appendCalls.push({ customType, data });
|
|
entries.push({ type: "custom", customType, data });
|
|
},
|
|
} as unknown as ExtensionAPI, {
|
|
configPath: "/test-agent/extensions/pi-extension-codex-fast-mode/config.json",
|
|
loadGlobalState(path) {
|
|
loadCalls.push(path);
|
|
return { state: { enabled: globalEnabled } };
|
|
},
|
|
saveGlobalState(path, state) {
|
|
saveCalls.push({ path, state });
|
|
if (options.saveError) return { success: false, error: options.saveError };
|
|
globalEnabled = state.enabled;
|
|
return { success: true };
|
|
},
|
|
});
|
|
|
|
return {
|
|
appendCalls,
|
|
commands,
|
|
context,
|
|
entries,
|
|
handlers,
|
|
loadCalls,
|
|
notifications,
|
|
saveCalls,
|
|
setBusy(value: boolean) { busy = value; },
|
|
setPending(value: boolean) { pending = value; },
|
|
statusUpdates,
|
|
};
|
|
}
|
|
|
|
test("plain-object guard excludes malformed payloads", () => {
|
|
assert.equal(isPlainObject({}), true);
|
|
assert.equal(isPlainObject(Object.create(null)), true);
|
|
assert.equal(isPlainObject([]), false);
|
|
assert.equal(isPlainObject(new Date()), false);
|
|
assert.equal(isPlainObject(null), false);
|
|
assert.equal(isPlainObject("payload"), false);
|
|
});
|
|
|
|
test("provider eligibility is exact", () => {
|
|
assert.equal(isFastModeEligibleModel({ provider: "openai-codex", api: "openai-codex-responses" }), true);
|
|
assert.equal(isFastModeEligibleModel({ provider: "openai", api: "openai-codex-responses" }), false);
|
|
assert.equal(isFastModeEligibleModel({ provider: "openai-codex", api: "openai-responses" }), false);
|
|
assert.equal(isFastModeEligibleModel(undefined), false);
|
|
});
|
|
|
|
test("request transformer is isolated, non-mutating, and overwrites only service_tier", () => {
|
|
const payload = { model: "gpt-5.6-codex", service_tier: "default", nested: { preserved: true } };
|
|
const transformed = transformFastModeRequest(true, { provider: "openai-codex", api: "openai-codex-responses" }, payload);
|
|
|
|
assert.deepEqual(transformed, {
|
|
model: "gpt-5.6-codex",
|
|
service_tier: FAST_MODE_SERVICE_TIER,
|
|
nested: { preserved: true },
|
|
});
|
|
assert.notStrictEqual(transformed, payload);
|
|
assert.strictEqual(transformed?.nested, payload.nested);
|
|
assert.equal(payload.service_tier, "default");
|
|
assert.equal(transformFastModeRequest(false, { provider: "openai-codex", api: "openai-codex-responses" }, payload), undefined);
|
|
assert.equal(transformFastModeRequest(true, { provider: "openai", api: "openai-codex-responses" }, payload), undefined);
|
|
assert.equal(transformFastModeRequest(true, { provider: "openai-codex", api: "openai-codex-responses" }, []), undefined);
|
|
});
|
|
|
|
test("branch reconstruction prefers the latest valid snapshot over the global default", () => {
|
|
assert.deepEqual(reconstructFastModeState([], true), { enabled: true });
|
|
assert.deepEqual(reconstructFastModeState([
|
|
{ type: "custom", customType: FAST_MODE_STATE_ENTRY_TYPE, data: { enabled: true } },
|
|
{ type: "custom", customType: "other", data: { enabled: false } },
|
|
{ type: "custom", customType: FAST_MODE_STATE_ENTRY_TYPE, data: { enabled: "invalid" } },
|
|
{ type: "custom", customType: FAST_MODE_STATE_ENTRY_TYPE, data: { enabled: false } },
|
|
], true), { enabled: false });
|
|
});
|
|
|
|
test("global config round-trips atomically with owner-only permissions", () => {
|
|
const agentDir = mkdtempSync(join(tmpdir(), "fast-mode-test-"));
|
|
const configPath = getFastModeConfigPath(agentDir);
|
|
try {
|
|
assert.deepEqual(loadGlobalFastModeState(configPath), { state: { enabled: false } });
|
|
assert.deepEqual(saveGlobalFastModeState(configPath, { enabled: true }), { success: true });
|
|
assert.deepEqual(loadGlobalFastModeState(configPath), { state: { enabled: true } });
|
|
assert.deepEqual(JSON.parse(readFileSync(configPath, "utf8")), { enabled: true });
|
|
assert.equal(statSync(configPath).mode & 0o777, 0o600);
|
|
assert.equal(statSync(join(agentDir, "extensions", "pi-extension-codex-fast-mode")).mode & 0o777, 0o700);
|
|
} finally {
|
|
rmSync(agentDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("invalid or unreadable global config fails safely to off", () => {
|
|
const directory = mkdtempSync(join(tmpdir(), "fast-mode-invalid-"));
|
|
const configPath = join(directory, "config.json");
|
|
try {
|
|
writeFileSync(configPath, "{ not-json", "utf8");
|
|
const invalid = loadGlobalFastModeState(configPath);
|
|
assert.deepEqual(invalid.state, { enabled: false });
|
|
assert.match(invalid.warning ?? "", /Could not read/u);
|
|
|
|
writeFileSync(configPath, JSON.stringify({ enabled: "yes" }), "utf8");
|
|
const wrongType = loadGlobalFastModeState(configPath);
|
|
assert.deepEqual(wrongType.state, { enabled: false });
|
|
assert.match(wrongType.warning ?? "", /expected/u);
|
|
|
|
chmodSync(configPath, 0o600);
|
|
} finally {
|
|
rmSync(directory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("command grammar and completions are deterministic", () => {
|
|
assert.equal(parseFastModeCommand(""), "toggle");
|
|
assert.equal(parseFastModeCommand(" ON "), "on");
|
|
assert.equal(parseFastModeCommand("off"), "off");
|
|
assert.equal(parseFastModeCommand("status"), "status");
|
|
assert.equal(parseFastModeCommand("on now"), "invalid");
|
|
assert.deepEqual(fastModeArgumentCompletions("o"), [
|
|
{ value: "on", label: "on" },
|
|
{ value: "off", label: "off" },
|
|
]);
|
|
assert.deepEqual(fastModeArgumentCompletions("status"), [{ value: "status", label: "status" }]);
|
|
});
|
|
|
|
test("a new session inherits the global default while a branch snapshot takes precedence", () => {
|
|
const inherited = createHarness({ globalEnabled: true });
|
|
const inheritedStart = inherited.handlers.get("session_start");
|
|
assert.ok(inheritedStart);
|
|
inheritedStart!({}, inherited.context as unknown as ExtensionContext);
|
|
assert.deepEqual(inherited.statusUpdates.at(-1), { key: FAST_MODE_STATUS_KEY, value: "on" });
|
|
|
|
const overridden = createHarness({
|
|
globalEnabled: true,
|
|
entries: [{ type: "custom", customType: FAST_MODE_STATE_ENTRY_TYPE, data: { enabled: false } }],
|
|
});
|
|
const overriddenStart = overridden.handlers.get("session_start");
|
|
assert.ok(overriddenStart);
|
|
overriddenStart!({}, overridden.context as unknown as ExtensionContext);
|
|
assert.deepEqual(overridden.statusUpdates.at(-1), { key: FAST_MODE_STATUS_KEY, value: "off" });
|
|
});
|
|
|
|
test("extension restores state, publishes status, and persists session and global mutations", async () => {
|
|
const harness = createHarness({
|
|
entries: [{ type: "custom", customType: FAST_MODE_STATE_ENTRY_TYPE, data: { enabled: true } }],
|
|
});
|
|
const sessionStart = harness.handlers.get("session_start");
|
|
const request = harness.handlers.get("before_provider_request");
|
|
const command = harness.commands.get("fast-mode");
|
|
assert.ok(sessionStart && request && command);
|
|
|
|
sessionStart!({}, harness.context as unknown as ExtensionContext);
|
|
assert.deepEqual(harness.statusUpdates.at(-1), { key: FAST_MODE_STATUS_KEY, value: "on" });
|
|
assert.deepEqual(request!({ payload: { service_tier: "standard", keep: true } }, harness.context), {
|
|
service_tier: FAST_MODE_SERVICE_TIER,
|
|
keep: true,
|
|
});
|
|
|
|
await command.handler("off", harness.context);
|
|
assert.deepEqual(harness.appendCalls, [{ customType: FAST_MODE_STATE_ENTRY_TYPE, data: { enabled: false } }]);
|
|
assert.deepEqual(harness.saveCalls.map((call) => call.state), [{ enabled: false }]);
|
|
assert.deepEqual(harness.statusUpdates.at(-1), { key: FAST_MODE_STATUS_KEY, value: "off" });
|
|
assert.equal(request!({ payload: { keep: true } }, harness.context), undefined);
|
|
|
|
await command.handler("status", harness.context);
|
|
assert.match(harness.notifications.at(-1)?.message ?? "", /Fast mode: off.*New sessions default to off/u);
|
|
assert.equal(harness.notifications.at(-1)?.level, "info");
|
|
});
|
|
|
|
test("an explicit no-op mode command still refreshes the global default", async () => {
|
|
const harness = createHarness({ globalEnabled: true });
|
|
const sessionStart = harness.handlers.get("session_start");
|
|
const command = harness.commands.get("fast-mode");
|
|
assert.ok(sessionStart && command);
|
|
sessionStart!({}, harness.context as unknown as ExtensionContext);
|
|
|
|
await command.handler("on", harness.context);
|
|
assert.equal(harness.appendCalls.length, 0);
|
|
assert.deepEqual(harness.saveCalls.map((call) => call.state), [{ enabled: true }]);
|
|
});
|
|
|
|
test("a global save failure leaves the session usable and reports the persistence failure", async () => {
|
|
const harness = createHarness({ saveError: "read-only filesystem" });
|
|
const sessionStart = harness.handlers.get("session_start");
|
|
const command = harness.commands.get("fast-mode");
|
|
assert.ok(sessionStart && command);
|
|
sessionStart!({}, harness.context as unknown as ExtensionContext);
|
|
|
|
await command.handler("on", harness.context);
|
|
assert.deepEqual(harness.appendCalls, [{ customType: FAST_MODE_STATE_ENTRY_TYPE, data: { enabled: true } }]);
|
|
assert.equal(harness.notifications.at(-1)?.level, "warning");
|
|
assert.match(harness.notifications.at(-1)?.message ?? "", /could not be saved.*read-only filesystem/u);
|
|
});
|
|
|
|
test("extension rejects every mutation while busy but leaves status readable", async () => {
|
|
const harness = createHarness({ busy: true });
|
|
const sessionStart = harness.handlers.get("session_start");
|
|
const command = harness.commands.get("fast-mode");
|
|
assert.ok(sessionStart && command);
|
|
sessionStart!({}, harness.context as unknown as ExtensionContext);
|
|
|
|
await command.handler("on", harness.context);
|
|
await command.handler("", harness.context);
|
|
await command.handler("off", harness.context);
|
|
assert.equal(harness.appendCalls.length, 0);
|
|
assert.equal(harness.saveCalls.length, 0);
|
|
assert.equal(harness.notifications.filter((item) => item.level === "warning").length, 3);
|
|
|
|
await command.handler("status", harness.context);
|
|
assert.match(harness.notifications.at(-1)?.message ?? "", /Fast mode: off/u);
|
|
assert.equal(harness.notifications.at(-1)?.level, "info");
|
|
|
|
harness.setBusy(false);
|
|
harness.setPending(true);
|
|
await command.handler("on", harness.context);
|
|
assert.equal(harness.appendCalls.length, 0);
|
|
assert.equal(harness.saveCalls.length, 0);
|
|
});
|
|
|
|
test("tree navigation reconstructs the active branch state", () => {
|
|
const harness = createHarness({
|
|
globalEnabled: true,
|
|
entries: [{ type: "custom", customType: FAST_MODE_STATE_ENTRY_TYPE, data: { enabled: true } }],
|
|
});
|
|
const sessionStart = harness.handlers.get("session_start");
|
|
const sessionTree = harness.handlers.get("session_tree");
|
|
assert.ok(sessionStart && sessionTree);
|
|
|
|
sessionStart!({}, harness.context as unknown as ExtensionContext);
|
|
harness.entries.push({ type: "custom", customType: FAST_MODE_STATE_ENTRY_TYPE, data: { enabled: false } });
|
|
sessionTree!({}, harness.context as unknown as ExtensionContext);
|
|
assert.deepEqual(harness.statusUpdates.at(-1), { key: FAST_MODE_STATUS_KEY, value: "off" });
|
|
});
|