@mariozechner/pi-ai 0.35.0 → 0.37.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +17 -1
- package/dist/cli.js.map +1 -1
- package/dist/models.d.ts +1 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +231 -42
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +269 -80
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +3 -2
- package/dist/models.js.map +1 -1
- package/dist/providers/openai-codex/constants.d.ts +21 -0
- package/dist/providers/openai-codex/constants.d.ts.map +1 -0
- package/dist/providers/openai-codex/constants.js +21 -0
- package/dist/providers/openai-codex/constants.js.map +1 -0
- package/dist/providers/openai-codex/prompts/codex-instructions.md +105 -0
- package/dist/providers/openai-codex/prompts/codex.d.ts +11 -0
- package/dist/providers/openai-codex/prompts/codex.d.ts.map +1 -0
- package/dist/providers/openai-codex/prompts/codex.js +184 -0
- package/dist/providers/openai-codex/prompts/codex.js.map +1 -0
- package/dist/providers/openai-codex/prompts/pi-codex-bridge.d.ts +6 -0
- package/dist/providers/openai-codex/prompts/pi-codex-bridge.d.ts.map +1 -0
- package/dist/providers/openai-codex/prompts/pi-codex-bridge.js +48 -0
- package/dist/providers/openai-codex/prompts/pi-codex-bridge.js.map +1 -0
- package/dist/providers/openai-codex/request-transformer.d.ts +41 -0
- package/dist/providers/openai-codex/request-transformer.d.ts.map +1 -0
- package/dist/providers/openai-codex/request-transformer.js +247 -0
- package/dist/providers/openai-codex/request-transformer.js.map +1 -0
- package/dist/providers/openai-codex/response-handler.d.ts +19 -0
- package/dist/providers/openai-codex/response-handler.d.ts.map +1 -0
- package/dist/providers/openai-codex/response-handler.js +107 -0
- package/dist/providers/openai-codex/response-handler.js.map +1 -0
- package/dist/providers/openai-codex-responses.d.ts +10 -0
- package/dist/providers/openai-codex-responses.d.ts.map +1 -0
- package/dist/providers/openai-codex-responses.js +530 -0
- package/dist/providers/openai-codex-responses.js.map +1 -0
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +27 -1
- package/dist/stream.js.map +1 -1
- package/dist/types.d.ts +6 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/oauth/github-copilot.d.ts +2 -0
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/dist/utils/oauth/github-copilot.js +28 -5
- package/dist/utils/oauth/github-copilot.js.map +1 -1
- package/dist/utils/oauth/google-antigravity.d.ts +3 -1
- package/dist/utils/oauth/google-antigravity.d.ts.map +1 -1
- package/dist/utils/oauth/google-antigravity.js +100 -19
- package/dist/utils/oauth/google-antigravity.js.map +1 -1
- package/dist/utils/oauth/google-gemini-cli.d.ts +3 -1
- package/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -1
- package/dist/utils/oauth/google-gemini-cli.js +100 -19
- package/dist/utils/oauth/google-gemini-cli.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +1 -0
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +11 -0
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/openai-codex.d.ts +28 -0
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
- package/dist/utils/oauth/openai-codex.js +342 -0
- package/dist/utils/oauth/openai-codex.js.map +1 -0
- package/dist/utils/oauth/types.d.ts +2 -1
- package/dist/utils/oauth/types.d.ts.map +1 -1
- package/dist/utils/oauth/types.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Codex (ChatGPT OAuth) flow
|
|
3
|
+
*/
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import http from "node:http";
|
|
6
|
+
import { generatePKCE } from "./pkce.js";
|
|
7
|
+
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
8
|
+
const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
9
|
+
const TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
10
|
+
const REDIRECT_URI = "http://localhost:1455/auth/callback";
|
|
11
|
+
const SCOPE = "openid profile email offline_access";
|
|
12
|
+
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
13
|
+
const SUCCESS_HTML = `<!doctype html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="utf-8" />
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
18
|
+
<title>Authentication successful</title>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<p>Authentication successful. Return to your terminal to continue.</p>
|
|
22
|
+
</body>
|
|
23
|
+
</html>`;
|
|
24
|
+
function createState() {
|
|
25
|
+
return randomBytes(16).toString("hex");
|
|
26
|
+
}
|
|
27
|
+
function parseAuthorizationInput(input) {
|
|
28
|
+
const value = input.trim();
|
|
29
|
+
if (!value)
|
|
30
|
+
return {};
|
|
31
|
+
try {
|
|
32
|
+
const url = new URL(value);
|
|
33
|
+
return {
|
|
34
|
+
code: url.searchParams.get("code") ?? undefined,
|
|
35
|
+
state: url.searchParams.get("state") ?? undefined,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// not a URL
|
|
40
|
+
}
|
|
41
|
+
if (value.includes("#")) {
|
|
42
|
+
const [code, state] = value.split("#", 2);
|
|
43
|
+
return { code, state };
|
|
44
|
+
}
|
|
45
|
+
if (value.includes("code=")) {
|
|
46
|
+
const params = new URLSearchParams(value);
|
|
47
|
+
return {
|
|
48
|
+
code: params.get("code") ?? undefined,
|
|
49
|
+
state: params.get("state") ?? undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return { code: value };
|
|
53
|
+
}
|
|
54
|
+
function decodeJwt(token) {
|
|
55
|
+
try {
|
|
56
|
+
const parts = token.split(".");
|
|
57
|
+
if (parts.length !== 3)
|
|
58
|
+
return null;
|
|
59
|
+
const payload = parts[1] ?? "";
|
|
60
|
+
const decoded = Buffer.from(payload, "base64").toString("utf-8");
|
|
61
|
+
return JSON.parse(decoded);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function exchangeAuthorizationCode(code, verifier, redirectUri = REDIRECT_URI) {
|
|
68
|
+
const response = await fetch(TOKEN_URL, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
71
|
+
body: new URLSearchParams({
|
|
72
|
+
grant_type: "authorization_code",
|
|
73
|
+
client_id: CLIENT_ID,
|
|
74
|
+
code,
|
|
75
|
+
code_verifier: verifier,
|
|
76
|
+
redirect_uri: redirectUri,
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const text = await response.text().catch(() => "");
|
|
81
|
+
console.error("[openai-codex] code->token failed:", response.status, text);
|
|
82
|
+
return { type: "failed" };
|
|
83
|
+
}
|
|
84
|
+
const json = (await response.json());
|
|
85
|
+
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
86
|
+
console.error("[openai-codex] token response missing fields:", json);
|
|
87
|
+
return { type: "failed" };
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
type: "success",
|
|
91
|
+
access: json.access_token,
|
|
92
|
+
refresh: json.refresh_token,
|
|
93
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function refreshAccessToken(refreshToken) {
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch(TOKEN_URL, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
101
|
+
body: new URLSearchParams({
|
|
102
|
+
grant_type: "refresh_token",
|
|
103
|
+
refresh_token: refreshToken,
|
|
104
|
+
client_id: CLIENT_ID,
|
|
105
|
+
}),
|
|
106
|
+
});
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const text = await response.text().catch(() => "");
|
|
109
|
+
console.error("[openai-codex] Token refresh failed:", response.status, text);
|
|
110
|
+
return { type: "failed" };
|
|
111
|
+
}
|
|
112
|
+
const json = (await response.json());
|
|
113
|
+
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
114
|
+
console.error("[openai-codex] Token refresh response missing fields:", json);
|
|
115
|
+
return { type: "failed" };
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
type: "success",
|
|
119
|
+
access: json.access_token,
|
|
120
|
+
refresh: json.refresh_token,
|
|
121
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.error("[openai-codex] Token refresh error:", error);
|
|
126
|
+
return { type: "failed" };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function createAuthorizationFlow() {
|
|
130
|
+
const { verifier, challenge } = await generatePKCE();
|
|
131
|
+
const state = createState();
|
|
132
|
+
const url = new URL(AUTHORIZE_URL);
|
|
133
|
+
url.searchParams.set("response_type", "code");
|
|
134
|
+
url.searchParams.set("client_id", CLIENT_ID);
|
|
135
|
+
url.searchParams.set("redirect_uri", REDIRECT_URI);
|
|
136
|
+
url.searchParams.set("scope", SCOPE);
|
|
137
|
+
url.searchParams.set("code_challenge", challenge);
|
|
138
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
139
|
+
url.searchParams.set("state", state);
|
|
140
|
+
url.searchParams.set("id_token_add_organizations", "true");
|
|
141
|
+
url.searchParams.set("codex_cli_simplified_flow", "true");
|
|
142
|
+
url.searchParams.set("originator", "codex_cli_rs");
|
|
143
|
+
return { verifier, state, url: url.toString() };
|
|
144
|
+
}
|
|
145
|
+
function startLocalOAuthServer(state) {
|
|
146
|
+
let lastCode = null;
|
|
147
|
+
let cancelled = false;
|
|
148
|
+
const server = http.createServer((req, res) => {
|
|
149
|
+
try {
|
|
150
|
+
const url = new URL(req.url || "", "http://localhost");
|
|
151
|
+
if (url.pathname !== "/auth/callback") {
|
|
152
|
+
res.statusCode = 404;
|
|
153
|
+
res.end("Not found");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (url.searchParams.get("state") !== state) {
|
|
157
|
+
res.statusCode = 400;
|
|
158
|
+
res.end("State mismatch");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const code = url.searchParams.get("code");
|
|
162
|
+
if (!code) {
|
|
163
|
+
res.statusCode = 400;
|
|
164
|
+
res.end("Missing authorization code");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
res.statusCode = 200;
|
|
168
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
169
|
+
res.end(SUCCESS_HTML);
|
|
170
|
+
lastCode = code;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
res.statusCode = 500;
|
|
174
|
+
res.end("Internal error");
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
return new Promise((resolve) => {
|
|
178
|
+
server
|
|
179
|
+
.listen(1455, "127.0.0.1", () => {
|
|
180
|
+
resolve({
|
|
181
|
+
close: () => server.close(),
|
|
182
|
+
cancelWait: () => {
|
|
183
|
+
cancelled = true;
|
|
184
|
+
},
|
|
185
|
+
waitForCode: async () => {
|
|
186
|
+
const sleep = () => new Promise((r) => setTimeout(r, 100));
|
|
187
|
+
for (let i = 0; i < 600; i += 1) {
|
|
188
|
+
if (lastCode)
|
|
189
|
+
return { code: lastCode };
|
|
190
|
+
if (cancelled)
|
|
191
|
+
return null;
|
|
192
|
+
await sleep();
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
})
|
|
198
|
+
.on("error", (err) => {
|
|
199
|
+
console.error("[openai-codex] Failed to bind http://127.0.0.1:1455 (", err.code, ") Falling back to manual paste.");
|
|
200
|
+
resolve({
|
|
201
|
+
close: () => {
|
|
202
|
+
try {
|
|
203
|
+
server.close();
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// ignore
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
cancelWait: () => { },
|
|
210
|
+
waitForCode: async () => null,
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function getAccountId(accessToken) {
|
|
216
|
+
const payload = decodeJwt(accessToken);
|
|
217
|
+
const auth = payload?.[JWT_CLAIM_PATH];
|
|
218
|
+
const accountId = auth?.chatgpt_account_id;
|
|
219
|
+
return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Login with OpenAI Codex OAuth
|
|
223
|
+
*
|
|
224
|
+
* @param options.onAuth - Called with URL and instructions when auth starts
|
|
225
|
+
* @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)
|
|
226
|
+
* @param options.onProgress - Optional progress messages
|
|
227
|
+
* @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.
|
|
228
|
+
* Races with browser callback - whichever completes first wins.
|
|
229
|
+
* Useful for showing paste input immediately alongside browser flow.
|
|
230
|
+
*/
|
|
231
|
+
export async function loginOpenAICodex(options) {
|
|
232
|
+
const { verifier, state, url } = await createAuthorizationFlow();
|
|
233
|
+
const server = await startLocalOAuthServer(state);
|
|
234
|
+
options.onAuth({ url, instructions: "A browser window should open. Complete login to finish." });
|
|
235
|
+
let code;
|
|
236
|
+
try {
|
|
237
|
+
if (options.onManualCodeInput) {
|
|
238
|
+
// Race between browser callback and manual input
|
|
239
|
+
let manualCode;
|
|
240
|
+
let manualError;
|
|
241
|
+
const manualPromise = options
|
|
242
|
+
.onManualCodeInput()
|
|
243
|
+
.then((input) => {
|
|
244
|
+
manualCode = input;
|
|
245
|
+
server.cancelWait();
|
|
246
|
+
})
|
|
247
|
+
.catch((err) => {
|
|
248
|
+
manualError = err instanceof Error ? err : new Error(String(err));
|
|
249
|
+
server.cancelWait();
|
|
250
|
+
});
|
|
251
|
+
const result = await server.waitForCode();
|
|
252
|
+
// If manual input was cancelled, throw that error
|
|
253
|
+
if (manualError) {
|
|
254
|
+
throw manualError;
|
|
255
|
+
}
|
|
256
|
+
if (result?.code) {
|
|
257
|
+
// Browser callback won
|
|
258
|
+
code = result.code;
|
|
259
|
+
}
|
|
260
|
+
else if (manualCode) {
|
|
261
|
+
// Manual input won (or callback timed out and user had entered code)
|
|
262
|
+
const parsed = parseAuthorizationInput(manualCode);
|
|
263
|
+
if (parsed.state && parsed.state !== state) {
|
|
264
|
+
throw new Error("State mismatch");
|
|
265
|
+
}
|
|
266
|
+
code = parsed.code;
|
|
267
|
+
}
|
|
268
|
+
// If still no code, wait for manual promise to complete and try that
|
|
269
|
+
if (!code) {
|
|
270
|
+
await manualPromise;
|
|
271
|
+
if (manualError) {
|
|
272
|
+
throw manualError;
|
|
273
|
+
}
|
|
274
|
+
if (manualCode) {
|
|
275
|
+
const parsed = parseAuthorizationInput(manualCode);
|
|
276
|
+
if (parsed.state && parsed.state !== state) {
|
|
277
|
+
throw new Error("State mismatch");
|
|
278
|
+
}
|
|
279
|
+
code = parsed.code;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
// Original flow: wait for callback, then prompt if needed
|
|
285
|
+
const result = await server.waitForCode();
|
|
286
|
+
if (result?.code) {
|
|
287
|
+
code = result.code;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Fallback to onPrompt if still no code
|
|
291
|
+
if (!code) {
|
|
292
|
+
const input = await options.onPrompt({
|
|
293
|
+
message: "Paste the authorization code (or full redirect URL):",
|
|
294
|
+
});
|
|
295
|
+
const parsed = parseAuthorizationInput(input);
|
|
296
|
+
if (parsed.state && parsed.state !== state) {
|
|
297
|
+
throw new Error("State mismatch");
|
|
298
|
+
}
|
|
299
|
+
code = parsed.code;
|
|
300
|
+
}
|
|
301
|
+
if (!code) {
|
|
302
|
+
throw new Error("Missing authorization code");
|
|
303
|
+
}
|
|
304
|
+
const tokenResult = await exchangeAuthorizationCode(code, verifier);
|
|
305
|
+
if (tokenResult.type !== "success") {
|
|
306
|
+
throw new Error("Token exchange failed");
|
|
307
|
+
}
|
|
308
|
+
const accountId = getAccountId(tokenResult.access);
|
|
309
|
+
if (!accountId) {
|
|
310
|
+
throw new Error("Failed to extract accountId from token");
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
access: tokenResult.access,
|
|
314
|
+
refresh: tokenResult.refresh,
|
|
315
|
+
expires: tokenResult.expires,
|
|
316
|
+
accountId,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
server.close();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Refresh OpenAI Codex OAuth token
|
|
325
|
+
*/
|
|
326
|
+
export async function refreshOpenAICodexToken(refreshToken) {
|
|
327
|
+
const result = await refreshAccessToken(refreshToken);
|
|
328
|
+
if (result.type !== "success") {
|
|
329
|
+
throw new Error("Failed to refresh OpenAI Codex token");
|
|
330
|
+
}
|
|
331
|
+
const accountId = getAccountId(result.access);
|
|
332
|
+
if (!accountId) {
|
|
333
|
+
throw new Error("Failed to extract accountId from token");
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
access: result.access,
|
|
337
|
+
refresh: result.refresh,
|
|
338
|
+
expires: result.expires,
|
|
339
|
+
accountId,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=openai-codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-codex.js","sourceRoot":"","sources":["../../../src/utils/oauth/openai-codex.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OpenAI Codex (ChatGPT OAuth) flow\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport http from \"node:http\";\nimport { generatePKCE } from \"./pkce.js\";\nimport type { OAuthCredentials, OAuthPrompt } from \"./types.js\";\n\nconst CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\";\nconst AUTHORIZE_URL = \"https://auth.openai.com/oauth/authorize\";\nconst TOKEN_URL = \"https://auth.openai.com/oauth/token\";\nconst REDIRECT_URI = \"http://localhost:1455/auth/callback\";\nconst SCOPE = \"openid profile email offline_access\";\nconst JWT_CLAIM_PATH = \"https://api.openai.com/auth\";\n\nconst SUCCESS_HTML = `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Authentication successful</title>\n</head>\n<body>\n <p>Authentication successful. Return to your terminal to continue.</p>\n</body>\n</html>`;\n\ntype TokenSuccess = { type: \"success\"; access: string; refresh: string; expires: number };\ntype TokenFailure = { type: \"failed\" };\ntype TokenResult = TokenSuccess | TokenFailure;\n\ntype JwtPayload = {\n\t[JWT_CLAIM_PATH]?: {\n\t\tchatgpt_account_id?: string;\n\t};\n\t[key: string]: unknown;\n};\n\nfunction createState(): string {\n\treturn randomBytes(16).toString(\"hex\");\n}\n\nfunction parseAuthorizationInput(input: string): { code?: string; state?: string } {\n\tconst value = input.trim();\n\tif (!value) return {};\n\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn {\n\t\t\tcode: url.searchParams.get(\"code\") ?? undefined,\n\t\t\tstate: url.searchParams.get(\"state\") ?? undefined,\n\t\t};\n\t} catch {\n\t\t// not a URL\n\t}\n\n\tif (value.includes(\"#\")) {\n\t\tconst [code, state] = value.split(\"#\", 2);\n\t\treturn { code, state };\n\t}\n\n\tif (value.includes(\"code=\")) {\n\t\tconst params = new URLSearchParams(value);\n\t\treturn {\n\t\t\tcode: params.get(\"code\") ?? undefined,\n\t\t\tstate: params.get(\"state\") ?? undefined,\n\t\t};\n\t}\n\n\treturn { code: value };\n}\n\nfunction decodeJwt(token: string): JwtPayload | null {\n\ttry {\n\t\tconst parts = token.split(\".\");\n\t\tif (parts.length !== 3) return null;\n\t\tconst payload = parts[1] ?? \"\";\n\t\tconst decoded = Buffer.from(payload, \"base64\").toString(\"utf-8\");\n\t\treturn JSON.parse(decoded) as JwtPayload;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tverifier: string,\n\tredirectUri: string = REDIRECT_URI,\n): Promise<TokenResult> {\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode,\n\t\t\tcode_verifier: verifier,\n\t\t\tredirect_uri: redirectUri,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst text = await response.text().catch(() => \"\");\n\t\tconsole.error(\"[openai-codex] code->token failed:\", response.status, text);\n\t\treturn { type: \"failed\" };\n\t}\n\n\tconst json = (await response.json()) as {\n\t\taccess_token?: string;\n\t\trefresh_token?: string;\n\t\texpires_in?: number;\n\t};\n\n\tif (!json.access_token || !json.refresh_token || typeof json.expires_in !== \"number\") {\n\t\tconsole.error(\"[openai-codex] token response missing fields:\", json);\n\t\treturn { type: \"failed\" };\n\t}\n\n\treturn {\n\t\ttype: \"success\",\n\t\taccess: json.access_token,\n\t\trefresh: json.refresh_token,\n\t\texpires: Date.now() + json.expires_in * 1000,\n\t};\n}\n\nasync function refreshAccessToken(refreshToken: string): Promise<TokenResult> {\n\ttry {\n\t\tconst response = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t}),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tconsole.error(\"[openai-codex] Token refresh failed:\", response.status, text);\n\t\t\treturn { type: \"failed\" };\n\t\t}\n\n\t\tconst json = (await response.json()) as {\n\t\t\taccess_token?: string;\n\t\t\trefresh_token?: string;\n\t\t\texpires_in?: number;\n\t\t};\n\n\t\tif (!json.access_token || !json.refresh_token || typeof json.expires_in !== \"number\") {\n\t\t\tconsole.error(\"[openai-codex] Token refresh response missing fields:\", json);\n\t\t\treturn { type: \"failed\" };\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"success\",\n\t\t\taccess: json.access_token,\n\t\t\trefresh: json.refresh_token,\n\t\t\texpires: Date.now() + json.expires_in * 1000,\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"[openai-codex] Token refresh error:\", error);\n\t\treturn { type: \"failed\" };\n\t}\n}\n\nasync function createAuthorizationFlow(): Promise<{ verifier: string; state: string; url: string }> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst state = createState();\n\n\tconst url = new URL(AUTHORIZE_URL);\n\turl.searchParams.set(\"response_type\", \"code\");\n\turl.searchParams.set(\"client_id\", CLIENT_ID);\n\turl.searchParams.set(\"redirect_uri\", REDIRECT_URI);\n\turl.searchParams.set(\"scope\", SCOPE);\n\turl.searchParams.set(\"code_challenge\", challenge);\n\turl.searchParams.set(\"code_challenge_method\", \"S256\");\n\turl.searchParams.set(\"state\", state);\n\turl.searchParams.set(\"id_token_add_organizations\", \"true\");\n\turl.searchParams.set(\"codex_cli_simplified_flow\", \"true\");\n\turl.searchParams.set(\"originator\", \"codex_cli_rs\");\n\n\treturn { verifier, state, url: url.toString() };\n}\n\ntype OAuthServerInfo = {\n\tclose: () => void;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string } | null>;\n};\n\nfunction startLocalOAuthServer(state: string): Promise<OAuthServerInfo> {\n\tlet lastCode: string | null = null;\n\tlet cancelled = false;\n\tconst server = http.createServer((req, res) => {\n\t\ttry {\n\t\t\tconst url = new URL(req.url || \"\", \"http://localhost\");\n\t\t\tif (url.pathname !== \"/auth/callback\") {\n\t\t\t\tres.statusCode = 404;\n\t\t\t\tres.end(\"Not found\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (url.searchParams.get(\"state\") !== state) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.end(\"State mismatch\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\tif (!code) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.end(\"Missing authorization code\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tres.statusCode = 200;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(SUCCESS_HTML);\n\t\t\tlastCode = code;\n\t\t} catch {\n\t\t\tres.statusCode = 500;\n\t\t\tres.end(\"Internal error\");\n\t\t}\n\t});\n\n\treturn new Promise((resolve) => {\n\t\tserver\n\t\t\t.listen(1455, \"127.0.0.1\", () => {\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => server.close(),\n\t\t\t\t\tcancelWait: () => {\n\t\t\t\t\t\tcancelled = true;\n\t\t\t\t\t},\n\t\t\t\t\twaitForCode: async () => {\n\t\t\t\t\t\tconst sleep = () => new Promise((r) => setTimeout(r, 100));\n\t\t\t\t\t\tfor (let i = 0; i < 600; i += 1) {\n\t\t\t\t\t\t\tif (lastCode) return { code: lastCode };\n\t\t\t\t\t\t\tif (cancelled) return null;\n\t\t\t\t\t\t\tawait sleep();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t})\n\t\t\t.on(\"error\", (err: NodeJS.ErrnoException) => {\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"[openai-codex] Failed to bind http://127.0.0.1:1455 (\",\n\t\t\t\t\terr.code,\n\t\t\t\t\t\") Falling back to manual paste.\",\n\t\t\t\t);\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tserver.close();\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// ignore\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tcancelWait: () => {},\n\t\t\t\t\twaitForCode: async () => null,\n\t\t\t\t});\n\t\t\t});\n\t});\n}\n\nfunction getAccountId(accessToken: string): string | null {\n\tconst payload = decodeJwt(accessToken);\n\tconst auth = payload?.[JWT_CLAIM_PATH];\n\tconst accountId = auth?.chatgpt_account_id;\n\treturn typeof accountId === \"string\" && accountId.length > 0 ? accountId : null;\n}\n\n/**\n * Login with OpenAI Codex OAuth\n *\n * @param options.onAuth - Called with URL and instructions when auth starts\n * @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)\n * @param options.onProgress - Optional progress messages\n * @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.\n * Races with browser callback - whichever completes first wins.\n * Useful for showing paste input immediately alongside browser flow.\n */\nexport async function loginOpenAICodex(options: {\n\tonAuth: (info: { url: string; instructions?: string }) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n}): Promise<OAuthCredentials> {\n\tconst { verifier, state, url } = await createAuthorizationFlow();\n\tconst server = await startLocalOAuthServer(state);\n\n\toptions.onAuth({ url, instructions: \"A browser window should open. Complete login to finish.\" });\n\n\tlet code: string | undefined;\n\ttry {\n\t\tif (options.onManualCodeInput) {\n\t\t\t// Race between browser callback and manual input\n\t\t\tlet manualCode: string | undefined;\n\t\t\tlet manualError: Error | undefined;\n\t\t\tconst manualPromise = options\n\t\t\t\t.onManualCodeInput()\n\t\t\t\t.then((input) => {\n\t\t\t\t\tmanualCode = input;\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tmanualError = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\tserver.cancelWait();\n\t\t\t\t});\n\n\t\t\tconst result = await server.waitForCode();\n\n\t\t\t// If manual input was cancelled, throw that error\n\t\t\tif (manualError) {\n\t\t\t\tthrow manualError;\n\t\t\t}\n\n\t\t\tif (result?.code) {\n\t\t\t\t// Browser callback won\n\t\t\t\tcode = result.code;\n\t\t\t} else if (manualCode) {\n\t\t\t\t// Manual input won (or callback timed out and user had entered code)\n\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t}\n\t\t\t\tcode = parsed.code;\n\t\t\t}\n\n\t\t\t// If still no code, wait for manual promise to complete and try that\n\t\t\tif (!code) {\n\t\t\t\tawait manualPromise;\n\t\t\t\tif (manualError) {\n\t\t\t\t\tthrow manualError;\n\t\t\t\t}\n\t\t\t\tif (manualCode) {\n\t\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t\t\t}\n\t\t\t\t\tcode = parsed.code;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Original flow: wait for callback, then prompt if needed\n\t\t\tconst result = await server.waitForCode();\n\t\t\tif (result?.code) {\n\t\t\t\tcode = result.code;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to onPrompt if still no code\n\t\tif (!code) {\n\t\t\tconst input = await options.onPrompt({\n\t\t\t\tmessage: \"Paste the authorization code (or full redirect URL):\",\n\t\t\t});\n\t\t\tconst parsed = parseAuthorizationInput(input);\n\t\t\tif (parsed.state && parsed.state !== state) {\n\t\t\t\tthrow new Error(\"State mismatch\");\n\t\t\t}\n\t\t\tcode = parsed.code;\n\t\t}\n\n\t\tif (!code) {\n\t\t\tthrow new Error(\"Missing authorization code\");\n\t\t}\n\n\t\tconst tokenResult = await exchangeAuthorizationCode(code, verifier);\n\t\tif (tokenResult.type !== \"success\") {\n\t\t\tthrow new Error(\"Token exchange failed\");\n\t\t}\n\n\t\tconst accountId = getAccountId(tokenResult.access);\n\t\tif (!accountId) {\n\t\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t\t}\n\n\t\treturn {\n\t\t\taccess: tokenResult.access,\n\t\t\trefresh: tokenResult.refresh,\n\t\t\texpires: tokenResult.expires,\n\t\t\taccountId,\n\t\t};\n\t} finally {\n\t\tserver.close();\n\t}\n}\n\n/**\n * Refresh OpenAI Codex OAuth token\n */\nexport async function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials> {\n\tconst result = await refreshAccessToken(refreshToken);\n\tif (result.type !== \"success\") {\n\t\tthrow new Error(\"Failed to refresh OpenAI Codex token\");\n\t}\n\n\tconst accountId = getAccountId(result.access);\n\tif (!accountId) {\n\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t}\n\n\treturn {\n\t\taccess: result.access,\n\t\trefresh: result.refresh,\n\t\texpires: result.expires,\n\t\taccountId,\n\t};\n}\n"]}
|
|
@@ -5,8 +5,9 @@ export type OAuthCredentials = {
|
|
|
5
5
|
enterpriseUrl?: string;
|
|
6
6
|
projectId?: string;
|
|
7
7
|
email?: string;
|
|
8
|
+
accountId?: string;
|
|
8
9
|
};
|
|
9
|
-
export type OAuthProvider = "anthropic" | "github-copilot" | "google-gemini-cli" | "google-antigravity";
|
|
10
|
+
export type OAuthProvider = "anthropic" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "openai-codex";
|
|
10
11
|
export type OAuthPrompt = {
|
|
11
12
|
message: string;
|
|
12
13
|
placeholder?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GACtB,WAAW,GACX,gBAAgB,GAChB,mBAAmB,GACnB,oBAAoB,GACpB,cAAc,CAAC;AAElB,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,aAAa,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACnB","sourcesContent":["export type OAuthCredentials = {\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\tenterpriseUrl?: string;\n\tprojectId?: string;\n\temail?: string;\n\taccountId?: string;\n};\n\nexport type OAuthProvider =\n\t| \"anthropic\"\n\t| \"github-copilot\"\n\t| \"google-gemini-cli\"\n\t| \"google-antigravity\"\n\t| \"openai-codex\";\n\nexport type OAuthPrompt = {\n\tmessage: string;\n\tplaceholder?: string;\n\tallowEmpty?: boolean;\n};\n\nexport type OAuthAuthInfo = {\n\turl: string;\n\tinstructions?: string;\n};\n\nexport interface OAuthProviderInfo {\n\tid: OAuthProvider;\n\tname: string;\n\tavailable: boolean;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/utils/oauth/types.ts"],"names":[],"mappings":"","sourcesContent":["export type OAuthCredentials = {\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\tenterpriseUrl?: string;\n\tprojectId?: string;\n\temail?: string;\n};\n\nexport type OAuthProvider
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/utils/oauth/types.ts"],"names":[],"mappings":"","sourcesContent":["export type OAuthCredentials = {\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\tenterpriseUrl?: string;\n\tprojectId?: string;\n\temail?: string;\n\taccountId?: string;\n};\n\nexport type OAuthProvider =\n\t| \"anthropic\"\n\t| \"github-copilot\"\n\t| \"google-gemini-cli\"\n\t| \"google-antigravity\"\n\t| \"openai-codex\";\n\nexport type OAuthPrompt = {\n\tmessage: string;\n\tplaceholder?: string;\n\tallowEmpty?: boolean;\n};\n\nexport type OAuthAuthInfo = {\n\turl: string;\n\tinstructions?: string;\n};\n\nexport interface OAuthProviderInfo {\n\tid: OAuthProvider;\n\tname: string;\n\tavailable: boolean;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mariozechner/pi-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"clean": "rm -rf dist",
|
|
17
17
|
"generate-models": "npx tsx scripts/generate-models.ts",
|
|
18
|
-
"build": "npm run generate-models && tsgo -p tsconfig.build.json",
|
|
18
|
+
"build": "npm run generate-models && tsgo -p tsconfig.build.json && node scripts/copy-assets.js",
|
|
19
19
|
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
20
20
|
"dev:tsc": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
21
21
|
"test": "vitest --run",
|