@monotykamary/pi-better-openai 0.1.23

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/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@monotykamary/pi-better-openai",
3
+ "version": "0.1.23",
4
+ "description": "Improve OpenAI in pi with fast mode, usage stats, realtime voice, image generation, and footer polish.",
5
+ "keywords": [
6
+ "fast",
7
+ "gpt",
8
+ "image",
9
+ "openai",
10
+ "pi",
11
+ "pi-extension",
12
+ "pi-package",
13
+ "priority",
14
+ "service-tier",
15
+ "voice",
16
+ "webrtc"
17
+ ],
18
+ "homepage": "https://github.com/monotykamary/pi-better-openai#readme",
19
+ "bugs": {
20
+ "url": "https://github.com/monotykamary/pi-better-openai/issues"
21
+ },
22
+ "license": "MIT",
23
+ "author": "monotykamary",
24
+ "contributors": [
25
+ {
26
+ "name": "Matt Leong",
27
+ "url": "https://github.com/mattleong"
28
+ }
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/monotykamary/pi-better-openai.git"
33
+ },
34
+ "files": [
35
+ "index.ts",
36
+ "src/",
37
+ "README.md",
38
+ "LICENSE",
39
+ "THIRD_PARTY_NOTICES.md"
40
+ ],
41
+ "type": "module",
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "dependencies": {
46
+ "https-proxy-agent": "9.1.0",
47
+ "proxy-from-env": "2.1.0",
48
+ "sharp": "0.35.3",
49
+ "undici": "8.10.0",
50
+ "ws": "8.21.2"
51
+ },
52
+ "devDependencies": {
53
+ "@earendil-works/pi-coding-agent": "0.80.6",
54
+ "@earendil-works/pi-tui": "0.80.6",
55
+ "@types/node": "~22.19.0",
56
+ "@types/proxy-from-env": "1.0.4",
57
+ "@types/ws": "8.18.1",
58
+ "oxfmt": "^0.47.0",
59
+ "oxlint": "^1.62.0",
60
+ "typescript": "^6.0.3",
61
+ "vitest": "^4.1.5"
62
+ },
63
+ "peerDependencies": {
64
+ "@earendil-works/pi-coding-agent": "*",
65
+ "@earendil-works/pi-tui": "*"
66
+ },
67
+ "optionalDependencies": {
68
+ "@oh-my-pi/pi-natives-darwin-arm64": "17.2.9",
69
+ "@oh-my-pi/pi-natives-darwin-x64": "17.2.9",
70
+ "@oh-my-pi/pi-natives-linux-arm64": "17.2.9",
71
+ "@oh-my-pi/pi-natives-linux-x64": "17.2.9",
72
+ "@oh-my-pi/pi-natives-win32-x64": "17.2.9"
73
+ },
74
+ "overrides": {
75
+ "postcss": "8.5.25"
76
+ },
77
+ "engines": {
78
+ "node": ">=22.19.0"
79
+ },
80
+ "pi": {
81
+ "extensions": [
82
+ "./index.ts"
83
+ ]
84
+ },
85
+ "scripts": {
86
+ "check": "npm run typecheck && npm run lint && npm run format:check && npm test",
87
+ "format": "oxfmt .",
88
+ "format:check": "oxfmt --check .",
89
+ "lint": "oxlint --deny-warnings index.ts src/*.ts src/*/*.ts tests/*.ts",
90
+ "lint:fix": "oxlint --fix index.ts src/*.ts src/*/*.ts tests/*.ts",
91
+ "test": "vitest run",
92
+ "typecheck": "tsc --noEmit"
93
+ }
94
+ }
@@ -0,0 +1,136 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
4
+ import { piAgentDir } from "./paths.ts";
5
+
6
+ export const AUTH_FILE = join(piAgentDir(), "auth.json");
7
+
8
+ export type CodexCredentials = {
9
+ accessToken: string;
10
+ accountId: string;
11
+ };
12
+
13
+ export type CodexCredentialsWithSource = CodexCredentials & {
14
+ source: "modelRegistry" | "authFile";
15
+ };
16
+
17
+ function waitForSignal<T>(operation: Promise<T>, signal?: AbortSignal): Promise<T> {
18
+ if (!signal) return operation;
19
+ if (signal.aborted) return Promise.reject(signal.reason ?? new Error("Operation was aborted."));
20
+
21
+ return new Promise<T>((resolve, reject) => {
22
+ const onAbort = () => {
23
+ cleanup();
24
+ reject(signal.reason ?? new Error("Operation was aborted."));
25
+ };
26
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
27
+ signal.addEventListener("abort", onAbort, { once: true });
28
+ void operation.then(
29
+ (value) => {
30
+ cleanup();
31
+ resolve(value);
32
+ },
33
+ (error: unknown) => {
34
+ cleanup();
35
+ reject(error);
36
+ },
37
+ );
38
+ });
39
+ }
40
+
41
+ function decodeBase64Url(value: string): string {
42
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
43
+ const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
44
+ return Buffer.from(padded, "base64").toString("utf8");
45
+ }
46
+
47
+ function isRecord(value: unknown): value is Record<string, unknown> {
48
+ return typeof value === "object" && value !== null && !Array.isArray(value);
49
+ }
50
+
51
+ export function extractAccountIdFromJwt(token: string): string | undefined {
52
+ try {
53
+ const [, payload] = token.split(".");
54
+ if (!payload) return undefined;
55
+ const parsed = JSON.parse(decodeBase64Url(payload)) as unknown;
56
+ if (!isRecord(parsed)) return undefined;
57
+ const auth = parsed["https://api.openai.com/auth"];
58
+ if (!isRecord(auth)) return undefined;
59
+ const accountId = auth.chatgpt_account_id;
60
+ return typeof accountId === "string" && accountId.trim() ? accountId.trim() : undefined;
61
+ } catch {
62
+ return undefined;
63
+ }
64
+ }
65
+
66
+ export function parseCodexRegistryCredentials(
67
+ raw: string | undefined,
68
+ ): CodexCredentials | undefined {
69
+ const value = raw?.trim();
70
+ if (!value) return undefined;
71
+ try {
72
+ const parsed = JSON.parse(value) as unknown;
73
+ if (isRecord(parsed)) {
74
+ const accessToken =
75
+ typeof parsed.access === "string"
76
+ ? parsed.access
77
+ : typeof parsed.token === "string"
78
+ ? parsed.token
79
+ : undefined;
80
+ const accountId =
81
+ typeof parsed.accountId === "string"
82
+ ? parsed.accountId
83
+ : typeof parsed.account_id === "string"
84
+ ? parsed.account_id
85
+ : undefined;
86
+ if (accessToken?.trim() && accountId?.trim())
87
+ return { accessToken: accessToken.trim(), accountId: accountId.trim() };
88
+ }
89
+ } catch {
90
+ // Plain bearer token is expected for openai-codex in pi.
91
+ }
92
+ const accountId = extractAccountIdFromJwt(value);
93
+ return accountId ? { accessToken: value, accountId } : undefined;
94
+ }
95
+
96
+ export function readCodexAuth(): CodexCredentials | undefined {
97
+ try {
98
+ const auth = JSON.parse(readFileSync(AUTH_FILE, "utf8")) as Record<
99
+ string,
100
+ | {
101
+ type?: string;
102
+ access?: string | null;
103
+ accountId?: string | null;
104
+ account_id?: string | null;
105
+ expires?: number | null;
106
+ }
107
+ | undefined
108
+ >;
109
+ const entry = auth["openai-codex"];
110
+ if (entry?.type !== "oauth") return undefined;
111
+ if (typeof entry.expires === "number" && Date.now() >= entry.expires) return undefined;
112
+ const accessToken = entry.access?.trim();
113
+ const accountId = (entry.accountId ?? entry.account_id)?.trim();
114
+ return accessToken && accountId ? { accessToken, accountId } : undefined;
115
+ } catch {
116
+ return undefined;
117
+ }
118
+ }
119
+
120
+ export async function getCodexCredentials(
121
+ ctx?: Pick<ExtensionContext, "modelRegistry">,
122
+ signal?: AbortSignal,
123
+ ): Promise<CodexCredentialsWithSource | undefined> {
124
+ if (signal?.aborted) throw signal.reason ?? new Error("Operation was aborted.");
125
+ const registryRequest = ctx?.modelRegistry?.getApiKeyForProvider("openai-codex");
126
+ const registryToken = registryRequest
127
+ ? await waitForSignal(
128
+ registryRequest.catch(() => undefined),
129
+ signal,
130
+ )
131
+ : undefined;
132
+ const registryCredentials = parseCodexRegistryCredentials(registryToken);
133
+ if (registryCredentials) return { ...registryCredentials, source: "modelRegistry" };
134
+ const auth = readCodexAuth();
135
+ return auth ? { ...auth, source: "authFile" } : undefined;
136
+ }