@diegopetrucci/pi-openai-fast 0.1.0
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 +95 -0
- package/index.ts +292 -0
- package/openai-fast.example.json +4 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# openai-fast
|
|
2
|
+
|
|
3
|
+
A pi extension that enables OpenAI Codex Fast mode for ChatGPT-auth GPT-5.4 and GPT-5.5.
|
|
4
|
+
|
|
5
|
+
When active, the extension injects this into eligible OpenAI Codex request payloads:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"service_tier": "priority"
|
|
10
|
+
}
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The user-facing feature is OpenAI Codex **Fast mode**. The wire value is `priority` because current Codex clients map Fast mode to the OpenAI priority service tier.
|
|
14
|
+
|
|
15
|
+
## Eligibility
|
|
16
|
+
|
|
17
|
+
Fast mode is only injected when all of these are true:
|
|
18
|
+
|
|
19
|
+
- The current provider is `openai-codex`.
|
|
20
|
+
- The current API is `openai-codex-responses`.
|
|
21
|
+
- The current model is `gpt-5.4` or `gpt-5.5`.
|
|
22
|
+
- The provider is using ChatGPT OAuth/subscription auth, not API-key auth.
|
|
23
|
+
- The request payload does not already include `service_tier`.
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
/fast status
|
|
29
|
+
/fast on
|
|
30
|
+
/fast off
|
|
31
|
+
/fast auto
|
|
32
|
+
/fast toggle
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`/fast on` and `/fast off` are temporary session/runtime overrides. Use `/fast auto` to reload and follow config defaults again.
|
|
36
|
+
|
|
37
|
+
The extension defaults to off so installing the full collection does not accidentally spend Fast-mode credits.
|
|
38
|
+
|
|
39
|
+
## Config
|
|
40
|
+
|
|
41
|
+
Optional global config:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
~/.pi/agent/extensions/openai-fast.json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Optional project config:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
.pi/openai-fast.json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Project config overrides global config.
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"enabled": false,
|
|
58
|
+
"showStatus": true
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- `enabled`: default Fast-mode state when there is no session override.
|
|
63
|
+
- `showStatus`: show a compact `fast` status when Fast mode is active for the current model.
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
### Standalone npm package
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pi install npm:@diegopetrucci/pi-openai-fast
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Collection package
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pi install npm:@diegopetrucci/pi-extensions
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### GitHub package
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pi install git:github.com/diegopetrucci/pi-extensions
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then reload pi:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
/reload
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Notes
|
|
92
|
+
|
|
93
|
+
- This extension intentionally does not affect API-key OpenAI models.
|
|
94
|
+
- Pi may only account Fast-mode cost correctly when the backend reports `service_tier: "priority"` in the streamed response. The extension does not patch usage totals to avoid double-counting.
|
|
95
|
+
- If pi adds first-class service-tier support later, this extension skips payloads that already contain `service_tier`.
|
package/index.ts
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
getAgentDir,
|
|
5
|
+
type ExtensionAPI,
|
|
6
|
+
type ExtensionContext,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
|
|
9
|
+
const EXTENSION_ID = "openai-fast";
|
|
10
|
+
const PROVIDER_ID = "openai-codex";
|
|
11
|
+
const API_ID = "openai-codex-responses";
|
|
12
|
+
const FAST_SERVICE_TIER = "priority";
|
|
13
|
+
const SUPPORTED_MODELS = new Set(["gpt-5.4", "gpt-5.5"]);
|
|
14
|
+
const COMMANDS = ["status", "on", "off", "auto", "toggle"];
|
|
15
|
+
|
|
16
|
+
const DEFAULT_CONFIG: OpenAIFastConfig = {
|
|
17
|
+
enabled: false,
|
|
18
|
+
showStatus: true,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type FastOverride = "auto" | "on" | "off";
|
|
22
|
+
|
|
23
|
+
type OpenAIFastConfig = {
|
|
24
|
+
/** Default Fast-mode state when there is no session override. */
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
/** Show a compact `fast` status when Fast mode is active for the current model. */
|
|
27
|
+
showStatus: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type SessionState = {
|
|
31
|
+
config: OpenAIFastConfig;
|
|
32
|
+
override: FastOverride;
|
|
33
|
+
lastInjectedAt?: number;
|
|
34
|
+
lastInjectedModel?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type RecursivePartial<T> = {
|
|
38
|
+
[P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type PayloadRecord = Record<string, unknown>;
|
|
42
|
+
|
|
43
|
+
type Eligibility = {
|
|
44
|
+
eligible: boolean;
|
|
45
|
+
modelKey: string;
|
|
46
|
+
reason?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function readConfigFile(path: string): RecursivePartial<OpenAIFastConfig> {
|
|
50
|
+
if (!existsSync(path)) return {};
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
54
|
+
return isPayloadRecord(parsed) ? (parsed as RecursivePartial<OpenAIFastConfig>) : {};
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error(`Warning: Could not parse ${path}: ${error}`);
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeBoolean(value: unknown, fallback: boolean): boolean {
|
|
62
|
+
return typeof value === "boolean" ? value : fallback;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function mergeConfig(
|
|
66
|
+
base: OpenAIFastConfig,
|
|
67
|
+
overrides: RecursivePartial<OpenAIFastConfig>,
|
|
68
|
+
): OpenAIFastConfig {
|
|
69
|
+
return {
|
|
70
|
+
enabled: normalizeBoolean(overrides.enabled, base.enabled),
|
|
71
|
+
showStatus: normalizeBoolean(overrides.showStatus, base.showStatus),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function findProjectConfigPath(cwd: string): string {
|
|
76
|
+
let current = cwd;
|
|
77
|
+
while (true) {
|
|
78
|
+
const candidate = join(current, ".pi", "openai-fast.json");
|
|
79
|
+
if (existsSync(candidate)) return candidate;
|
|
80
|
+
|
|
81
|
+
const parent = dirname(current);
|
|
82
|
+
if (parent === current) return join(cwd, ".pi", "openai-fast.json");
|
|
83
|
+
current = parent;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadConfig(cwd: string): OpenAIFastConfig {
|
|
88
|
+
const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "openai-fast.json"));
|
|
89
|
+
const projectConfig = readConfigFile(findProjectConfigPath(cwd));
|
|
90
|
+
return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isPayloadRecord(payload: unknown): payload is PayloadRecord {
|
|
94
|
+
return typeof payload === "object" && payload !== null && !Array.isArray(payload);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function modelKey(ctx: ExtensionContext): string {
|
|
98
|
+
const model = ctx.model;
|
|
99
|
+
return model ? `${model.provider}/${model.id}` : "no-model";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isFastEnabled(state: SessionState): boolean {
|
|
103
|
+
if (state.override === "on") return true;
|
|
104
|
+
if (state.override === "off") return false;
|
|
105
|
+
return state.config.enabled;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function describeMode(state: SessionState): string {
|
|
109
|
+
if (state.override === "on") return "on (session override)";
|
|
110
|
+
if (state.override === "off") return "off (session override)";
|
|
111
|
+
return state.config.enabled ? "on (config default)" : "off (config default)";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getEligibility(ctx: ExtensionContext): Eligibility {
|
|
115
|
+
const model = ctx.model;
|
|
116
|
+
if (!model) {
|
|
117
|
+
return { eligible: false, modelKey: "no-model", reason: "no model is selected" };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const key = `${model.provider}/${model.id}`;
|
|
121
|
+
if (model.provider !== PROVIDER_ID) {
|
|
122
|
+
return {
|
|
123
|
+
eligible: false,
|
|
124
|
+
modelKey: key,
|
|
125
|
+
reason: `current provider is ${model.provider}, not ${PROVIDER_ID}`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (model.api !== API_ID) {
|
|
130
|
+
return {
|
|
131
|
+
eligible: false,
|
|
132
|
+
modelKey: key,
|
|
133
|
+
reason: `current API is ${model.api}, not ${API_ID}`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!SUPPORTED_MODELS.has(model.id)) {
|
|
138
|
+
return {
|
|
139
|
+
eligible: false,
|
|
140
|
+
modelKey: key,
|
|
141
|
+
reason: "Fast mode is only enabled for gpt-5.4 and gpt-5.5",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!ctx.modelRegistry.isUsingOAuth(model)) {
|
|
146
|
+
return {
|
|
147
|
+
eligible: false,
|
|
148
|
+
modelKey: key,
|
|
149
|
+
reason: "ChatGPT OAuth auth is required; API-key auth is intentionally not used",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { eligible: true, modelKey: key };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function updateStatus(ctx: ExtensionContext, state: SessionState): void {
|
|
157
|
+
if (!ctx.hasUI) return;
|
|
158
|
+
if (!state.config.showStatus) {
|
|
159
|
+
ctx.ui.setStatus(EXTENSION_ID, undefined);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const eligibility = getEligibility(ctx);
|
|
164
|
+
ctx.ui.setStatus(
|
|
165
|
+
EXTENSION_ID,
|
|
166
|
+
isFastEnabled(state) && eligibility.eligible ? "fast" : undefined,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getStatusMessage(ctx: ExtensionContext, state: SessionState): string {
|
|
171
|
+
const enabled = isFastEnabled(state);
|
|
172
|
+
const eligibility = getEligibility(ctx);
|
|
173
|
+
const active = enabled && eligibility.eligible;
|
|
174
|
+
const injected = state.lastInjectedAt
|
|
175
|
+
? ` Last injected for ${state.lastInjectedModel ?? "unknown model"} ${Math.max(0, Math.round((Date.now() - state.lastInjectedAt) / 1000))}s ago.`
|
|
176
|
+
: "";
|
|
177
|
+
|
|
178
|
+
if (active) {
|
|
179
|
+
return `OpenAI Fast mode is ${describeMode(state)} and active for ${eligibility.modelKey}; requests will use service_tier=${FAST_SERVICE_TIER}.${injected}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (enabled) {
|
|
183
|
+
return `OpenAI Fast mode is ${describeMode(state)}, but inactive for ${eligibility.modelKey}: ${eligibility.reason}.${injected}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return `OpenAI Fast mode is ${describeMode(state)}. Current model: ${eligibility.modelKey}.${injected}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function injectFastServiceTier(
|
|
190
|
+
payload: unknown,
|
|
191
|
+
ctx: ExtensionContext,
|
|
192
|
+
state: SessionState,
|
|
193
|
+
): PayloadRecord | undefined {
|
|
194
|
+
if (!isFastEnabled(state)) return undefined;
|
|
195
|
+
if (!getEligibility(ctx).eligible) return undefined;
|
|
196
|
+
if (!isPayloadRecord(payload)) return undefined;
|
|
197
|
+
if (payload.model !== ctx.model?.id) return undefined;
|
|
198
|
+
if ("service_tier" in payload) return undefined;
|
|
199
|
+
|
|
200
|
+
state.lastInjectedAt = Date.now();
|
|
201
|
+
state.lastInjectedModel = modelKey(ctx);
|
|
202
|
+
return {
|
|
203
|
+
...payload,
|
|
204
|
+
service_tier: FAST_SERVICE_TIER,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export default function openAIFastExtension(pi: ExtensionAPI) {
|
|
209
|
+
const states = new WeakMap<object, SessionState>();
|
|
210
|
+
|
|
211
|
+
function getState(ctx: ExtensionContext): SessionState {
|
|
212
|
+
let state = states.get(ctx.sessionManager);
|
|
213
|
+
if (!state) {
|
|
214
|
+
state = {
|
|
215
|
+
config: loadConfig(ctx.cwd),
|
|
216
|
+
override: "auto",
|
|
217
|
+
};
|
|
218
|
+
states.set(ctx.sessionManager, state);
|
|
219
|
+
}
|
|
220
|
+
return state;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
pi.on("session_start", (_event, ctx) => {
|
|
224
|
+
const state: SessionState = {
|
|
225
|
+
config: loadConfig(ctx.cwd),
|
|
226
|
+
override: "auto",
|
|
227
|
+
};
|
|
228
|
+
states.set(ctx.sessionManager, state);
|
|
229
|
+
updateStatus(ctx, state);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
pi.on("model_select", (_event, ctx) => {
|
|
233
|
+
updateStatus(ctx, getState(ctx));
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
237
|
+
const state = getState(ctx);
|
|
238
|
+
const nextPayload = injectFastServiceTier(event.payload, ctx, state);
|
|
239
|
+
updateStatus(ctx, state);
|
|
240
|
+
return nextPayload;
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
pi.registerCommand("fast", {
|
|
244
|
+
description: "Manage OpenAI Codex Fast mode for ChatGPT-auth GPT-5.4/GPT-5.5",
|
|
245
|
+
getArgumentCompletions: (prefix) => {
|
|
246
|
+
const normalized = prefix.trim().toLowerCase();
|
|
247
|
+
const matches = COMMANDS.filter((command) => command.startsWith(normalized));
|
|
248
|
+
return matches.length > 0 ? matches.map((value) => ({ value, label: value })) : null;
|
|
249
|
+
},
|
|
250
|
+
handler: async (args, ctx) => {
|
|
251
|
+
const state = getState(ctx);
|
|
252
|
+
const action = args.trim().toLowerCase() || "status";
|
|
253
|
+
|
|
254
|
+
if (action === "on" || action === "enable") {
|
|
255
|
+
state.override = "on";
|
|
256
|
+
updateStatus(ctx, state);
|
|
257
|
+
ctx.ui.notify(getStatusMessage(ctx, state), "info");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (action === "off" || action === "disable") {
|
|
262
|
+
state.override = "off";
|
|
263
|
+
updateStatus(ctx, state);
|
|
264
|
+
ctx.ui.notify(getStatusMessage(ctx, state), "info");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (action === "auto" || action === "default") {
|
|
269
|
+
state.override = "auto";
|
|
270
|
+
state.config = loadConfig(ctx.cwd);
|
|
271
|
+
updateStatus(ctx, state);
|
|
272
|
+
ctx.ui.notify(getStatusMessage(ctx, state), "info");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (action === "toggle") {
|
|
277
|
+
state.override = isFastEnabled(state) ? "off" : "on";
|
|
278
|
+
updateStatus(ctx, state);
|
|
279
|
+
ctx.ui.notify(getStatusMessage(ctx, state), "info");
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (action === "status") {
|
|
284
|
+
updateStatus(ctx, state);
|
|
285
|
+
ctx.ui.notify(getStatusMessage(ctx, state), "info");
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
ctx.ui.notify("Usage: /fast on | off | auto | toggle | status", "warning");
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@diegopetrucci/pi-openai-fast",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A pi extension that enables OpenAI Codex Fast mode for ChatGPT-auth GPT-5.4 and GPT-5.5 by injecting the priority service tier.",
|
|
5
|
+
"keywords": ["pi-package", "pi", "openai", "codex", "fast"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/diegopetrucci/pi-extensions.git",
|
|
10
|
+
"directory": "extensions/openai-fast"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"index.ts",
|
|
14
|
+
"openai-fast.example.json",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"pi": {
|
|
21
|
+
"extensions": [
|
|
22
|
+
"index.ts"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
27
|
+
}
|
|
28
|
+
}
|