codex-imagegen-mcp 0.1.2
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/CHANGELOG.md +96 -0
- package/LICENSE +201 -0
- package/NOTICE +19 -0
- package/README.md +373 -0
- package/dist/src/auth/borrowed.js +77 -0
- package/dist/src/auth/borrowed.js.map +1 -0
- package/dist/src/auth/browser-login.js +179 -0
- package/dist/src/auth/browser-login.js.map +1 -0
- package/dist/src/auth/device-login.js +39 -0
- package/dist/src/auth/device-login.js.map +1 -0
- package/dist/src/auth/jwt.js +69 -0
- package/dist/src/auth/jwt.js.map +1 -0
- package/dist/src/auth/manager.js +289 -0
- package/dist/src/auth/manager.js.map +1 -0
- package/dist/src/auth/oauth.js +246 -0
- package/dist/src/auth/oauth.js.map +1 -0
- package/dist/src/auth/pages.js +39 -0
- package/dist/src/auth/pages.js.map +1 -0
- package/dist/src/auth/pkce.js +14 -0
- package/dist/src/auth/pkce.js.map +1 -0
- package/dist/src/auth/store.js +75 -0
- package/dist/src/auth/store.js.map +1 -0
- package/dist/src/backend/images-client.js +223 -0
- package/dist/src/backend/images-client.js.map +1 -0
- package/dist/src/backend/ratelimits.js +183 -0
- package/dist/src/backend/ratelimits.js.map +1 -0
- package/dist/src/cli.js +407 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config.js +87 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/constants.js +52 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/doctor.js +122 -0
- package/dist/src/doctor.js.map +1 -0
- package/dist/src/errors.js +76 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/generation.js +183 -0
- package/dist/src/generation.js.map +1 -0
- package/dist/src/history.js +47 -0
- package/dist/src/history.js.map +1 -0
- package/dist/src/images/chroma.js +200 -0
- package/dist/src/images/chroma.js.map +1 -0
- package/dist/src/images/codec.js +159 -0
- package/dist/src/images/codec.js.map +1 -0
- package/dist/src/images/inputs.js +83 -0
- package/dist/src/images/inputs.js.map +1 -0
- package/dist/src/images/output.js +100 -0
- package/dist/src/images/output.js.map +1 -0
- package/dist/src/images/preview.js +20 -0
- package/dist/src/images/preview.js.map +1 -0
- package/dist/src/images/resize.js +135 -0
- package/dist/src/images/resize.js.map +1 -0
- package/dist/src/install/opencode.js +138 -0
- package/dist/src/install/opencode.js.map +1 -0
- package/dist/src/install/skill.js +128 -0
- package/dist/src/install/skill.js.map +1 -0
- package/dist/src/install/snippets.js +127 -0
- package/dist/src/install/snippets.js.map +1 -0
- package/dist/src/log.js +58 -0
- package/dist/src/log.js.map +1 -0
- package/dist/src/remove-background.js +101 -0
- package/dist/src/remove-background.js.map +1 -0
- package/dist/src/server/context.js +2 -0
- package/dist/src/server/context.js.map +1 -0
- package/dist/src/server/index.js +94 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/server/instructions.js +20 -0
- package/dist/src/server/instructions.js.map +1 -0
- package/dist/src/server/login-coordinator.js +73 -0
- package/dist/src/server/login-coordinator.js.map +1 -0
- package/dist/src/server/progress.js +33 -0
- package/dist/src/server/progress.js.map +1 -0
- package/dist/src/server/prompts.js +45 -0
- package/dist/src/server/prompts.js.map +1 -0
- package/dist/src/server/resources.js +73 -0
- package/dist/src/server/resources.js.map +1 -0
- package/dist/src/server/tools.js +338 -0
- package/dist/src/server/tools.js.map +1 -0
- package/dist/src/server/workspace.js +45 -0
- package/dist/src/server/workspace.js.map +1 -0
- package/dist/src/status.js +86 -0
- package/dist/src/status.js.map +1 -0
- package/dist/src/util/format.js +36 -0
- package/dist/src/util/format.js.map +1 -0
- package/dist/src/util/fs.js +117 -0
- package/dist/src/util/fs.js.map +1 -0
- package/dist/src/util/http.js +65 -0
- package/dist/src/util/http.js.map +1 -0
- package/dist/src/util/invocation.js +43 -0
- package/dist/src/util/invocation.js.map +1 -0
- package/dist/src/util/open.js +41 -0
- package/dist/src/util/open.js.map +1 -0
- package/docs/ARCHITECTURE.md +123 -0
- package/docs/AUTH.md +242 -0
- package/docs/BACKEND.md +169 -0
- package/docs/CLIENTS.md +154 -0
- package/docs/DEVELOPMENT.md +171 -0
- package/docs/README.md +84 -0
- package/docs/TOOLS.md +221 -0
- package/package.json +67 -0
- package/skill/imagegen/LICENSE.txt +201 -0
- package/skill/imagegen/SKILL.md +189 -0
- package/skill/imagegen/references/prompting.md +112 -0
- package/skill/imagegen/references/sample-prompts.md +418 -0
- package/skill/imagegen/references/tools.md +90 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { DEVICE_CODE_TIMEOUT_MS, DEVICE_VERIFICATION_PATH, OAUTH_SCOPES } from "../constants.js";
|
|
2
|
+
import { ImagegenError, toImagegenError } from "../errors.js";
|
|
3
|
+
import { isRecord, num, readBody, sleep, snippet, str } from "../util/http.js";
|
|
4
|
+
function doFetch(client) {
|
|
5
|
+
return client.fetchImpl ?? fetch;
|
|
6
|
+
}
|
|
7
|
+
function uaHeaders(client) {
|
|
8
|
+
return client.userAgent ? { "user-agent": client.userAgent } : {};
|
|
9
|
+
}
|
|
10
|
+
/** Build the /oauth/authorize URL with the same parameters (and order) Codex sends. */
|
|
11
|
+
export function buildAuthorizeUrl(p) {
|
|
12
|
+
const q = new URLSearchParams();
|
|
13
|
+
q.set("response_type", "code");
|
|
14
|
+
q.set("client_id", p.clientId);
|
|
15
|
+
q.set("redirect_uri", p.redirectUri);
|
|
16
|
+
q.set("scope", p.scopes ?? OAUTH_SCOPES);
|
|
17
|
+
q.set("code_challenge", p.codeChallenge);
|
|
18
|
+
q.set("code_challenge_method", "S256");
|
|
19
|
+
q.set("id_token_add_organizations", "true");
|
|
20
|
+
q.set("codex_cli_simplified_flow", "true");
|
|
21
|
+
q.set("state", p.state);
|
|
22
|
+
q.set("originator", p.originator);
|
|
23
|
+
// URLSearchParams encodes spaces as '+'; Codex (and RFC 3986 style) uses %20.
|
|
24
|
+
return `${p.issuer}/oauth/authorize?${q.toString().replace(/\+/g, "%20")}`;
|
|
25
|
+
}
|
|
26
|
+
/** Extract a readable message from an OAuth error body. */
|
|
27
|
+
export function describeOAuthError(json, text) {
|
|
28
|
+
if (isRecord(json)) {
|
|
29
|
+
const desc = str(json.error_description);
|
|
30
|
+
if (desc)
|
|
31
|
+
return desc;
|
|
32
|
+
if (isRecord(json.error)) {
|
|
33
|
+
const m = str(json.error.message) ?? str(json.error.code);
|
|
34
|
+
if (m)
|
|
35
|
+
return m;
|
|
36
|
+
}
|
|
37
|
+
const e = str(json.error) ?? str(json.message) ?? str(json.detail);
|
|
38
|
+
if (e)
|
|
39
|
+
return e;
|
|
40
|
+
}
|
|
41
|
+
return snippet(text) || "no details";
|
|
42
|
+
}
|
|
43
|
+
/** OAuth error code: `error.code` (object form), then `error` (string form), then top-level `code`. */
|
|
44
|
+
export function oauthErrorCode(json) {
|
|
45
|
+
if (!isRecord(json))
|
|
46
|
+
return undefined;
|
|
47
|
+
if (isRecord(json.error))
|
|
48
|
+
return str(json.error.code);
|
|
49
|
+
return str(json.error) ?? str(json.code);
|
|
50
|
+
}
|
|
51
|
+
function parseTokenResponse(json) {
|
|
52
|
+
if (!isRecord(json))
|
|
53
|
+
return {};
|
|
54
|
+
const out = {};
|
|
55
|
+
const id = str(json.id_token);
|
|
56
|
+
if (id)
|
|
57
|
+
out.idToken = id;
|
|
58
|
+
const access = str(json.access_token);
|
|
59
|
+
if (access)
|
|
60
|
+
out.accessToken = access;
|
|
61
|
+
const refresh = str(json.refresh_token);
|
|
62
|
+
if (refresh)
|
|
63
|
+
out.refreshToken = refresh;
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
/** Exchange an authorization code for tokens (form-encoded, as Codex does). */
|
|
67
|
+
export async function exchangeAuthorizationCode(client, p) {
|
|
68
|
+
const body = new URLSearchParams({
|
|
69
|
+
grant_type: "authorization_code",
|
|
70
|
+
code: p.code,
|
|
71
|
+
redirect_uri: p.redirectUri,
|
|
72
|
+
client_id: client.clientId,
|
|
73
|
+
code_verifier: p.codeVerifier,
|
|
74
|
+
});
|
|
75
|
+
let res;
|
|
76
|
+
try {
|
|
77
|
+
res = await doFetch(client)(`${client.issuer}/oauth/token`, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json", ...uaHeaders(client) },
|
|
80
|
+
body: body.toString(),
|
|
81
|
+
signal: p.signal ?? AbortSignal.timeout(60_000),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
throw toImagegenError(err, "network");
|
|
86
|
+
}
|
|
87
|
+
const { json, text } = await readBody(res);
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
throw new ImagegenError("login_failed", `Token exchange failed (HTTP ${res.status}): ${describeOAuthError(json, text)}`, {
|
|
90
|
+
details: { status: res.status, code: oauthErrorCode(json) },
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const tokens = parseTokenResponse(json);
|
|
94
|
+
if (!tokens.accessToken || !tokens.refreshToken) {
|
|
95
|
+
throw new ImagegenError("login_failed", "Token exchange succeeded but the response is missing tokens.");
|
|
96
|
+
}
|
|
97
|
+
const out = { accessToken: tokens.accessToken, refreshToken: tokens.refreshToken };
|
|
98
|
+
if (tokens.idToken)
|
|
99
|
+
out.idToken = tokens.idToken;
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
const PERMANENT_REFRESH_MESSAGES = {
|
|
103
|
+
refresh_token_expired: "Your sign-in has expired.",
|
|
104
|
+
refresh_token_reused: "Your sign-in was invalidated because its refresh token was already used (another process or machine refreshed it first).",
|
|
105
|
+
refresh_token_invalidated: "Your sign-in was revoked.",
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Refresh tokens. OpenAI rotates refresh tokens (they are single-use), so callers MUST persist the
|
|
109
|
+
* returned refresh token and serialize refreshes across processes.
|
|
110
|
+
* Throws `session_expired` for permanent failures and a retryable error otherwise.
|
|
111
|
+
*/
|
|
112
|
+
export async function refreshAccessToken(client, p) {
|
|
113
|
+
let res;
|
|
114
|
+
try {
|
|
115
|
+
res = await doFetch(client)(`${client.issuer}/oauth/token`, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: {
|
|
118
|
+
"content-type": "application/json",
|
|
119
|
+
accept: "application/json",
|
|
120
|
+
...uaHeaders(client),
|
|
121
|
+
...(p.originator ? { originator: p.originator } : {}),
|
|
122
|
+
},
|
|
123
|
+
body: JSON.stringify({ client_id: client.clientId, grant_type: "refresh_token", refresh_token: p.refreshToken }),
|
|
124
|
+
signal: p.signal ?? AbortSignal.timeout(60_000),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
throw toImagegenError(err, "network");
|
|
129
|
+
}
|
|
130
|
+
const { json, text } = await readBody(res);
|
|
131
|
+
if (!res.ok) {
|
|
132
|
+
const code = oauthErrorCode(json);
|
|
133
|
+
const known = code ? PERMANENT_REFRESH_MESSAGES[code] : undefined;
|
|
134
|
+
const permanent = known !== undefined || res.status === 401 || (res.status === 400 && code === "invalid_grant");
|
|
135
|
+
if (permanent) {
|
|
136
|
+
throw new ImagegenError("session_expired", `${known ?? `Refreshing your sign-in failed (HTTP ${res.status}: ${describeOAuthError(json, text)}).`} Please sign in again.`, {
|
|
137
|
+
details: { status: res.status, code, permanent: true },
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
throw new ImagegenError("server_error", `Token refresh failed (HTTP ${res.status}): ${describeOAuthError(json, text)}`, {
|
|
141
|
+
details: { status: res.status, code },
|
|
142
|
+
retryable: true,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const tokens = parseTokenResponse(json);
|
|
146
|
+
if (!tokens.accessToken)
|
|
147
|
+
throw new ImagegenError("server_error", "Token refresh response did not include an access token.", { retryable: true });
|
|
148
|
+
return tokens;
|
|
149
|
+
}
|
|
150
|
+
/** Revoke a token (best effort). Returns whether the server acknowledged it. */
|
|
151
|
+
export async function revokeToken(client, token, hint) {
|
|
152
|
+
try {
|
|
153
|
+
const body = { token, token_type_hint: hint };
|
|
154
|
+
if (hint === "refresh_token")
|
|
155
|
+
body.client_id = client.clientId;
|
|
156
|
+
const res = await doFetch(client)(`${client.issuer}/oauth/revoke`, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: { "content-type": "application/json", accept: "application/json", ...uaHeaders(client) },
|
|
159
|
+
body: JSON.stringify(body),
|
|
160
|
+
signal: AbortSignal.timeout(10_000),
|
|
161
|
+
});
|
|
162
|
+
return res.ok;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export async function requestDeviceCode(client, signal) {
|
|
169
|
+
let res;
|
|
170
|
+
try {
|
|
171
|
+
res = await doFetch(client)(`${client.issuer}/api/accounts/deviceauth/usercode`, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers: { "content-type": "application/json", accept: "application/json", ...uaHeaders(client) },
|
|
174
|
+
body: JSON.stringify({ client_id: client.clientId }),
|
|
175
|
+
signal: signal ?? AbortSignal.timeout(30_000),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
throw toImagegenError(err, "network");
|
|
180
|
+
}
|
|
181
|
+
const { json, text } = await readBody(res);
|
|
182
|
+
if (res.status === 404) {
|
|
183
|
+
throw new ImagegenError("login_failed", "Device-code sign-in is not enabled for this account. Enable it in ChatGPT → Settings → Security (device code authorization for Codex), or use the browser sign-in instead.", { details: { status: 404 } });
|
|
184
|
+
}
|
|
185
|
+
if (!res.ok || !isRecord(json)) {
|
|
186
|
+
throw new ImagegenError("login_failed", `Could not start device-code sign-in (HTTP ${res.status}): ${describeOAuthError(json, text)}`, {
|
|
187
|
+
details: { status: res.status },
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const deviceAuthId = str(json.device_auth_id);
|
|
191
|
+
const userCode = str(json.user_code) ?? str(json.usercode);
|
|
192
|
+
if (!deviceAuthId || !userCode)
|
|
193
|
+
throw new ImagegenError("login_failed", "Device-code response is missing the device id or user code.");
|
|
194
|
+
const intervalSeconds = num(json.interval) ?? 5;
|
|
195
|
+
return {
|
|
196
|
+
deviceAuthId,
|
|
197
|
+
userCode,
|
|
198
|
+
intervalMs: Math.max(1, intervalSeconds) * 1000,
|
|
199
|
+
verificationUrl: `${client.issuer}${DEVICE_VERIFICATION_PATH}`,
|
|
200
|
+
expiresAt: Date.now() + DEVICE_CODE_TIMEOUT_MS,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/** Poll until the user approves (403/404 = still pending). */
|
|
204
|
+
export async function pollDeviceAuthorization(client, device, signal) {
|
|
205
|
+
for (;;) {
|
|
206
|
+
if (Date.now() > device.expiresAt) {
|
|
207
|
+
throw new ImagegenError("login_failed", "Device-code sign-in timed out after 15 minutes. Start again.");
|
|
208
|
+
}
|
|
209
|
+
let res;
|
|
210
|
+
try {
|
|
211
|
+
res = await doFetch(client)(`${client.issuer}/api/accounts/deviceauth/token`, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: { "content-type": "application/json", accept: "application/json", ...uaHeaders(client) },
|
|
214
|
+
body: JSON.stringify({ device_auth_id: device.deviceAuthId, user_code: device.userCode }),
|
|
215
|
+
signal: signal ?? AbortSignal.timeout(30_000),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
const e = toImagegenError(err, "network");
|
|
220
|
+
if (e.kind === "cancelled")
|
|
221
|
+
throw new ImagegenError("login_cancelled", "Device-code sign-in was cancelled.");
|
|
222
|
+
// Transient network trouble while polling: keep going until the code expires.
|
|
223
|
+
await sleep(device.intervalMs, signal);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const { json, text } = await readBody(res);
|
|
227
|
+
if (res.ok && isRecord(json)) {
|
|
228
|
+
const authorizationCode = str(json.authorization_code);
|
|
229
|
+
const codeVerifier = str(json.code_verifier);
|
|
230
|
+
if (!authorizationCode || !codeVerifier)
|
|
231
|
+
throw new ImagegenError("login_failed", "Device authorization response is missing the authorization code.");
|
|
232
|
+
return { authorizationCode, codeVerifier };
|
|
233
|
+
}
|
|
234
|
+
if (res.status === 403 || res.status === 404) {
|
|
235
|
+
await sleep(Math.min(device.intervalMs, Math.max(0, device.expiresAt - Date.now())), signal);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
throw new ImagegenError("login_failed", `Device-code sign-in failed (HTTP ${res.status}): ${describeOAuthError(json, text)}`, {
|
|
239
|
+
details: { status: res.status },
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
export function deviceRedirectUri(issuer) {
|
|
244
|
+
return `${issuer}/deviceauth/callback`;
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/auth/oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function escapeHtml(s) {
|
|
2
|
+
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c] ?? c);
|
|
3
|
+
}
|
|
4
|
+
function page(title, heading, body, ok) {
|
|
5
|
+
const accent = ok ? "#10a37f" : "#d9534f";
|
|
6
|
+
return `<!doctype html>
|
|
7
|
+
<html lang="en">
|
|
8
|
+
<head>
|
|
9
|
+
<meta charset="utf-8">
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
11
|
+
<title>${escapeHtml(title)}</title>
|
|
12
|
+
<style>
|
|
13
|
+
:root { color-scheme: light dark; }
|
|
14
|
+
body { margin: 0; min-height: 100vh; display: grid; place-items: center;
|
|
15
|
+
font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
16
|
+
background: Canvas; color: CanvasText; }
|
|
17
|
+
main { max-width: 34rem; padding: 2.5rem; border-radius: 16px; border: 1px solid color-mix(in srgb, CanvasText 15%, transparent); }
|
|
18
|
+
.badge { width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center;
|
|
19
|
+
background: ${accent}; color: white; font-size: 24px; margin-bottom: 1rem; }
|
|
20
|
+
h1 { font-size: 1.35rem; margin: 0 0 .5rem; }
|
|
21
|
+
p { margin: .5rem 0; opacity: .85; }
|
|
22
|
+
code { font-size: .9em; }
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
<body><main>
|
|
26
|
+
<div class="badge">${ok ? "✓" : "!"}</div>
|
|
27
|
+
<h1>${escapeHtml(heading)}</h1>
|
|
28
|
+
${body}
|
|
29
|
+
</main></body>
|
|
30
|
+
</html>`;
|
|
31
|
+
}
|
|
32
|
+
export function successPage(email) {
|
|
33
|
+
const who = email ? `<p>Signed in as <strong>${escapeHtml(email)}</strong>.</p>` : "";
|
|
34
|
+
return page("Signed in — Codex ImageGen MCP", "You're signed in to Codex ImageGen MCP", `${who}<p>Image generation will use your ChatGPT plan. You can close this tab and return to your coding tool.</p>`, true);
|
|
35
|
+
}
|
|
36
|
+
export function errorPage(message) {
|
|
37
|
+
return page("Sign-in failed — Codex ImageGen MCP", "Sign-in failed", `<p>${escapeHtml(message)}</p><p>Return to your terminal or coding tool and try again.</p>`, false);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=pages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages.js","sourceRoot":"","sources":["../../../src/auth/pages.ts"],"names":[],"mappings":"AAAA,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACzH,CAAC;AAED,SAAS,IAAI,CAAC,KAAa,EAAE,OAAe,EAAE,IAAY,EAAE,EAAW;IACrE,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1C,OAAO;;;;;SAKA,UAAU,CAAC,KAAK,CAAC;;;;;;;;yBAQD,MAAM;;;;;;;uBAOR,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG;QACpC,UAAU,CAAC,OAAO,CAAC;IACvB,IAAI;;QAEA,CAAC;AACT,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAyB;IACnD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,2BAA2B,UAAU,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;IACtF,OAAO,IAAI,CACT,gCAAgC,EAChC,wCAAwC,EACxC,GAAG,GAAG,4GAA4G,EAClH,IAAI,CACL,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,IAAI,CACT,qCAAqC,EACrC,gBAAgB,EAChB,MAAM,UAAU,CAAC,OAAO,CAAC,kEAAkE,EAC3F,KAAK,CACN,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
/** RFC 7636 S256 pair: 64 random bytes -> base64url verifier (86 chars), SHA-256 -> challenge. */
|
|
3
|
+
export function createPkce() {
|
|
4
|
+
const verifier = randomBytes(64).toString("base64url");
|
|
5
|
+
return { verifier, challenge: pkceChallenge(verifier) };
|
|
6
|
+
}
|
|
7
|
+
export function pkceChallenge(verifier) {
|
|
8
|
+
return createHash("sha256").update(verifier, "ascii").digest("base64url");
|
|
9
|
+
}
|
|
10
|
+
/** Opaque CSRF state for the authorize round-trip. */
|
|
11
|
+
export function createState() {
|
|
12
|
+
return randomBytes(32).toString("base64url");
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../src/auth/pkce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAOtD,kGAAkG;AAClG,MAAM,UAAU,UAAU;IACxB,MAAM,QAAQ,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACvD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAC5E,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,WAAW;IACzB,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { ImagegenError } from "../errors.js";
|
|
3
|
+
import { readJsonFile, writeFileAtomic } from "../util/fs.js";
|
|
4
|
+
import { isRecord, str } from "../util/http.js";
|
|
5
|
+
import { identityFromTokens } from "./jwt.js";
|
|
6
|
+
export function buildStoredAuth(tokens, method, now = new Date()) {
|
|
7
|
+
const identity = identityFromTokens(tokens.idToken, tokens.accessToken);
|
|
8
|
+
const stored = {
|
|
9
|
+
version: 1,
|
|
10
|
+
auth_mode: "chatgpt",
|
|
11
|
+
tokens: {
|
|
12
|
+
access_token: tokens.accessToken,
|
|
13
|
+
refresh_token: tokens.refreshToken,
|
|
14
|
+
},
|
|
15
|
+
last_refresh: now.toISOString(),
|
|
16
|
+
login_method: method,
|
|
17
|
+
created_at: now.toISOString(),
|
|
18
|
+
};
|
|
19
|
+
if (tokens.idToken)
|
|
20
|
+
stored.tokens.id_token = tokens.idToken;
|
|
21
|
+
if (identity.accountId)
|
|
22
|
+
stored.tokens.account_id = identity.accountId;
|
|
23
|
+
return stored;
|
|
24
|
+
}
|
|
25
|
+
function corrupt(file, why) {
|
|
26
|
+
return new ImagegenError("auth_failed", `The saved credentials at ${file} are unreadable (${why}). Sign in again to replace them.`);
|
|
27
|
+
}
|
|
28
|
+
export async function readStoredAuth(file) {
|
|
29
|
+
let raw;
|
|
30
|
+
try {
|
|
31
|
+
raw = await readJsonFile(file);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
throw corrupt(file, err instanceof Error ? err.message : String(err));
|
|
35
|
+
}
|
|
36
|
+
if (raw === undefined)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (!isRecord(raw) || !isRecord(raw.tokens))
|
|
39
|
+
throw corrupt(file, "missing tokens");
|
|
40
|
+
const accessToken = str(raw.tokens.access_token);
|
|
41
|
+
const refreshToken = str(raw.tokens.refresh_token);
|
|
42
|
+
if (!accessToken || !refreshToken)
|
|
43
|
+
throw corrupt(file, "missing access or refresh token");
|
|
44
|
+
const stored = {
|
|
45
|
+
version: 1,
|
|
46
|
+
auth_mode: "chatgpt",
|
|
47
|
+
tokens: { access_token: accessToken, refresh_token: refreshToken },
|
|
48
|
+
last_refresh: str(raw.last_refresh) ?? new Date(0).toISOString(),
|
|
49
|
+
login_method: raw.login_method === "device" ? "device" : "browser",
|
|
50
|
+
created_at: str(raw.created_at) ?? str(raw.last_refresh) ?? new Date(0).toISOString(),
|
|
51
|
+
};
|
|
52
|
+
const idToken = str(raw.tokens.id_token);
|
|
53
|
+
if (idToken)
|
|
54
|
+
stored.tokens.id_token = idToken;
|
|
55
|
+
const accountId = str(raw.tokens.account_id) ?? identityFromTokens(idToken, accessToken).accountId;
|
|
56
|
+
if (accountId)
|
|
57
|
+
stored.tokens.account_id = accountId;
|
|
58
|
+
return stored;
|
|
59
|
+
}
|
|
60
|
+
export async function writeStoredAuth(file, auth) {
|
|
61
|
+
await writeFileAtomic(file, `${JSON.stringify(auth, null, 2)}\n`, 0o600);
|
|
62
|
+
}
|
|
63
|
+
/** Delete the credential file. Returns whether a file was removed. */
|
|
64
|
+
export async function deleteStoredAuth(file) {
|
|
65
|
+
try {
|
|
66
|
+
await fs.rm(file);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
if (err.code === "ENOENT")
|
|
71
|
+
return false;
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/auth/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { SOURCE_LABELS } from "../auth/manager.js";
|
|
2
|
+
import { IMAGE_MODEL, MAX_INPUT_IMAGES, MAX_OUTPUT_IMAGE_BYTES } from "../constants.js";
|
|
3
|
+
import { ImagegenError, loginHint, toImagegenError } from "../errors.js";
|
|
4
|
+
import { sniffImageMime } from "../images/codec.js";
|
|
5
|
+
import { formatWhen } from "../util/format.js";
|
|
6
|
+
import { anySignal, isRecord, num, readBody, sleep, snippet, str } from "../util/http.js";
|
|
7
|
+
import { exhaustedResetAt, parseRateLimitHeaders, parseUsageResponse } from "./ratelimits.js";
|
|
8
|
+
const DEFAULT_RETRY_DELAYS = [1_000, 3_000];
|
|
9
|
+
/**
|
|
10
|
+
* Client for the ChatGPT backend image endpoints used by the Codex built-in `image_gen` tool:
|
|
11
|
+
* POST {base}/images/generations {prompt, background, model, quality, size}
|
|
12
|
+
* POST {base}/images/edits {…, images: [{image_url: data-url}]} (max 5 images)
|
|
13
|
+
* Auth: `Authorization: Bearer <ChatGPT access token>` + `ChatGPT-Account-ID`.
|
|
14
|
+
*/
|
|
15
|
+
export class ImagesClient {
|
|
16
|
+
auth;
|
|
17
|
+
o;
|
|
18
|
+
constructor(auth, o) {
|
|
19
|
+
this.auth = auth;
|
|
20
|
+
this.o = o;
|
|
21
|
+
}
|
|
22
|
+
headers(creds) {
|
|
23
|
+
const h = {
|
|
24
|
+
authorization: `Bearer ${creds.accessToken}`,
|
|
25
|
+
originator: this.o.originator,
|
|
26
|
+
"user-agent": this.o.userAgent,
|
|
27
|
+
accept: "application/json",
|
|
28
|
+
};
|
|
29
|
+
if (creds.accountId)
|
|
30
|
+
h["chatgpt-account-id"] = creds.accountId;
|
|
31
|
+
if (creds.identity.isFedramp)
|
|
32
|
+
h["x-openai-fedramp"] = "true";
|
|
33
|
+
return h;
|
|
34
|
+
}
|
|
35
|
+
async createImage(req, options = {}) {
|
|
36
|
+
const images = req.images ?? [];
|
|
37
|
+
if (images.length > MAX_INPUT_IMAGES) {
|
|
38
|
+
throw new ImagegenError("invalid_input", `At most ${MAX_INPUT_IMAGES} input images are supported (got ${images.length}).`);
|
|
39
|
+
}
|
|
40
|
+
const endpoint = images.length > 0 ? "images/edits" : "images/generations";
|
|
41
|
+
// Same body the Codex built-in tool sends. The service picks model/quality/size itself;
|
|
42
|
+
// only `prompt`, `background` and `images` influence the result.
|
|
43
|
+
const body = {
|
|
44
|
+
prompt: req.prompt,
|
|
45
|
+
background: req.background,
|
|
46
|
+
model: IMAGE_MODEL,
|
|
47
|
+
quality: "auto",
|
|
48
|
+
size: "auto",
|
|
49
|
+
};
|
|
50
|
+
if (images.length > 0)
|
|
51
|
+
body.images = images.map((i) => ({ image_url: i.dataUrl }));
|
|
52
|
+
const payload = JSON.stringify(body);
|
|
53
|
+
const { res, creds, started } = await this.send(`${this.o.baseUrl}/${endpoint}`, "POST", payload, options.signal);
|
|
54
|
+
const requestId = requestIdOf(res.headers);
|
|
55
|
+
const rateLimits = parseRateLimitHeaders(res.headers);
|
|
56
|
+
const { json, text } = await readBody(res);
|
|
57
|
+
const first = isRecord(json) && Array.isArray(json.data) ? json.data[0] : undefined;
|
|
58
|
+
const b64 = isRecord(first) ? str(first.b64_json) : undefined;
|
|
59
|
+
if (!b64) {
|
|
60
|
+
throw new ImagegenError("server_error", `The image service returned no image data${text ? `: ${snippet(text, 200)}` : "."}`, {
|
|
61
|
+
details: { requestId },
|
|
62
|
+
retryable: true,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const bytes = Buffer.from(b64, "base64");
|
|
66
|
+
if (bytes.length === 0 || bytes.length > MAX_OUTPUT_IMAGE_BYTES) {
|
|
67
|
+
throw new ImagegenError("server_error", `The image service returned an invalid image (${bytes.length} bytes).`, { details: { requestId } });
|
|
68
|
+
}
|
|
69
|
+
const result = {
|
|
70
|
+
bytes,
|
|
71
|
+
mimeType: sniffImageMime(bytes) ?? "image/png",
|
|
72
|
+
elapsedMs: Date.now() - started,
|
|
73
|
+
credentialSource: creds.source,
|
|
74
|
+
};
|
|
75
|
+
const meta = isRecord(json) ? json : {};
|
|
76
|
+
const background = str(meta.background);
|
|
77
|
+
if (background)
|
|
78
|
+
result.background = background;
|
|
79
|
+
const size = str(meta.size);
|
|
80
|
+
if (size)
|
|
81
|
+
result.size = size;
|
|
82
|
+
const quality = str(meta.quality);
|
|
83
|
+
if (quality)
|
|
84
|
+
result.quality = quality;
|
|
85
|
+
const format = str(meta.output_format);
|
|
86
|
+
if (format)
|
|
87
|
+
result.outputFormat = format;
|
|
88
|
+
if (isRecord(first)) {
|
|
89
|
+
const gen = str(first.generation_id);
|
|
90
|
+
if (gen)
|
|
91
|
+
result.generationId = gen;
|
|
92
|
+
}
|
|
93
|
+
if (requestId)
|
|
94
|
+
result.requestId = requestId;
|
|
95
|
+
if (isRecord(meta.usage))
|
|
96
|
+
result.usage = meta.usage;
|
|
97
|
+
if (rateLimits)
|
|
98
|
+
result.rateLimits = rateLimits;
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
/** Current plan + quota windows (does not consume any quota). */
|
|
102
|
+
async getUsage(options = {}) {
|
|
103
|
+
const { res, creds } = await this.send(this.o.usageUrl, "GET", undefined, options.signal);
|
|
104
|
+
const { json } = await readBody(res);
|
|
105
|
+
return { ...parseUsageResponse(json), credentialSource: creds.source };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Send with auth, a per-attempt timeout, one 401 recovery (refresh / re-read / next source) and
|
|
109
|
+
* bounded retries for transient failures. Returns only successful responses.
|
|
110
|
+
*/
|
|
111
|
+
async send(url, method, payload, signal) {
|
|
112
|
+
const fetchImpl = this.o.fetchImpl ?? fetch;
|
|
113
|
+
const maxRetries = this.o.maxRetries ?? 2;
|
|
114
|
+
const delays = this.o.retryDelaysMs ?? DEFAULT_RETRY_DELAYS;
|
|
115
|
+
let creds = await this.auth.getCredentials();
|
|
116
|
+
let authRecovered = false;
|
|
117
|
+
let attempt = 0;
|
|
118
|
+
for (;;) {
|
|
119
|
+
const started = Date.now();
|
|
120
|
+
let res;
|
|
121
|
+
try {
|
|
122
|
+
const headers = this.headers(creds);
|
|
123
|
+
if (payload !== undefined)
|
|
124
|
+
headers["content-type"] = "application/json";
|
|
125
|
+
const init = { method, headers, signal: anySignal(signal, AbortSignal.timeout(this.o.timeoutMs)) ?? null };
|
|
126
|
+
if (payload !== undefined)
|
|
127
|
+
init.body = payload;
|
|
128
|
+
res = await fetchImpl(url, init);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
const e = signal?.aborted ? new ImagegenError("cancelled", "The request was cancelled.") : toImagegenError(err, "network");
|
|
132
|
+
if (e.retryable && attempt < maxRetries) {
|
|
133
|
+
this.o.logger.warn("request failed; retrying", { url, attempt, message: e.message });
|
|
134
|
+
await sleep(delays[Math.min(attempt, delays.length - 1)] ?? 1_000, signal);
|
|
135
|
+
attempt++;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
throw e;
|
|
139
|
+
}
|
|
140
|
+
if (res.ok)
|
|
141
|
+
return { res, creds, started };
|
|
142
|
+
const error = await errorFromResponse(res, creds);
|
|
143
|
+
if (res.status === 401 && !authRecovered) {
|
|
144
|
+
authRecovered = true;
|
|
145
|
+
const next = await this.auth.recoverFromUnauthorized(creds);
|
|
146
|
+
if (next) {
|
|
147
|
+
this.o.logger.info("retrying with recovered credentials", { from: creds.source, to: next.source });
|
|
148
|
+
creds = next;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (error.retryable && attempt < maxRetries) {
|
|
153
|
+
this.o.logger.warn("transient backend error; retrying", { url, status: res.status, attempt });
|
|
154
|
+
await sleep(delays[Math.min(attempt, delays.length - 1)] ?? 1_000, signal);
|
|
155
|
+
attempt++;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
this.o.logger.warn("backend request failed", { url, status: res.status, kind: error.kind, requestId: error.details.requestId });
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function requestIdOf(headers) {
|
|
164
|
+
return headers.get("x-codex-imagegen-request-id") ?? headers.get("x-oai-request-id") ?? headers.get("x-request-id") ?? undefined;
|
|
165
|
+
}
|
|
166
|
+
const POLICY_CODES = new Set(["moderation_blocked", "content_policy_violation", "safety_violation", "image_generation_user_error_moderation"]);
|
|
167
|
+
/** Map a non-2xx backend response onto an actionable ImagegenError. */
|
|
168
|
+
export async function errorFromResponse(res, creds) {
|
|
169
|
+
const { json, text } = await readBody(res);
|
|
170
|
+
const status = res.status;
|
|
171
|
+
const err = isRecord(json) && isRecord(json.error) ? json.error : undefined;
|
|
172
|
+
const type = str(err?.type);
|
|
173
|
+
const code = str(err?.code);
|
|
174
|
+
const detail = isRecord(json) ? (str(json.detail) ?? (isRecord(json.detail) ? str(json.detail.message) : undefined)) : undefined;
|
|
175
|
+
const message = str(err?.message) ?? detail ?? (isRecord(json) ? str(json.message) : undefined);
|
|
176
|
+
const requestId = requestIdOf(res.headers);
|
|
177
|
+
const rateLimits = parseRateLimitHeaders(res.headers);
|
|
178
|
+
const details = { status, type, code, requestId, cfRay: res.headers.get("cf-ray") ?? undefined };
|
|
179
|
+
const isCloudflare = /cloudflare|cf-chl|just a moment/i.test(text) && !json;
|
|
180
|
+
if (status === 401) {
|
|
181
|
+
return new ImagegenError("auth_failed", `ChatGPT rejected the ${SOURCE_LABELS[creds.source]} (HTTP 401${message ? `: ${message}` : ""}). ${loginHint()}`, { details });
|
|
182
|
+
}
|
|
183
|
+
if (status === 403) {
|
|
184
|
+
if (isCloudflare) {
|
|
185
|
+
return new ImagegenError("blocked", "The request was blocked by Cloudflare before reaching ChatGPT (HTTP 403).", { details });
|
|
186
|
+
}
|
|
187
|
+
return new ImagegenError("auth_failed", `Access denied (HTTP 403${message ? `: ${message}` : ""}). Your ChatGPT plan or workspace may not include Codex image generation (it is not available on the Free plan).`, { details });
|
|
188
|
+
}
|
|
189
|
+
if (status === 429) {
|
|
190
|
+
const resetsAt = num(err?.resets_at) ?? exhaustedResetAt(rateLimits);
|
|
191
|
+
const plan = str(err?.plan_type) ?? rateLimits?.planType ?? creds.identity.planType;
|
|
192
|
+
if (resetsAt !== undefined)
|
|
193
|
+
details.resetsAt = resetsAt;
|
|
194
|
+
if (type === "usage_not_included") {
|
|
195
|
+
return new ImagegenError("usage_limit", `Your ChatGPT${plan ? ` ${plan}` : ""} plan does not include Codex image generation.`, { details });
|
|
196
|
+
}
|
|
197
|
+
const when = resetsAt !== undefined ? ` It resets ${formatWhen(resetsAt * 1000)}.` : "";
|
|
198
|
+
const what = type === "usage_limit_reached" ? "You've hit your ChatGPT image-generation usage limit" : "Rate limited by ChatGPT (HTTP 429)";
|
|
199
|
+
return new ImagegenError("usage_limit", `${what}${plan ? ` (${plan} plan)` : ""}.${when}${message && type !== "usage_limit_reached" ? ` ${message}` : ""}`, {
|
|
200
|
+
details,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (status === 400 || status === 422) {
|
|
204
|
+
const policy = (code !== undefined && POLICY_CODES.has(code)) || /safety|moderation|content policy|policy violation|not allowed/i.test(message ?? "");
|
|
205
|
+
if (policy) {
|
|
206
|
+
return new ImagegenError("content_policy", `The request was rejected by OpenAI's safety system${message ? `: ${message}` : "."}`, { details });
|
|
207
|
+
}
|
|
208
|
+
return new ImagegenError("invalid_request", `The image service rejected the request (HTTP ${status})${message ? `: ${message}` : "."}`, { details });
|
|
209
|
+
}
|
|
210
|
+
if (status === 413) {
|
|
211
|
+
return new ImagegenError("invalid_input", "The request is too large — use fewer or smaller input images.", { details });
|
|
212
|
+
}
|
|
213
|
+
if (status >= 500) {
|
|
214
|
+
return new ImagegenError("server_error", `OpenAI's image service had a problem (HTTP ${status})${message ? `: ${message}` : "."}`, {
|
|
215
|
+
details,
|
|
216
|
+
retryable: true,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return new ImagegenError("internal", `Unexpected response from the image service (HTTP ${status})${message ? `: ${message}` : text ? `: ${snippet(text, 200)}` : "."}`, {
|
|
220
|
+
details,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=images-client.js.map
|