mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: vendor and persist Codex fast mode
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# Development guide: Codex Fast Mode for Pi
|
||||
|
||||
Contributor implementation and validation notes for the locally maintained fork.
|
||||
|
||||
[Back to README](README.md) · [Technical reference](TECHNICAL.md) · [Upstream provenance](UPSTREAM.md)
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
npm test
|
||||
npm run check
|
||||
npm run smoke
|
||||
npm pack --dry-run --json
|
||||
```
|
||||
|
||||
Tests cover command grammar and completions, branch restoration, cross-session defaults, owner-only atomic config writes, invalid-config fallback, persistence failures, busy mutation guards, status publication, exact provider/API isolation, malformed payload passthrough, non-mutation, and tier overwrite. They make no authenticated or paid model requests.
|
||||
|
||||
## State model
|
||||
|
||||
The effective state is resolved in this order:
|
||||
|
||||
1. Read the global default from `<agentDir>/extensions/pi-extension-codex-fast-mode/config.json`; missing or invalid data defaults to off.
|
||||
2. Reconstruct the latest valid `codex-fast-mode` custom entry on the active session branch.
|
||||
3. Use the branch entry when present; otherwise use the global default.
|
||||
|
||||
A successful mode command appends a session entry when the branch value changes and atomically writes the global default even when the branch already has the requested value. This allows an explicit `/fast-mode on` or `/fast-mode off` to repair or recreate global configuration.
|
||||
|
||||
If the global write fails, the branch state still changes and the user receives a warning. Concurrent Pi processes use atomic replacement; the last successful command determines the default for future sessions.
|
||||
|
||||
## Request isolation
|
||||
|
||||
When enabled, the extension shallow-copies only a plain serialized payload whose provider is exactly `openai-codex` and API is exactly `openai-codex-responses`, then writes `service_tier: "priority"`. It preserves all other top-level fields and does not mutate the original payload.
|
||||
|
||||
## Maintenance
|
||||
|
||||
This directory is maintained directly in `my-pi`, is not a submodule, and must not acquire a nested `.git`, `node_modules`, or generated build output. Compare local changes with the snapshot recorded in [UPSTREAM.md](UPSTREAM.md) before porting later upstream work.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Firstpick
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Codex Fast Mode for Pi
|
||||
|
||||
A locally maintained fork of `@firstpick/pi-extension-codex-fast-mode@0.1.1` that adds a cross-session Fast mode default.
|
||||
|
||||
## Behavior
|
||||
|
||||
- `/fast-mode on` and `/fast-mode off` update the current session and the global default for future sessions.
|
||||
- A new session inherits the global default.
|
||||
- A resumed session branch keeps its own latest recorded setting, overriding the global default.
|
||||
- Only `openai-codex` requests using `openai-codex-responses` receive `service_tier: "priority"`.
|
||||
- Other providers, APIs, payload fields, and authentication remain unchanged.
|
||||
|
||||
The global preference is stored at:
|
||||
|
||||
```text
|
||||
<Pi agent dir>/extensions/pi-extension-codex-fast-mode/config.json
|
||||
```
|
||||
|
||||
Normally this is `~/.pi/agent/extensions/pi-extension-codex-fast-mode/config.json`. The directory and file are restricted to owner-only permissions (`0700` and `0600`). Missing, unreadable, or invalid configuration fails safely to Fast mode off.
|
||||
|
||||
## Commands
|
||||
|
||||
- `/fast-mode` toggles Fast mode and saves the new global default.
|
||||
- `/fast-mode on` enables it and makes new sessions default to on.
|
||||
- `/fast-mode off` disables it and makes new sessions default to off.
|
||||
- `/fast-mode status` reports the active branch setting and the default for new sessions.
|
||||
|
||||
Mode changes are rejected while Pi is busy; status remains available.
|
||||
|
||||
## Installation in my-pi
|
||||
|
||||
The root bundle loads this directory directly and declares it as a local `file:` dependency. Do not also install the upstream npm package, or Pi may load the command twice.
|
||||
|
||||
## Source and technical details
|
||||
|
||||
See [UPSTREAM.md](UPSTREAM.md) for provenance, [TECHNICAL.md](TECHNICAL.md) for runtime behavior, and [DEVELOPMENT.md](DEVELOPMENT.md) for validation. The upstream MIT license is retained in [LICENSE](LICENSE).
|
||||
@@ -0,0 +1,44 @@
|
||||
# Technical reference: Codex Fast Mode for Pi
|
||||
|
||||
Advanced behavior, configuration, compatibility, and troubleshooting for the locally maintained fork.
|
||||
|
||||
[Back to README](README.md) · [Development guide](DEVELOPMENT.md) · [Upstream provenance](UPSTREAM.md)
|
||||
|
||||
## Commands
|
||||
|
||||
- `/fast-mode` — toggle the active branch and global default.
|
||||
- `/fast-mode on` — enable the active branch and make future sessions default to on.
|
||||
- `/fast-mode off` — disable the active branch and make future sessions default to off.
|
||||
- `/fast-mode status` — show both the active branch setting and global default.
|
||||
|
||||
Mutation is refused while Pi is busy. Status remains available.
|
||||
|
||||
## Persistence
|
||||
|
||||
The extension stores the global default in:
|
||||
|
||||
```text
|
||||
<Pi agent dir>/extensions/pi-extension-codex-fast-mode/config.json
|
||||
```
|
||||
|
||||
The file contains a single boolean setting:
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
Writes use a temporary file followed by atomic rename. The extension enforces `0700` on the containing directory and `0600` on the file. Missing configuration defaults to off. Malformed or unreadable configuration also fails to off and emits a warning without overwriting the bad file automatically.
|
||||
|
||||
Pi custom session entries remain branch-scoped. On session start or tree navigation, the latest valid branch entry overrides the global default. Consequently, resumed historical branches retain their own decision while genuinely new sessions inherit the latest global setting.
|
||||
|
||||
## Eligibility and credit use
|
||||
|
||||
Fast mode applies only when the selected model has provider `openai-codex` and API `openai-codex-responses`. For eligible plain-object payloads, the extension returns a shallow copy with `service_tier: "priority"`; unsupported providers, APIs, disabled state, and malformed payloads pass through unchanged.
|
||||
|
||||
Fast mode expresses request intent and cannot guarantee that the upstream service accepts or provides priority processing. OpenAI controls account and model eligibility, credit multipliers, and current service terms.
|
||||
|
||||
## Privacy and limitations
|
||||
|
||||
The extension does not inspect credentials, change authentication, choose a model, or make a separate network request. The only additional local data is the boolean global preference. If multiple Pi processes change the setting concurrently, the last successful atomic write becomes the default for future sessions.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Upstream provenance
|
||||
|
||||
- Upstream repository: <https://github.com/Firstp1ck/pi-coding-agent-forge/tree/main/pi-extension-codex-fast-mode>
|
||||
- Imported package: `@firstpick/pi-extension-codex-fast-mode@0.1.1`
|
||||
- Initial source snapshot: `f1d0efd24a7f4ae99d19e10c5f4c3770a3bdd845` (2026-08-18)
|
||||
- Published npm source commit: `239829d747deae4d9c0f039421ab29440f40f1a2`; the only later upstream change in this directory at the imported snapshot was the package version bump from `0.1.0` to `0.1.1`.
|
||||
- License: MIT; the upstream `LICENSE` file is retained unchanged.
|
||||
|
||||
## Local maintenance
|
||||
|
||||
This directory was imported from the upstream Git repository and is maintained directly in `my-pi`. It is not a submodule and does not retain upstream `.git` state, dependencies, or build output.
|
||||
|
||||
Local changes add an owner-only, atomically written global Fast mode preference. New sessions inherit that preference, while existing Pi custom session entries continue to preserve branch-specific history. The root bundle loads this source directly and uses a local `file:` dependency instead of the npm package.
|
||||
@@ -0,0 +1,261 @@
|
||||
import { chmodSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { dirname, join } from "node:path";
|
||||
import {
|
||||
getAgentDir,
|
||||
type ExtensionAPI,
|
||||
type ExtensionCommandContext,
|
||||
type ExtensionContext,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
|
||||
export const FAST_MODE_STATUS_KEY = "codex-fast-mode";
|
||||
export const FAST_MODE_STATE_ENTRY_TYPE = "codex-fast-mode";
|
||||
export const FAST_MODE_SERVICE_TIER = "priority";
|
||||
export const FAST_MODE_CONFIG_DIRECTORY = "pi-extension-codex-fast-mode";
|
||||
|
||||
const OWNER_ONLY_DIRECTORY_MODE = 0o700;
|
||||
const OWNER_ONLY_FILE_MODE = 0o600;
|
||||
|
||||
export type FastModeState = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type FastModeModel = {
|
||||
provider?: unknown;
|
||||
api?: unknown;
|
||||
};
|
||||
|
||||
export type FastModeCommand = "toggle" | "on" | "off" | "status" | "invalid";
|
||||
|
||||
export type FastModeConfigLoadResult = {
|
||||
state: FastModeState;
|
||||
warning?: string;
|
||||
};
|
||||
|
||||
export type FastModeConfigSaveResult =
|
||||
| { success: true }
|
||||
| { success: false; error: string };
|
||||
|
||||
export type FastModeExtensionDependencies = {
|
||||
configPath?: string;
|
||||
loadGlobalState?: (path: string) => FastModeConfigLoadResult;
|
||||
saveGlobalState?: (path: string, state: FastModeState) => FastModeConfigSaveResult;
|
||||
};
|
||||
|
||||
/** Returns true only for object records that can safely receive a shallow request rewrite. */
|
||||
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
if (value === null || typeof value !== "object") return false;
|
||||
const prototype = Object.getPrototypeOf(value);
|
||||
return prototype === Object.prototype || prototype === null;
|
||||
}
|
||||
|
||||
/** Fast mode is limited to Pi's subscription-backed Codex Responses provider. */
|
||||
export function isFastModeEligibleModel(model: FastModeModel | undefined): boolean {
|
||||
return model?.provider === "openai-codex" && model.api === "openai-codex-responses";
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies Fast-mode request intent without mutating the provider's serialized payload.
|
||||
* Undefined is intentional: Pi then retains the original payload unchanged.
|
||||
*/
|
||||
export function transformFastModeRequest(
|
||||
enabled: boolean,
|
||||
model: FastModeModel | undefined,
|
||||
payload: unknown,
|
||||
): Record<string, unknown> | undefined {
|
||||
if (!enabled || !isFastModeEligibleModel(model) || !isPlainObject(payload)) return undefined;
|
||||
return { ...payload, service_tier: FAST_MODE_SERVICE_TIER };
|
||||
}
|
||||
|
||||
/** Resolves the owner-only global preference file beneath Pi's agent directory. */
|
||||
export function getFastModeConfigPath(agentDir: string): string {
|
||||
return join(agentDir, "extensions", FAST_MODE_CONFIG_DIRECTORY, "config.json");
|
||||
}
|
||||
|
||||
/** Reads the cross-session default. Missing or invalid configuration fails safely to off. */
|
||||
export function loadGlobalFastModeState(path: string): FastModeConfigLoadResult {
|
||||
try {
|
||||
const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
|
||||
if (!isPlainObject(parsed) || typeof parsed.enabled !== "boolean") {
|
||||
return {
|
||||
state: { enabled: false },
|
||||
warning: `Ignoring invalid Fast mode config at ${path}; expected { "enabled": boolean }.`,
|
||||
};
|
||||
}
|
||||
return { state: { enabled: parsed.enabled } };
|
||||
} catch (error) {
|
||||
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
||||
return { state: { enabled: false } };
|
||||
}
|
||||
return {
|
||||
state: { enabled: false },
|
||||
warning: `Could not read Fast mode config at ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Atomically writes the cross-session default with owner-only permissions. */
|
||||
export function saveGlobalFastModeState(path: string, state: FastModeState): FastModeConfigSaveResult {
|
||||
const directory = dirname(path);
|
||||
const temporaryPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
||||
try {
|
||||
mkdirSync(directory, { recursive: true, mode: OWNER_ONLY_DIRECTORY_MODE });
|
||||
chmodSync(directory, OWNER_ONLY_DIRECTORY_MODE);
|
||||
writeFileSync(temporaryPath, `${JSON.stringify(state, null, 2)}\n`, {
|
||||
encoding: "utf8",
|
||||
flag: "wx",
|
||||
mode: OWNER_ONLY_FILE_MODE,
|
||||
});
|
||||
chmodSync(temporaryPath, OWNER_ONLY_FILE_MODE);
|
||||
renameSync(temporaryPath, path);
|
||||
chmodSync(path, OWNER_ONLY_FILE_MODE);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
try {
|
||||
unlinkSync(temporaryPath);
|
||||
} catch {
|
||||
// The temporary file may not have been created.
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Reconstructs the latest branch snapshot, falling back to the cross-session default. */
|
||||
export function reconstructFastModeState(
|
||||
entries: readonly unknown[],
|
||||
defaultEnabled = false,
|
||||
): FastModeState {
|
||||
let enabled = defaultEnabled;
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!isPlainObject(entry)) continue;
|
||||
if (entry.type !== "custom" || entry.customType !== FAST_MODE_STATE_ENTRY_TYPE) continue;
|
||||
if (!isPlainObject(entry.data) || typeof entry.data.enabled !== "boolean") continue;
|
||||
enabled = entry.data.enabled;
|
||||
}
|
||||
|
||||
return { enabled };
|
||||
}
|
||||
|
||||
export function parseFastModeCommand(args: string): FastModeCommand {
|
||||
const normalized = args.trim().toLowerCase();
|
||||
if (!normalized) return "toggle";
|
||||
if (normalized === "on" || normalized === "off" || normalized === "status") return normalized;
|
||||
return "invalid";
|
||||
}
|
||||
|
||||
export function fastModeArgumentCompletions(prefix: string) {
|
||||
const normalized = prefix.trim().toLowerCase();
|
||||
return ["on", "off", "status"]
|
||||
.filter((value) => value.startsWith(normalized))
|
||||
.map((value) => ({ value, label: value }));
|
||||
}
|
||||
|
||||
function isBusy(ctx: Pick<ExtensionCommandContext, "isIdle" | "hasPendingMessages">): boolean {
|
||||
return !ctx.isIdle() || ctx.hasPendingMessages();
|
||||
}
|
||||
|
||||
function publishStatus(ctx: Pick<ExtensionContext, "ui">, enabled: boolean): void {
|
||||
ctx.ui.setStatus(FAST_MODE_STATUS_KEY, enabled ? "on" : "off");
|
||||
}
|
||||
|
||||
function formatStatus(enabled: boolean, globalEnabled: boolean): string {
|
||||
const state = enabled ? "on" : "off";
|
||||
const globalState = globalEnabled ? "on" : "off";
|
||||
return `Fast mode: ${state}. New sessions default to ${globalState}. It only requests priority service for openai-codex/openai-codex-responses.`;
|
||||
}
|
||||
|
||||
export default function codexFastModeExtension(
|
||||
pi: ExtensionAPI,
|
||||
dependencies: FastModeExtensionDependencies = {},
|
||||
): void {
|
||||
const configPath = dependencies.configPath ?? getFastModeConfigPath(getAgentDir());
|
||||
const loadGlobalState = dependencies.loadGlobalState ?? loadGlobalFastModeState;
|
||||
const saveGlobalState = dependencies.saveGlobalState ?? saveGlobalFastModeState;
|
||||
let enabled = false;
|
||||
let globalEnabled = false;
|
||||
let lastConfigWarning: string | undefined;
|
||||
|
||||
const restoreState = (ctx: ExtensionContext): void => {
|
||||
const loaded = loadGlobalState(configPath);
|
||||
globalEnabled = loaded.state.enabled;
|
||||
enabled = reconstructFastModeState(ctx.sessionManager.getBranch(), globalEnabled).enabled;
|
||||
publishStatus(ctx, enabled);
|
||||
|
||||
if (loaded.warning && loaded.warning !== lastConfigWarning) {
|
||||
ctx.ui.notify(loaded.warning, "warning");
|
||||
}
|
||||
lastConfigWarning = loaded.warning;
|
||||
};
|
||||
|
||||
const persistSessionState = (): void => {
|
||||
pi.appendEntry<FastModeState>(FAST_MODE_STATE_ENTRY_TYPE, { enabled });
|
||||
};
|
||||
|
||||
const setEnabled = (ctx: ExtensionCommandContext, nextEnabled: boolean): void => {
|
||||
const stateChanged = enabled !== nextEnabled;
|
||||
const saveResult = saveGlobalState(configPath, { enabled: nextEnabled });
|
||||
if (saveResult.success) {
|
||||
globalEnabled = nextEnabled;
|
||||
}
|
||||
|
||||
if (stateChanged) {
|
||||
enabled = nextEnabled;
|
||||
persistSessionState();
|
||||
}
|
||||
|
||||
publishStatus(ctx, enabled);
|
||||
if (stateChanged) {
|
||||
ctx.ui.notify(enabled
|
||||
? "Fast mode enabled. Supported Codex requests will use priority service, and new sessions will inherit this setting."
|
||||
: "Fast mode disabled. Supported Codex requests will keep their existing service tier, and new sessions will inherit this setting.", "info");
|
||||
} else {
|
||||
ctx.ui.notify(`Fast mode is already ${enabled ? "on" : "off"}. The global default was refreshed.`, "info");
|
||||
}
|
||||
|
||||
if (!saveResult.success) {
|
||||
ctx.ui.notify(`The current session was updated, but the Fast mode global default could not be saved: ${saveResult.error}`, "warning");
|
||||
}
|
||||
};
|
||||
|
||||
pi.on("session_start", (_event, ctx) => {
|
||||
restoreState(ctx);
|
||||
});
|
||||
|
||||
pi.on("session_tree", (_event, ctx) => {
|
||||
restoreState(ctx);
|
||||
});
|
||||
|
||||
pi.on("before_provider_request", (event, ctx) => {
|
||||
return transformFastModeRequest(enabled, ctx.model, event.payload);
|
||||
});
|
||||
|
||||
pi.registerCommand("fast-mode", {
|
||||
description: "Toggle persistent Codex subscription Fast mode. Usage: /fast-mode [on|off|status]",
|
||||
getArgumentCompletions: fastModeArgumentCompletions,
|
||||
handler: async (args, ctx) => {
|
||||
const command = parseFastModeCommand(args);
|
||||
|
||||
if (command === "status") {
|
||||
publishStatus(ctx, enabled);
|
||||
ctx.ui.notify(formatStatus(enabled, globalEnabled), "info");
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "invalid") {
|
||||
ctx.ui.notify("Usage: /fast-mode [on|off|status]", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBusy(ctx)) {
|
||||
ctx.ui.notify("Fast mode cannot be changed while the session is busy. Run /fast-mode status to inspect it.", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
setEnabled(ctx, command === "toggle" ? !enabled : command === "on");
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@firstpick/pi-extension-codex-fast-mode",
|
||||
"version": "0.1.1",
|
||||
"description": "Persistent Codex subscription Fast mode for Pi requests.",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/Firstp1ck/pi-coding-agent-forge/tree/main/pi-extension-codex-fast-mode#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Firstp1ck/pi-coding-agent-forge.git",
|
||||
"directory": "pi-extension-codex-fast-mode"
|
||||
},
|
||||
"keywords": [
|
||||
"pi-package",
|
||||
"pi",
|
||||
"pi-coding-agent",
|
||||
"extension",
|
||||
"openai-codex",
|
||||
"fast-mode"
|
||||
],
|
||||
"pi": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
||||
"check": "node --experimental-strip-types --check index.ts",
|
||||
"smoke": "node --experimental-strip-types --input-type=module -e \"import('./index.ts')\""
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*"
|
||||
},
|
||||
"files": [
|
||||
"index.ts",
|
||||
"README.md",
|
||||
"DEVELOPMENT.md",
|
||||
"TECHNICAL.md",
|
||||
"UPSTREAM.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
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" });
|
||||
});
|
||||
Reference in New Issue
Block a user