@mtayfur/opencode-codex-fast 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # OpenCode Codex Fast
2
+
3
+ `@mtayfur/opencode-codex-fast` adds a global Fast Mode toggle for ChatGPT Codex. When enabled, matching HTTP
4
+ requests include:
5
+
6
+ ```json
7
+ {
8
+ "service_tier": "priority"
9
+ }
10
+ ```
11
+
12
+ Other providers and endpoints are unaffected.
13
+
14
+ ## Installation
15
+
16
+ ### OpenCode installer
17
+
18
+ Install both plugin targets with OpenCode:
19
+
20
+ ```sh
21
+ opencode plugin --global @mtayfur/opencode-codex-fast
22
+ ```
23
+
24
+ The package uses separate server and TUI entry points. The installer registers both targets.
25
+
26
+ ### Manual configuration
27
+
28
+ Add the package to `~/.config/opencode/opencode.jsonc` (`opencode.json` is also supported):
29
+
30
+ ```jsonc
31
+ {
32
+ "plugin": ["@mtayfur/opencode-codex-fast"]
33
+ }
34
+ ```
35
+
36
+ Add the same package to `~/.config/opencode/tui.json`:
37
+
38
+ ```json
39
+ {
40
+ "plugin": ["@mtayfur/opencode-codex-fast"]
41
+ }
42
+ ```
43
+
44
+ Restart OpenCode after installation or configuration changes.
45
+
46
+ ### Local checkout
47
+
48
+ For a local checkout, run `bun run --cwd packages/codex-fast setup`. Use `setup:uninstall` to remove both local
49
+ registrations.
50
+
51
+ ## Usage
52
+
53
+ Run `/fast` or select `codex.fast.toggle` from the command palette. The command updates the global state and shows a
54
+ toast; it does not create a session message or call a model.
55
+
56
+ - `⚡` — Fast Mode is enabled.
57
+ - `🐢` — Fast Mode is disabled.
58
+
59
+ The icon appears only in sessions using the `openai` provider. A model change is reflected after the first prompt sent
60
+ with that model.
61
+
62
+ ## Behavior
63
+
64
+ Fast Mode applies to primary agents and sub-agents that use the ChatGPT Codex HTTP endpoint:
65
+
66
+ - Protocol: `https:`
67
+ - Hostname: `chatgpt.com`
68
+ - Pathname: `/backend-api/codex/responses`
69
+
70
+ The state is read before every matching request. Experimental WebSocket transports may bypass `globalThis.fetch` and
71
+ are not supported.
72
+
73
+ ## State
74
+
75
+ The state file contains only the `enabled` boolean. Its location is:
76
+
77
+ - `$XDG_CONFIG_HOME/opencode/codex-fast.json`
78
+ - `%APPDATA%\opencode\codex-fast.json` on Windows
79
+ - `~/.config/opencode/codex-fast.json` otherwise
80
+
81
+ Missing or invalid state means disabled. Writes are atomic.
82
+
83
+ ## Development
84
+
85
+ ```sh
86
+ bun install --frozen-lockfile
87
+ bun run --filter @mtayfur/opencode-codex-fast typecheck
88
+ bun run --filter @mtayfur/opencode-codex-fast build
89
+ npm pack ./packages/codex-fast --dry-run
90
+ ```
@@ -0,0 +1,6 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { Accessor } from "solid-js";
3
+ export declare function FastStatus(props: {
4
+ enabled: Accessor<boolean>;
5
+ providerID: Accessor<string | undefined>;
6
+ }): any;
@@ -0,0 +1,8 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ type ReadEnabled = () => Promise<boolean>;
3
+ export declare function installFetchInterceptor(readEnabled?: ReadEnabled): () => void;
4
+ declare const _default: {
5
+ id: string;
6
+ server: Plugin;
7
+ };
8
+ export default _default;
package/dist/server.js ADDED
@@ -0,0 +1,153 @@
1
+ // @bun
2
+ // src/state.ts
3
+ import { randomUUID } from "crypto";
4
+ import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
5
+ import { homedir } from "os";
6
+ import { basename, dirname, join } from "path";
7
+ var STATE_FILE_NAME = "codex-fast.json";
8
+ function resolveStatePath(options = {}) {
9
+ const env = options.env ?? process.env;
10
+ const home = options.home ?? homedir();
11
+ const platform = options.platform ?? process.platform;
12
+ const xdgConfigHome = nonEmpty(env.XDG_CONFIG_HOME);
13
+ const appData = nonEmpty(env.APPDATA);
14
+ const configRoot = xdgConfigHome ?? (platform === "win32" ? appData : undefined) ?? join(home, ".config");
15
+ return join(configRoot, "opencode", STATE_FILE_NAME);
16
+ }
17
+ async function readFastMode(filePath = resolveStatePath()) {
18
+ try {
19
+ const parsed = JSON.parse(await readFile(filePath, "utf8"));
20
+ return isRecord(parsed) && parsed.enabled === true;
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+ async function writeFastMode(enabled, filePath = resolveStatePath()) {
26
+ const directory = dirname(filePath);
27
+ const temporaryPath = join(directory, `.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
28
+ await mkdir(directory, { recursive: true });
29
+ try {
30
+ await writeFile(temporaryPath, `${JSON.stringify({ enabled }, null, 2)}
31
+ `, {
32
+ encoding: "utf8",
33
+ flag: "wx",
34
+ mode: 384
35
+ });
36
+ await rename(temporaryPath, filePath);
37
+ } catch (error) {
38
+ await rm(temporaryPath, { force: true }).catch(() => {
39
+ return;
40
+ });
41
+ throw error;
42
+ }
43
+ }
44
+ function nonEmpty(value) {
45
+ const trimmed = value?.trim();
46
+ return trimmed ? trimmed : undefined;
47
+ }
48
+ function isRecord(value) {
49
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50
+ }
51
+
52
+ // src/server.ts
53
+ var PLUGIN_ID = "mtayfur.codex-fast";
54
+ var FETCH_RUNTIME_SYMBOL = Symbol.for("@mtayfur/opencode-codex-fast.fetch-runtime.v1");
55
+ function installFetchInterceptor(readEnabled = readFastMode) {
56
+ const host = globalThis;
57
+ const current = host[FETCH_RUNTIME_SYMBOL];
58
+ const existing = isFetchRuntime(current) ? current : undefined;
59
+ const runtime = existing ?? createFetchRuntime(globalThis.fetch, readEnabled);
60
+ if (existing) {
61
+ const wasInactive = runtime.references === 0;
62
+ runtime.references += 1;
63
+ if (wasInactive && globalThis.fetch === runtime.original)
64
+ globalThis.fetch = runtime.wrapper;
65
+ } else {
66
+ host[FETCH_RUNTIME_SYMBOL] = runtime;
67
+ globalThis.fetch = runtime.wrapper;
68
+ }
69
+ let disposed = false;
70
+ return () => {
71
+ if (disposed)
72
+ return;
73
+ disposed = true;
74
+ runtime.references = Math.max(0, runtime.references - 1);
75
+ if (runtime.references !== 0 || globalThis.fetch !== runtime.wrapper)
76
+ return;
77
+ globalThis.fetch = runtime.original;
78
+ if (host[FETCH_RUNTIME_SYMBOL] === runtime)
79
+ delete host[FETCH_RUNTIME_SYMBOL];
80
+ };
81
+ }
82
+ function createFetchRuntime(original, readEnabled) {
83
+ let runtime;
84
+ const wrapper = Object.assign(async (input, init) => {
85
+ if (runtime.references === 0 || !isCodexEndpoint(input)) {
86
+ return original.call(globalThis, input, init);
87
+ }
88
+ const enabled = await safelyReadEnabled(readEnabled);
89
+ if (!enabled || typeof init?.body !== "string") {
90
+ return original.call(globalThis, input, init);
91
+ }
92
+ const body = parseObject(init.body);
93
+ if (!body || body.service_tier === "priority") {
94
+ return original.call(globalThis, input, init);
95
+ }
96
+ return original.call(globalThis, input, {
97
+ ...init,
98
+ body: JSON.stringify({ ...body, service_tier: "priority" })
99
+ });
100
+ }, { preconnect: original.preconnect });
101
+ runtime = { original, wrapper, references: 1 };
102
+ return runtime;
103
+ }
104
+ function isCodexEndpoint(input) {
105
+ try {
106
+ const url = new URL(requestUrl(input));
107
+ return url.protocol === "https:" && url.hostname === "chatgpt.com" && url.pathname === "/backend-api/codex/responses";
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+ function requestUrl(input) {
113
+ if (typeof input === "string")
114
+ return input;
115
+ if (input instanceof URL)
116
+ return input.href;
117
+ return input.url;
118
+ }
119
+ async function safelyReadEnabled(readEnabled) {
120
+ try {
121
+ return await readEnabled();
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
126
+ function parseObject(body) {
127
+ try {
128
+ const parsed = JSON.parse(body);
129
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : undefined;
130
+ } catch {
131
+ return;
132
+ }
133
+ }
134
+ function isFetchRuntime(value) {
135
+ if (typeof value !== "object" || value === null)
136
+ return false;
137
+ const candidate = value;
138
+ return typeof candidate.original === "function" && typeof candidate.wrapper === "function" && typeof candidate.references === "number";
139
+ }
140
+ var server = async () => {
141
+ const dispose = installFetchInterceptor();
142
+ return {
143
+ dispose: async () => dispose()
144
+ };
145
+ };
146
+ var server_default = {
147
+ id: PLUGIN_ID,
148
+ server
149
+ };
150
+ export {
151
+ installFetchInterceptor,
152
+ server_default as default
153
+ };
@@ -0,0 +1,8 @@
1
+ export type StatePathOptions = {
2
+ env?: Readonly<Record<string, string | undefined>>;
3
+ home?: string;
4
+ platform?: NodeJS.Platform;
5
+ };
6
+ export declare function resolveStatePath(options?: StatePathOptions): string;
7
+ export declare function readFastMode(filePath?: string): Promise<boolean>;
8
+ export declare function writeFastMode(enabled: boolean, filePath?: string): Promise<void>;
package/dist/tui.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { TuiPlugin } from "@opencode-ai/plugin/tui";
2
+ declare const _default: {
3
+ id: string;
4
+ tui: TuiPlugin;
5
+ };
6
+ export default _default;
package/dist/tui.js ADDED
@@ -0,0 +1,170 @@
1
+ // @bun
2
+ // src/tui.ts
3
+ import { watch } from "fs";
4
+ import { mkdir as mkdir2 } from "fs/promises";
5
+ import { basename as basename2, dirname as dirname2 } from "path";
6
+ import { createComponent, createSignal } from "solid-js";
7
+
8
+ // src/fast-status.tsx
9
+ import { effect as _$effect } from "@opentui/solid";
10
+ import { insertNode as _$insertNode } from "@opentui/solid";
11
+ import { insert as _$insert } from "@opentui/solid";
12
+ import { memo as _$memo } from "@opentui/solid";
13
+ import { setProp as _$setProp } from "@opentui/solid";
14
+ import { createElement as _$createElement } from "@opentui/solid";
15
+ function FastStatus(props) {
16
+ return (() => {
17
+ var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("span");
18
+ _$insertNode(_el$, _el$2);
19
+ _$setProp(_el$, "paddingRight", 1);
20
+ _$insertNode(_el$2, _el$3);
21
+ _$insert(_el$3, () => props.enabled() ? "\u26A1" : "\uD83D\uDC22");
22
+ _$effect((_$p) => _$setProp(_el$, "visible", props.providerID() === "openai", _$p));
23
+ return _el$;
24
+ })();
25
+ }
26
+
27
+ // src/state.ts
28
+ import { randomUUID } from "crypto";
29
+ import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
30
+ import { homedir } from "os";
31
+ import { basename, dirname, join } from "path";
32
+ var STATE_FILE_NAME = "codex-fast.json";
33
+ function resolveStatePath(options = {}) {
34
+ const env = options.env ?? process.env;
35
+ const home = options.home ?? homedir();
36
+ const platform = options.platform ?? process.platform;
37
+ const xdgConfigHome = nonEmpty(env.XDG_CONFIG_HOME);
38
+ const appData = nonEmpty(env.APPDATA);
39
+ const configRoot = xdgConfigHome ?? (platform === "win32" ? appData : undefined) ?? join(home, ".config");
40
+ return join(configRoot, "opencode", STATE_FILE_NAME);
41
+ }
42
+ async function readFastMode(filePath = resolveStatePath()) {
43
+ try {
44
+ const parsed = JSON.parse(await readFile(filePath, "utf8"));
45
+ return isRecord(parsed) && parsed.enabled === true;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+ async function writeFastMode(enabled, filePath = resolveStatePath()) {
51
+ const directory = dirname(filePath);
52
+ const temporaryPath = join(directory, `.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
53
+ await mkdir(directory, { recursive: true });
54
+ try {
55
+ await writeFile(temporaryPath, `${JSON.stringify({ enabled }, null, 2)}
56
+ `, {
57
+ encoding: "utf8",
58
+ flag: "wx",
59
+ mode: 384
60
+ });
61
+ await rename(temporaryPath, filePath);
62
+ } catch (error) {
63
+ await rm(temporaryPath, { force: true }).catch(() => {
64
+ return;
65
+ });
66
+ throw error;
67
+ }
68
+ }
69
+ function nonEmpty(value) {
70
+ const trimmed = value?.trim();
71
+ return trimmed ? trimmed : undefined;
72
+ }
73
+ function isRecord(value) {
74
+ return typeof value === "object" && value !== null && !Array.isArray(value);
75
+ }
76
+
77
+ // src/tui.ts
78
+ var PLUGIN_ID = "mtayfur.codex-fast";
79
+ var TOAST_TITLE = "Codex Fast";
80
+ function registerFastModeCommands(api, setEnabled) {
81
+ let toggles = Promise.resolve();
82
+ const disposeCommands = api.keymap.registerLayer({
83
+ commands: [
84
+ {
85
+ namespace: "palette",
86
+ name: "codex.fast.toggle",
87
+ title: "Toggle Codex Fast Mode",
88
+ desc: "Toggle priority service tier for ChatGPT Codex requests",
89
+ category: TOAST_TITLE,
90
+ slashName: "fast",
91
+ run: () => {
92
+ toggles = toggles.then(() => toggleFastMode(api, setEnabled));
93
+ return toggles;
94
+ }
95
+ }
96
+ ],
97
+ bindings: []
98
+ });
99
+ api.lifecycle.onDispose(disposeCommands);
100
+ }
101
+ async function toggleFastMode(api, setEnabled) {
102
+ await setFastMode(api, setEnabled, !await readFastMode());
103
+ }
104
+ async function setFastMode(api, setEnabled, enabled) {
105
+ try {
106
+ await writeFastMode(enabled);
107
+ setEnabled(enabled);
108
+ showStateToast(api, enabled);
109
+ } catch {
110
+ api.ui.toast({
111
+ title: TOAST_TITLE,
112
+ message: "Fast mode state could not be updated.",
113
+ variant: "error"
114
+ });
115
+ }
116
+ }
117
+ function showStateToast(api, enabled) {
118
+ api.ui.toast({
119
+ title: TOAST_TITLE,
120
+ message: `Fast mode is now ${enabled ? "ON" : "OFF"}.`,
121
+ variant: "success"
122
+ });
123
+ }
124
+ async function watchFastMode(setEnabled) {
125
+ const stateFile = resolveStatePath();
126
+ try {
127
+ await mkdir2(dirname2(stateFile), { recursive: true });
128
+ const watcher = watch(dirname2(stateFile), (_, filename) => {
129
+ if (filename && basename2(String(filename)) !== basename2(stateFile))
130
+ return;
131
+ readFastMode(stateFile).then(setEnabled);
132
+ });
133
+ watcher.on("error", () => watcher.close());
134
+ return () => watcher.close();
135
+ } catch {
136
+ return () => {
137
+ return;
138
+ };
139
+ }
140
+ }
141
+ function sessionProvider(api, sessionID) {
142
+ const session = api.state.session.get(sessionID);
143
+ if (session?.model?.providerID)
144
+ return session.model.providerID;
145
+ const message = api.state.session.messages(sessionID).findLast((item) => item.role === "user");
146
+ return message?.model.providerID;
147
+ }
148
+ var tui = async (api) => {
149
+ const [enabled, setEnabled] = createSignal(await readFastMode());
150
+ registerFastModeCommands(api, setEnabled);
151
+ api.lifecycle.onDispose(await watchFastMode(setEnabled));
152
+ api.slots.register({
153
+ order: 55,
154
+ slots: {
155
+ session_prompt_right(_, props) {
156
+ return createComponent(FastStatus, {
157
+ enabled,
158
+ providerID: () => sessionProvider(api, props.session_id)
159
+ });
160
+ }
161
+ }
162
+ });
163
+ };
164
+ var tui_default = {
165
+ id: PLUGIN_ID,
166
+ tui
167
+ };
168
+ export {
169
+ tui_default as default
170
+ };
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "@mtayfur/opencode-codex-fast",
4
+ "version": "1.0.1",
5
+ "description": "Toggle ChatGPT Codex priority service tier from the OpenCode TUI.",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/mtayfur/opencode-plugins.git",
10
+ "directory": "packages/codex-fast"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/mtayfur/opencode-plugins/issues"
14
+ },
15
+ "homepage": "https://github.com/mtayfur/opencode-plugins/tree/main/packages/codex-fast#readme",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "type": "module",
20
+ "sideEffects": false,
21
+ "exports": {
22
+ "./server": {
23
+ "types": "./dist/server.d.ts",
24
+ "import": "./dist/server.js"
25
+ },
26
+ "./tui": {
27
+ "types": "./dist/tui.d.ts",
28
+ "import": "./dist/tui.js"
29
+ }
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "bun run scripts/build.ts",
36
+ "setup": "bash ./install.sh",
37
+ "setup:uninstall": "bash ./install.sh --uninstall",
38
+ "typecheck": "bun x tsc -p tsconfig.json",
39
+ "prepack": "bun run build"
40
+ },
41
+ "dependencies": {
42
+ "solid-js": "^1.9.0"
43
+ },
44
+ "peerDependencies": {
45
+ "@opencode-ai/plugin": ">=1.18.21 <2",
46
+ "@opentui/core": ">=0.5.1",
47
+ "@opentui/solid": ">=0.5.1"
48
+ },
49
+ "devDependencies": {
50
+ "@opencode-ai/plugin": "1.18.21",
51
+ "@opentui/core": "0.5.1",
52
+ "@opentui/keymap": "0.5.1",
53
+ "@opentui/solid": "0.5.1",
54
+ "@types/bun": "1.3.14",
55
+ "jsonc-parser": "^3.3.1",
56
+ "typescript": "^5.9.3"
57
+ },
58
+ "engines": {
59
+ "bun": ">=1.3.14",
60
+ "opencode": "^1.18.21"
61
+ },
62
+ "keywords": [
63
+ "opencode",
64
+ "plugin",
65
+ "codex",
66
+ "fast-mode",
67
+ "priority"
68
+ ],
69
+ "packageManager": "bun@1.3.14"
70
+ }