@likelz/pi-gpt 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 ADDED
@@ -0,0 +1,72 @@
1
+ # pi-gpt
2
+
3
+ GPT fast mode for [Pi](https://pi.dev), extracted from `gpt-fast-mode.ts`.
4
+
5
+ Run `/fast` to toggle priority processing for an explicit list of models. The extension adds `service_tier: "priority"` to matching requests and displays `fast` in the status bar. Provider support and account eligibility determine the actual processing tier and charges.
6
+
7
+ ## Install
8
+
9
+ Install from npm:
10
+
11
+ ```sh
12
+ pi install npm:@likelz/pi-gpt
13
+ ```
14
+
15
+ Or from GitHub:
16
+
17
+ ```sh
18
+ pi install git:github.com/ekil1100/pi-gpt
19
+ ```
20
+
21
+ Or from a local checkout:
22
+
23
+ ```sh
24
+ bun install
25
+ pi install /absolute/path/to/pi-gpt
26
+ ```
27
+
28
+ The package exposes TypeScript source through its Pi manifest, so local and Git installations work without a build step. Disable or move the original `~/.pi/agent/extensions/gpt-fast-mode.ts` outside the extensions directory before loading this package to avoid registering `/fast` twice. Rename its JSON configuration to `pi-gpt.json`.
29
+
30
+ ## Configure
31
+
32
+ Edit `~/.pi/agent/extensions/pi-gpt.json` (under `PI_CODING_AGENT_DIR` when overridden):
33
+
34
+ ```json
35
+ {
36
+ "enabled": false,
37
+ "models": ["openai/gpt-6-astra"]
38
+ }
39
+ ```
40
+
41
+ Use the exact `provider/model` identifiers shown by Pi, including any additional slashes in a model ID. The list starts empty; add models whose endpoint supports priority processing. Restart Pi or reload extensions after editing the file.
42
+
43
+ - `/fast` toggles and persists `enabled`.
44
+ - Unlisted models and disabled mode leave requests unchanged.
45
+ - Missing or invalid configuration starts with fast mode off and an empty list. Invalid configuration produces a warning.
46
+ - A failed save leaves the in-memory state unchanged.
47
+ - The status indicates the requested mode. It does not confirm the provider's actual processing tier.
48
+
49
+ ## Ultrafast
50
+
51
+ This extension requests **priority**, preserving the original behavior. OpenAI also documents `service_tier: "ultrafast"` for GPT-6 Astra in the Responses API. That is a separate tier with separate eligibility and pricing.
52
+
53
+ - **API key:** API billing and rate limits apply independently of ChatGPT subscriptions. The official guide says GPT-6 Astra Ultrafast is available to all API users at low limits; its published allowance table covers usage tiers 1–5.
54
+ - **Codex subscription:** Pro $500/month and eligible Enterprise/Edu plans have Ultrafast access. Other self-serve plans do not gain access by purchasing credits.
55
+ - A third-party proxy must support and forward the tier. This package does not verify account eligibility or enable Ultrafast.
56
+
57
+ Sources: [Ultrafast API](https://developers.openai.com/api/docs/guides/ultrafast-mode), [Fast API](https://developers.openai.com/api/docs/guides/fast-mode), [Codex speed and eligibility](https://learn.chatgpt.com/docs/agent-configuration/speed), [Codex pricing](https://learn.chatgpt.com/docs/pricing).
58
+
59
+ ## Development
60
+
61
+ Use Bun 1.4.2 and Vite+ 1.0.0. Development was verified on Node.js 24.
62
+
63
+ ```sh
64
+ vp install
65
+ vp check
66
+ vp test run
67
+ vp pack
68
+ ```
69
+
70
+ `vp pack` builds the ESM library and declarations in `dist/`. `vp run build` runs the package's build script; `vp dev` starts Vite's application server and is not used for this extension. Pi supplies its host package at runtime; Effect is a runtime dependency.
71
+
72
+ The implementation uses pure functions for state validation and request transformation, Effect for file I/O and failure handling, and a small Pi adapter for commands and events. Tests use temporary directories and make no model requests.
@@ -0,0 +1,4 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ //#region src/index.d.ts
3
+ export default function piGpt(pi: ExtensionAPI): void;
4
+ //#endregion
package/dist/index.mjs ADDED
@@ -0,0 +1,80 @@
1
+ import { dirname, join } from "node:path";
2
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
3
+ import { Effect, Either, Schema } from "effect";
4
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ //#region src/state.ts
6
+ const FastModeState = Schema.Struct({
7
+ enabled: Schema.Boolean,
8
+ models: Schema.Array(Schema.String.pipe(Schema.pattern(/^[^/\s]+\/[^\s]+$/)))
9
+ });
10
+ const emptyState = () => ({
11
+ enabled: false,
12
+ models: []
13
+ });
14
+ const decodeState = Schema.decodeUnknownSync(Schema.parseJson(FastModeState));
15
+ const modelKey = (model) => model ? `${model.provider}/${model.id}` : void 0;
16
+ const isActive = (state, model) => {
17
+ const key = modelKey(model);
18
+ return state.enabled && key !== void 0 && state.models.includes(key);
19
+ };
20
+ const transformRequest = (state, model, payload) => isActive(state, model) && typeof payload === "object" && payload !== null && !Array.isArray(payload) ? {
21
+ ...payload,
22
+ service_tier: "priority"
23
+ } : void 0;
24
+ const describeState = (state, model) => {
25
+ const key = modelKey(model) ?? "no model";
26
+ if (!state.enabled) return `Fast mode is off. Current model: ${key}.`;
27
+ return isActive(state, model) ? `Fast mode is on for ${key}.` : `Fast mode is on, but ${key} is not listed in pi-gpt.json models.`;
28
+ };
29
+ //#endregion
30
+ //#region src/storage.ts
31
+ const isMissing = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
32
+ const loadState = (path) => Effect.try({
33
+ try: () => readFileSync(path, "utf8"),
34
+ catch: (error) => error
35
+ }).pipe(Effect.flatMap((text) => Effect.try(() => decodeState(text))), Effect.catchAll((error) => Effect.sync(() => {
36
+ if (!isMissing(error)) console.warn(`[pi-gpt] Failed to read ${path}: ${String(error)}`);
37
+ return emptyState();
38
+ })));
39
+ const saveState = (path, state) => Effect.try(() => {
40
+ mkdirSync(dirname(path), { recursive: true });
41
+ writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`, "utf8");
42
+ });
43
+ //#endregion
44
+ //#region src/index.ts
45
+ const updateStatus = (ctx, state) => {
46
+ ctx.ui.setStatus("gpt-fast-mode", isActive(state, ctx.model) ? ctx.ui.theme.fg("accent", "fast") : void 0);
47
+ };
48
+ function piGpt(pi) {
49
+ const path = join(getAgentDir(), "extensions", "pi-gpt.json");
50
+ let state = Effect.runSync(loadState(path));
51
+ pi.registerCommand("fast", {
52
+ description: "Toggle GPT Fast mode for configured models",
53
+ handler: async (args, ctx) => {
54
+ if (args.trim()) {
55
+ ctx.ui.notify("Usage: /fast", "error");
56
+ return;
57
+ }
58
+ const next = {
59
+ ...state,
60
+ enabled: !state.enabled
61
+ };
62
+ const saved = Effect.runSync(Effect.either(saveState(path, next)));
63
+ if (Either.isLeft(saved)) {
64
+ ctx.ui.notify(`Failed to save Fast mode: ${String(saved.left)}`, "error");
65
+ return;
66
+ }
67
+ state = next;
68
+ updateStatus(ctx, state);
69
+ ctx.ui.notify(describeState(state, ctx.model), "info");
70
+ }
71
+ });
72
+ pi.on("session_start", (_event, ctx) => {
73
+ state = Effect.runSync(loadState(path));
74
+ updateStatus(ctx, state);
75
+ });
76
+ pi.on("model_select", (_event, ctx) => updateStatus(ctx, state));
77
+ pi.on("before_provider_request", (event, ctx) => transformRequest(state, ctx.model, event.payload));
78
+ }
79
+ //#endregion
80
+ export { piGpt as default };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@likelz/pi-gpt",
3
+ "version": "0.1.0",
4
+ "description": "GPT fast mode for Pi with explicit model allowlists.",
5
+ "keywords": [
6
+ "gpt",
7
+ "pi",
8
+ "pi-package"
9
+ ],
10
+ "homepage": "https://github.com/ekil1100/pi-gpt#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/ekil1100/pi-gpt/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/ekil1100/pi-gpt.git"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src"
21
+ ],
22
+ "type": "module",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.mts",
26
+ "import": "./dist/index.mjs"
27
+ }
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "vp pack",
34
+ "dev": "vp pack --watch",
35
+ "test": "vp test run",
36
+ "check": "vp check",
37
+ "prepublishOnly": "vp check && vp test run && vp pack"
38
+ },
39
+ "dependencies": {
40
+ "effect": "^3.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@earendil-works/pi-coding-agent": "1.0.2",
44
+ "@types/node": "^24.0.0",
45
+ "typescript": "^7.0.2",
46
+ "vite-plus": "1.0.0"
47
+ },
48
+ "peerDependencies": {
49
+ "@earendil-works/pi-coding-agent": "*"
50
+ },
51
+ "packageManager": "bun@1.4.2",
52
+ "pi": {
53
+ "extensions": [
54
+ "./src/index.ts"
55
+ ]
56
+ }
57
+ }
package/src/index.ts ADDED
@@ -0,0 +1,49 @@
1
+ import { join } from "node:path";
2
+ import {
3
+ getAgentDir,
4
+ type ExtensionAPI,
5
+ type ExtensionContext,
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import { Effect, Either } from "effect";
8
+ import { describeState, isActive, transformRequest, type FastModeState } from "./state.ts";
9
+ import { loadState, saveState } from "./storage.ts";
10
+
11
+ const updateStatus = (ctx: ExtensionContext, state: FastModeState): void => {
12
+ ctx.ui.setStatus(
13
+ "gpt-fast-mode",
14
+ isActive(state, ctx.model) ? ctx.ui.theme.fg("accent", "fast") : undefined,
15
+ );
16
+ };
17
+
18
+ export default function piGpt(pi: ExtensionAPI): void {
19
+ const path = join(getAgentDir(), "extensions", "pi-gpt.json");
20
+ let state = Effect.runSync(loadState(path));
21
+
22
+ pi.registerCommand("fast", {
23
+ description: "Toggle GPT Fast mode for configured models",
24
+ handler: async (args, ctx) => {
25
+ if (args.trim()) {
26
+ ctx.ui.notify("Usage: /fast", "error");
27
+ return;
28
+ }
29
+ const next = { ...state, enabled: !state.enabled };
30
+ const saved = Effect.runSync(Effect.either(saveState(path, next)));
31
+ if (Either.isLeft(saved)) {
32
+ ctx.ui.notify(`Failed to save Fast mode: ${String(saved.left)}`, "error");
33
+ return;
34
+ }
35
+ state = next;
36
+ updateStatus(ctx, state);
37
+ ctx.ui.notify(describeState(state, ctx.model), "info");
38
+ },
39
+ });
40
+
41
+ pi.on("session_start", (_event, ctx) => {
42
+ state = Effect.runSync(loadState(path));
43
+ updateStatus(ctx, state);
44
+ });
45
+ pi.on("model_select", (_event, ctx) => updateStatus(ctx, state));
46
+ pi.on("before_provider_request", (event, ctx) =>
47
+ transformRequest(state, ctx.model, event.payload),
48
+ );
49
+ }
package/src/state.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { Schema } from "effect";
2
+
3
+ export const FastModeState = Schema.Struct({
4
+ enabled: Schema.Boolean,
5
+ models: Schema.Array(Schema.String.pipe(Schema.pattern(/^[^/\s]+\/[^\s]+$/))),
6
+ });
7
+ export type FastModeState = typeof FastModeState.Type;
8
+ export const emptyState = (): FastModeState => ({ enabled: false, models: [] });
9
+ export const decodeState = Schema.decodeUnknownSync(Schema.parseJson(FastModeState));
10
+
11
+ type Model = { readonly provider: string; readonly id: string } | undefined;
12
+ export const modelKey = (model: Model): string | undefined =>
13
+ model ? `${model.provider}/${model.id}` : undefined;
14
+
15
+ export const isActive = (state: FastModeState, model: Model): boolean => {
16
+ const key = modelKey(model);
17
+ return state.enabled && key !== undefined && state.models.includes(key);
18
+ };
19
+
20
+ export const transformRequest = (
21
+ state: FastModeState,
22
+ model: Model,
23
+ payload: unknown,
24
+ ): Record<string, unknown> | undefined =>
25
+ isActive(state, model) &&
26
+ typeof payload === "object" &&
27
+ payload !== null &&
28
+ !Array.isArray(payload)
29
+ ? { ...payload, service_tier: "priority" }
30
+ : undefined;
31
+
32
+ export const describeState = (state: FastModeState, model: Model): string => {
33
+ const key = modelKey(model) ?? "no model";
34
+ if (!state.enabled) return `Fast mode is off. Current model: ${key}.`;
35
+ return isActive(state, model)
36
+ ? `Fast mode is on for ${key}.`
37
+ : `Fast mode is on, but ${key} is not listed in pi-gpt.json models.`;
38
+ };
package/src/storage.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { Effect } from "effect";
4
+ import { decodeState, emptyState, type FastModeState } from "./state.ts";
5
+
6
+ const isMissing = (error: unknown): boolean =>
7
+ typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
8
+
9
+ export const loadState = (path: string) =>
10
+ Effect.try({
11
+ try: () => readFileSync(path, "utf8"),
12
+ catch: (error) => error,
13
+ }).pipe(
14
+ Effect.flatMap((text) => Effect.try(() => decodeState(text))),
15
+ Effect.catchAll((error) =>
16
+ Effect.sync(() => {
17
+ if (!isMissing(error)) console.warn(`[pi-gpt] Failed to read ${path}: ${String(error)}`);
18
+ return emptyState();
19
+ }),
20
+ ),
21
+ );
22
+
23
+ export const saveState = (path: string, state: FastModeState) =>
24
+ Effect.try(() => {
25
+ mkdirSync(dirname(path), { recursive: true });
26
+ writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`, "utf8");
27
+ });