@kenkaiiii/gg-core 5.93.0 → 5.95.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 +11 -11
- package/dist/{chunk-2X5L6U4Z.js → chunk-GXOFTHM5.js} +4 -2
- package/dist/chunk-GXOFTHM5.js.map +1 -0
- package/dist/{chunk-SDUIKPGO.js → chunk-J7W5HS6X.js} +1191 -1189
- package/dist/chunk-J7W5HS6X.js.map +1 -0
- package/dist/index.cjs +1472 -1368
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +338 -187
- package/dist/index.d.ts +338 -187
- package/dist/index.js +319 -213
- package/dist/index.js.map +1 -1
- package/dist/model-registry.cjs +96 -77
- package/dist/model-registry.cjs.map +1 -1
- package/dist/model-registry.d.cts +11 -15
- package/dist/model-registry.d.ts +11 -15
- package/dist/model-registry.js +2 -2
- package/dist/paths.cjs +3 -1
- package/dist/paths.cjs.map +1 -1
- package/dist/paths.d.cts +4 -0
- package/dist/paths.d.ts +4 -0
- package/dist/paths.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-2X5L6U4Z.js.map +0 -1
- package/dist/chunk-SDUIKPGO.js.map +0 -1
|
@@ -1,1149 +1,959 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getAppPaths
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GXOFTHM5.js";
|
|
4
4
|
|
|
5
|
-
// src/
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// src/oauth/request-timeout.ts
|
|
13
|
-
var OAUTH_REQUEST_TIMEOUT_MS = 3e4;
|
|
14
|
-
function oauthRequestSignal() {
|
|
15
|
-
return AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// src/oauth/kimi.ts
|
|
19
|
-
var CLIENT_ID = "17e5f671-d194-4dfb-9706-5516cb48c098";
|
|
20
|
-
var DEFAULT_OAUTH_HOST = "https://auth.kimi.com";
|
|
21
|
-
var DEFAULT_CODING_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
22
|
-
var KIMI_PLATFORM = "kimi_code_cli";
|
|
23
|
-
var DEFAULT_KIMI_VERSION = "1.0.11";
|
|
24
|
-
var DEVICE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
25
|
-
function oauthHost() {
|
|
26
|
-
const host = process.env.KIMI_CODE_OAUTH_HOST ?? process.env.KIMI_OAUTH_HOST ?? DEFAULT_OAUTH_HOST;
|
|
27
|
-
return host.replace(/\/+$/, "");
|
|
28
|
-
}
|
|
29
|
-
function kimiCodeBaseUrl() {
|
|
30
|
-
return (process.env.KIMI_CODE_BASE_URL ?? DEFAULT_CODING_BASE_URL).replace(/\/+$/, "");
|
|
31
|
-
}
|
|
32
|
-
function kimiVersion() {
|
|
33
|
-
const v = process.env.KIMI_CODE_VERSION ?? DEFAULT_KIMI_VERSION;
|
|
34
|
-
return asciiHeader(v, DEFAULT_KIMI_VERSION);
|
|
35
|
-
}
|
|
36
|
-
function asciiHeader(value, fallback = "unknown") {
|
|
37
|
-
const cleaned = value.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
38
|
-
return cleaned.length > 0 ? cleaned : fallback;
|
|
39
|
-
}
|
|
40
|
-
function macOsProductVersion() {
|
|
41
|
-
try {
|
|
42
|
-
const version = execFileSync("/usr/bin/sw_vers", ["-productVersion"], {
|
|
43
|
-
encoding: "utf-8",
|
|
44
|
-
timeout: 1e3
|
|
45
|
-
}).trim();
|
|
46
|
-
return version.length > 0 ? version : void 0;
|
|
47
|
-
} catch {
|
|
48
|
-
return void 0;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function deviceModel() {
|
|
52
|
-
const os = type();
|
|
53
|
-
const version = release();
|
|
54
|
-
const osArch = arch();
|
|
55
|
-
if (os === "Darwin") return `macOS ${macOsProductVersion() ?? version} ${osArch}`;
|
|
56
|
-
if (os === "Windows_NT") return `Windows ${version} ${osArch}`;
|
|
57
|
-
return `${os} ${version} ${osArch}`.trim();
|
|
58
|
-
}
|
|
59
|
-
function deviceId() {
|
|
60
|
-
const idPath = path.join(getAppPaths().agentDir, "kimi_device_id");
|
|
61
|
-
if (existsSync(idPath)) {
|
|
62
|
-
try {
|
|
63
|
-
const text = readFileSync(idPath, "utf-8").trim();
|
|
64
|
-
if (text.length > 0) return text;
|
|
65
|
-
} catch {
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const id = randomUUID();
|
|
5
|
+
// src/kimi-region.ts
|
|
6
|
+
import { GG_CODER_USER_AGENT } from "@kenkaiiii/gg-ai";
|
|
7
|
+
var KIMI_CODE_DEFAULT_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
8
|
+
var KIMI_CODE_OVERSEAS_BASE_URL = "https://api.kimi.ai/coding/v1";
|
|
9
|
+
var KIMI_CODE_BASE_URLS = [KIMI_CODE_DEFAULT_BASE_URL, KIMI_CODE_OVERSEAS_BASE_URL];
|
|
10
|
+
async function probeHost(baseUrl, apiKey, fetchImpl, timeoutMs) {
|
|
69
11
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
12
|
+
const res = await fetchImpl(`${baseUrl}/models`, {
|
|
13
|
+
headers: { Authorization: `Bearer ${apiKey}`, "User-Agent": GG_CODER_USER_AGENT },
|
|
14
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
15
|
+
});
|
|
16
|
+
if (res.ok) return "authorized";
|
|
17
|
+
if (res.status === 401 || res.status === 403) return "unauthorized";
|
|
18
|
+
return "inconclusive";
|
|
72
19
|
} catch {
|
|
20
|
+
return "inconclusive";
|
|
73
21
|
}
|
|
74
|
-
return id;
|
|
75
22
|
}
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
function kimiCodingHeaders() {
|
|
87
|
-
return {
|
|
88
|
-
"User-Agent": `kimi-code-cli/${kimiVersion()}`,
|
|
89
|
-
...deviceHeaders()
|
|
90
|
-
};
|
|
23
|
+
async function detectKimiCodeBaseUrl(apiKey, options = {}) {
|
|
24
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
25
|
+
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
26
|
+
const results = await Promise.all(
|
|
27
|
+
KIMI_CODE_BASE_URLS.map((url) => probeHost(url, apiKey, fetchImpl, timeoutMs))
|
|
28
|
+
);
|
|
29
|
+
const index = results.indexOf("authorized");
|
|
30
|
+
return index === -1 ? void 0 : KIMI_CODE_BASE_URLS[index];
|
|
91
31
|
}
|
|
92
32
|
function isKimiCodingEndpoint(baseUrl) {
|
|
93
33
|
if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
|
|
94
|
-
|
|
95
|
-
return normalized === kimiCodeBaseUrl() || /(^|\.)kimi\.com/i.test(normalized);
|
|
96
|
-
}
|
|
97
|
-
async function postForm(endpoint, params) {
|
|
98
|
-
const response = await fetch(`${oauthHost()}${endpoint}`, {
|
|
99
|
-
method: "POST",
|
|
100
|
-
headers: {
|
|
101
|
-
...deviceHeaders(),
|
|
102
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
103
|
-
Accept: "application/json"
|
|
104
|
-
},
|
|
105
|
-
body: new URLSearchParams(params).toString(),
|
|
106
|
-
signal: oauthRequestSignal()
|
|
107
|
-
});
|
|
108
|
-
let data = {};
|
|
109
|
-
try {
|
|
110
|
-
const parsed = await response.json();
|
|
111
|
-
if (parsed && typeof parsed === "object") data = parsed;
|
|
112
|
-
} catch {
|
|
113
|
-
}
|
|
114
|
-
return { status: response.status, data };
|
|
115
|
-
}
|
|
116
|
-
function errorDetail(data) {
|
|
117
|
-
const desc = data.error_description ?? data.message ?? data.error;
|
|
118
|
-
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
119
|
-
}
|
|
120
|
-
function credsFromTokenResponse(data, opts) {
|
|
121
|
-
const accessToken = data.access_token;
|
|
122
|
-
const responseRefreshToken = data.refresh_token;
|
|
123
|
-
const expiresIn = Number(data.expires_in);
|
|
124
|
-
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
125
|
-
throw new Error("Kimi OAuth response missing access_token.");
|
|
126
|
-
}
|
|
127
|
-
const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
|
|
128
|
-
if (refreshToken.length === 0) {
|
|
129
|
-
throw new Error("Kimi OAuth response missing refresh_token.");
|
|
130
|
-
}
|
|
131
|
-
if (!Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
132
|
-
throw new Error("Kimi OAuth response missing or invalid expires_in.");
|
|
133
|
-
}
|
|
134
|
-
return {
|
|
135
|
-
accessToken,
|
|
136
|
-
refreshToken,
|
|
137
|
-
expiresAt: Date.now() + expiresIn * 1e3,
|
|
138
|
-
expiresIn,
|
|
139
|
-
baseUrl: kimiCodeBaseUrl()
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
async function requestDeviceAuthorization() {
|
|
143
|
-
const { status, data } = await postForm("/api/oauth/device_authorization", {
|
|
144
|
-
client_id: CLIENT_ID
|
|
145
|
-
});
|
|
146
|
-
if (status !== 200) {
|
|
147
|
-
throw new Error(`Kimi device authorization failed (${status}): ${errorDetail(data)}`);
|
|
148
|
-
}
|
|
149
|
-
const userCode = data.user_code;
|
|
150
|
-
const deviceCode = data.device_code;
|
|
151
|
-
const verificationUriComplete = data.verification_uri_complete;
|
|
152
|
-
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
153
|
-
throw new Error("Kimi device authorization response missing user_code/device_code.");
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
userCode,
|
|
157
|
-
deviceCode,
|
|
158
|
-
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
159
|
-
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
160
|
-
interval: Number(data.interval ?? 5) || 5
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
async function pollDeviceToken(deviceCode) {
|
|
164
|
-
const { status, data } = await postForm("/api/oauth/token", {
|
|
165
|
-
client_id: CLIENT_ID,
|
|
166
|
-
device_code: deviceCode,
|
|
167
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
168
|
-
});
|
|
169
|
-
if (status === 200 && typeof data.access_token === "string") {
|
|
170
|
-
return { kind: "success", creds: credsFromTokenResponse(data) };
|
|
171
|
-
}
|
|
172
|
-
if (status >= 500) {
|
|
173
|
-
throw new Error(`Kimi token polling server error (${status}): ${errorDetail(data)}`);
|
|
174
|
-
}
|
|
175
|
-
const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
|
|
176
|
-
switch (errorCode) {
|
|
177
|
-
case "authorization_pending":
|
|
178
|
-
return { kind: "pending" };
|
|
179
|
-
case "slow_down":
|
|
180
|
-
return { kind: "slow_down" };
|
|
181
|
-
case "expired_token":
|
|
182
|
-
return { kind: "expired" };
|
|
183
|
-
case "access_denied":
|
|
184
|
-
return { kind: "denied" };
|
|
185
|
-
default:
|
|
186
|
-
throw new Error(`Kimi token polling failed (${status}): ${errorDetail(data)}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
function sleep(ms) {
|
|
190
|
-
return new Promise((resolve) => {
|
|
191
|
-
setTimeout(resolve, ms);
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
async function loginKimi(callbacks) {
|
|
195
|
-
const auth = await requestDeviceAuthorization();
|
|
196
|
-
callbacks.onStatus(
|
|
197
|
-
`Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
|
|
198
|
-
);
|
|
199
|
-
callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
|
|
200
|
-
callbacks.onStatus("Waiting for you to authorize in the browser...");
|
|
201
|
-
const deadline = Date.now() + DEVICE_TIMEOUT_MS;
|
|
202
|
-
let interval = Math.max(auth.interval, 1);
|
|
203
|
-
while (Date.now() < deadline) {
|
|
204
|
-
await sleep(interval * 1e3);
|
|
205
|
-
const result = await pollDeviceToken(auth.deviceCode);
|
|
206
|
-
if (result.kind === "success") return result.creds;
|
|
207
|
-
if (result.kind === "denied") {
|
|
208
|
-
throw new Error("Kimi authorization was denied.");
|
|
209
|
-
}
|
|
210
|
-
if (result.kind === "expired") {
|
|
211
|
-
throw new Error("Kimi device code expired. Please run login again.");
|
|
212
|
-
}
|
|
213
|
-
if (result.kind === "slow_down") {
|
|
214
|
-
interval += 5;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
throw new Error("Kimi login timed out. Please run login again.");
|
|
218
|
-
}
|
|
219
|
-
async function refreshKimiToken(refreshToken) {
|
|
220
|
-
const { status, data } = await postForm("/api/oauth/token", {
|
|
221
|
-
client_id: CLIENT_ID,
|
|
222
|
-
grant_type: "refresh_token",
|
|
223
|
-
refresh_token: refreshToken
|
|
224
|
-
});
|
|
225
|
-
if (status === 200 && typeof data.access_token === "string") {
|
|
226
|
-
return credsFromTokenResponse(data, { fallbackRefreshToken: refreshToken });
|
|
227
|
-
}
|
|
228
|
-
const errorCode = typeof data.error === "string" ? data.error : "";
|
|
229
|
-
throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
|
|
34
|
+
return /^https:\/\/api\.kimi\.(com|ai)\/coding(\/|$)/i.test(baseUrl);
|
|
230
35
|
}
|
|
231
36
|
|
|
232
37
|
// src/auth-storage.ts
|
|
233
|
-
import
|
|
234
|
-
import { readFileSync
|
|
235
|
-
import
|
|
38
|
+
import fs6 from "fs/promises";
|
|
39
|
+
import { readFileSync } from "fs";
|
|
40
|
+
import crypto4 from "crypto";
|
|
236
41
|
|
|
237
|
-
// src/
|
|
42
|
+
// src/claude-code/runtime.ts
|
|
43
|
+
import { spawn } from "child_process";
|
|
238
44
|
import crypto2 from "crypto";
|
|
45
|
+
import fs2 from "fs";
|
|
46
|
+
import fsp2 from "fs/promises";
|
|
47
|
+
import path from "path";
|
|
239
48
|
|
|
240
|
-
// src/
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
49
|
+
// src/claude-code/tar.ts
|
|
50
|
+
import fs from "fs";
|
|
51
|
+
import fsp from "fs/promises";
|
|
52
|
+
import zlib from "zlib";
|
|
53
|
+
var BLOCK = 512;
|
|
54
|
+
function readString(buf, offset, length) {
|
|
55
|
+
const end = buf.indexOf(0, offset);
|
|
56
|
+
return buf.toString("utf8", offset, end === -1 || end > offset + length ? offset + length : end);
|
|
57
|
+
}
|
|
58
|
+
function readSize(buf, offset) {
|
|
59
|
+
if (buf[offset] & 128) {
|
|
60
|
+
let value = buf[offset] & 127;
|
|
61
|
+
for (let i = 1; i < 12; i++) value = value * 256 + buf[offset + i];
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
const text = readString(buf, offset, 12).trim();
|
|
65
|
+
return text ? parseInt(text, 8) : 0;
|
|
66
|
+
}
|
|
67
|
+
function paxPath(data) {
|
|
68
|
+
let pos = 0;
|
|
69
|
+
while (pos < data.length) {
|
|
70
|
+
const space = data.indexOf(32, pos);
|
|
71
|
+
if (space === -1) break;
|
|
72
|
+
const len = parseInt(data.toString("utf8", pos, space), 10);
|
|
73
|
+
if (!Number.isFinite(len) || len <= 0) break;
|
|
74
|
+
const record = data.toString("utf8", space + 1, pos + len - 1);
|
|
75
|
+
const eq = record.indexOf("=");
|
|
76
|
+
if (eq !== -1 && record.slice(0, eq) === "path") return record.slice(eq + 1);
|
|
77
|
+
pos += len;
|
|
245
78
|
}
|
|
246
|
-
return
|
|
79
|
+
return void 0;
|
|
247
80
|
}
|
|
248
|
-
async function
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
81
|
+
async function extractFileFromTarGz(tgzPath, entryName, destPath) {
|
|
82
|
+
const input = fs.createReadStream(tgzPath).pipe(zlib.createGunzip());
|
|
83
|
+
let pending = Buffer.alloc(0);
|
|
84
|
+
let remaining = 0;
|
|
85
|
+
let padding = 0;
|
|
86
|
+
let kind = "skip";
|
|
87
|
+
let meta = [];
|
|
88
|
+
let overridePath;
|
|
89
|
+
let out;
|
|
90
|
+
let found = false;
|
|
91
|
+
try {
|
|
92
|
+
for await (const chunk of input) {
|
|
93
|
+
pending = pending.length ? Buffer.concat([pending, chunk]) : chunk;
|
|
94
|
+
let pos = 0;
|
|
95
|
+
while (pos < pending.length) {
|
|
96
|
+
if (remaining > 0) {
|
|
97
|
+
const take = Math.min(remaining, pending.length - pos);
|
|
98
|
+
const slice = pending.subarray(pos, pos + take);
|
|
99
|
+
if (kind === "target") await out.write(slice);
|
|
100
|
+
else if (kind === "pax" || kind === "longname") meta.push(Buffer.from(slice));
|
|
101
|
+
pos += take;
|
|
102
|
+
remaining -= take;
|
|
103
|
+
if (remaining === 0) {
|
|
104
|
+
if (kind === "target") {
|
|
105
|
+
await out.close();
|
|
106
|
+
out = void 0;
|
|
107
|
+
found = true;
|
|
108
|
+
} else if (kind === "pax") {
|
|
109
|
+
overridePath = paxPath(Buffer.concat(meta)) ?? overridePath;
|
|
110
|
+
} else if (kind === "longname") {
|
|
111
|
+
overridePath = readString(Buffer.concat(meta), 0, Number.MAX_SAFE_INTEGER);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (padding > 0) {
|
|
117
|
+
const skip = Math.min(padding, pending.length - pos);
|
|
118
|
+
pos += skip;
|
|
119
|
+
padding -= skip;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (found) break;
|
|
123
|
+
if (pending.length - pos < BLOCK) break;
|
|
124
|
+
const header = pending.subarray(pos, pos + BLOCK);
|
|
125
|
+
pos += BLOCK;
|
|
126
|
+
if (header.every((b) => b === 0)) continue;
|
|
127
|
+
const size = readSize(header, 124);
|
|
128
|
+
const type = String.fromCharCode(header[156] || 48);
|
|
129
|
+
let name = readString(header, 0, 100);
|
|
130
|
+
if (readString(header, 257, 6) === "ustar") {
|
|
131
|
+
const prefix = readString(header, 345, 155);
|
|
132
|
+
if (prefix) name = `${prefix}/${name}`;
|
|
133
|
+
}
|
|
134
|
+
remaining = size;
|
|
135
|
+
padding = (BLOCK - size % BLOCK) % BLOCK;
|
|
136
|
+
meta = [];
|
|
137
|
+
if (type === "x") kind = "pax";
|
|
138
|
+
else if (type === "L") kind = "longname";
|
|
139
|
+
else {
|
|
140
|
+
const path4 = overridePath ?? name;
|
|
141
|
+
overridePath = void 0;
|
|
142
|
+
if ((type === "0" || type === "\0" || type === "7") && path4 === entryName) {
|
|
143
|
+
kind = "target";
|
|
144
|
+
out = await fsp.open(destPath, "w", 493);
|
|
145
|
+
if (size === 0) {
|
|
146
|
+
await out.close();
|
|
147
|
+
out = void 0;
|
|
148
|
+
found = true;
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
kind = "skip";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
pending = pos < pending.length ? Buffer.from(pending.subarray(pos)) : Buffer.alloc(0);
|
|
156
|
+
if (found) break;
|
|
157
|
+
}
|
|
158
|
+
if (!found && out) throw new Error(`Archive ended inside ${entryName}`);
|
|
159
|
+
return found;
|
|
160
|
+
} finally {
|
|
161
|
+
if (out) await out.close().catch(() => {
|
|
162
|
+
});
|
|
163
|
+
input.destroy();
|
|
164
|
+
}
|
|
256
165
|
}
|
|
257
166
|
|
|
258
|
-
// src/claude-code
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
"
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
167
|
+
// src/claude-code/manifest.json
|
|
168
|
+
var manifest_default = {
|
|
169
|
+
sdkVersion: "0.3.318",
|
|
170
|
+
claudeCodeVersion: "2.1.318",
|
|
171
|
+
platforms: {
|
|
172
|
+
"darwin-arm64": {
|
|
173
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.318.tgz",
|
|
174
|
+
integrity: "sha512-AWoXeDXzAMD3RaA21lWuHvCrKd8xmbTTmKjovninn4j07jheoVSMzFbCFjpp4kk5TdhT36JtWPkAxvjNMbcO8w==",
|
|
175
|
+
bin: "claude"
|
|
176
|
+
},
|
|
177
|
+
"darwin-x64": {
|
|
178
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.318.tgz",
|
|
179
|
+
integrity: "sha512-6CkZ5PCdebSY/LePp16sf/HYRtM6zVuxtn8qYICFS5KUZFsjccJJ9swVsOdHXNHi4SWZcSmIFTwoUbfFCzisRw==",
|
|
180
|
+
bin: "claude"
|
|
181
|
+
},
|
|
182
|
+
"linux-arm64": {
|
|
183
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.318.tgz",
|
|
184
|
+
integrity: "sha512-mBm4su17aMitLBBCQj3bHdejyUDY6z/ormGih+HG24N689RSbdKECjGjkT87tDBIR/4jyoqmuSeHqM1DI8TJlg==",
|
|
185
|
+
bin: "claude"
|
|
186
|
+
},
|
|
187
|
+
"linux-arm64-musl": {
|
|
188
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.318.tgz",
|
|
189
|
+
integrity: "sha512-l4LSYFOrvlYCrUImGfxMTQ0wg+T3s9CBhrBvPF/0oJGKZg+QnxHHU8jUVm9e9/xYmDRTtqFIrr4id/5AfH4XGw==",
|
|
190
|
+
bin: "claude"
|
|
191
|
+
},
|
|
192
|
+
"linux-x64": {
|
|
193
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.318.tgz",
|
|
194
|
+
integrity: "sha512-+mE+ueBD3oRFdaG16rd0h+LMjdeivGxL0pr/+FZK7JeLWVu+yPSw/f/X02b9oK36/DWykiFTu2G6HnJ9/+YO6w==",
|
|
195
|
+
bin: "claude"
|
|
196
|
+
},
|
|
197
|
+
"linux-x64-musl": {
|
|
198
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.318.tgz",
|
|
199
|
+
integrity: "sha512-SAF3lj/2OzTU84p+OOA13QEKA9+5fYAw0M9E7AJjdnRmVFGqQfbV7Do1i1f9Sj0mIubB3rLTRphGRw8O4HZjgA==",
|
|
200
|
+
bin: "claude"
|
|
201
|
+
},
|
|
202
|
+
"win32-arm64": {
|
|
203
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.318.tgz",
|
|
204
|
+
integrity: "sha512-19iVSZ4379QbZU1XnGUtfFvhKS1WzS8H1sbU/DbrzqbJ5Kq1eB1drj+obB1fCxTh6m26tRPj0zMJmdeu/jk3Gw==",
|
|
205
|
+
bin: "claude.exe"
|
|
206
|
+
},
|
|
207
|
+
"win32-x64": {
|
|
208
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.318.tgz",
|
|
209
|
+
integrity: "sha512-ZdKVWpyVNVt0cXrOf8sUBaxcxLEAP+oGMdQDLBWr/BqOxTWLy7TBWOj1kzw+wVg/9WIkjkgQWeJzGXPWulR1sA==",
|
|
210
|
+
bin: "claude.exe"
|
|
301
211
|
}
|
|
302
|
-
lastError = new Error(`Anthropic ${label} failed (${response.status}): ${text}`);
|
|
303
212
|
}
|
|
304
|
-
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// src/claude-code/runtime.ts
|
|
216
|
+
var CLAUDE_CODE_MANIFEST = manifest_default;
|
|
217
|
+
var CLAUDE_CODE_AUTH_KEY = "anthropic-claude-code";
|
|
218
|
+
var CLAUDE_CODE_AUTH_MARKER = { via: "claude-code" };
|
|
219
|
+
function isClaudeCodeAuthMarker(value) {
|
|
220
|
+
return value?.via === CLAUDE_CODE_AUTH_MARKER.via;
|
|
221
|
+
}
|
|
222
|
+
function isLegacyClaudeOAuth(value) {
|
|
223
|
+
const token = value?.accessToken;
|
|
224
|
+
return typeof token === "string" && token.startsWith("sk-ant-oat");
|
|
225
|
+
}
|
|
226
|
+
function isMusl() {
|
|
227
|
+
if (process.platform !== "linux") return false;
|
|
228
|
+
const report = typeof process.report?.getReport === "function" ? process.report.getReport() : null;
|
|
229
|
+
return report != null && report.header?.glibcVersionRuntime === void 0;
|
|
230
|
+
}
|
|
231
|
+
function claudeCodePlatformKey(info = { platform: process.platform, arch: process.arch, musl: isMusl() }) {
|
|
232
|
+
const arch = info.arch === "arm64" || info.arch === "x64" ? info.arch : null;
|
|
233
|
+
if (!arch) return null;
|
|
234
|
+
if (info.platform === "darwin" || info.platform === "win32") return `${info.platform}-${arch}`;
|
|
235
|
+
if (info.platform === "linux") return `linux-${arch}${info.musl ? "-musl" : ""}`;
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
function resolveAsset(opts) {
|
|
239
|
+
const manifest = opts.manifest ?? CLAUDE_CODE_MANIFEST;
|
|
240
|
+
const key = opts.platformKey ?? claudeCodePlatformKey();
|
|
241
|
+
const asset = key ? manifest.platforms[key] : void 0;
|
|
242
|
+
if (!asset) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`Claude Code isn't available for ${process.platform}-${process.arch}. Use an Anthropic API key instead.`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
return { manifest, asset };
|
|
305
248
|
}
|
|
306
|
-
function
|
|
307
|
-
return
|
|
308
|
-
accessToken: data.access_token,
|
|
309
|
-
refreshToken: data.refresh_token,
|
|
310
|
-
expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3
|
|
311
|
-
};
|
|
249
|
+
function claudeCodeHome(opts = {}) {
|
|
250
|
+
return opts.home ?? getAppPaths().claudeCodeDir;
|
|
312
251
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const state = crypto2.randomBytes(16).toString("hex");
|
|
316
|
-
const params = new URLSearchParams({
|
|
317
|
-
code: "true",
|
|
318
|
-
client_id: CLIENT_ID2,
|
|
319
|
-
response_type: "code",
|
|
320
|
-
redirect_uri: REDIRECT_URI,
|
|
321
|
-
scope: SCOPES,
|
|
322
|
-
code_challenge: challenge,
|
|
323
|
-
code_challenge_method: "S256",
|
|
324
|
-
state
|
|
325
|
-
});
|
|
326
|
-
const authUrl = `${AUTHORIZE_URL}?${params}`;
|
|
327
|
-
callbacks.onOpenUrl(authUrl);
|
|
328
|
-
const raw = await callbacks.onPromptCode("Paste the code from the browser (format: code#state):");
|
|
329
|
-
const parts = raw.trim().split("#");
|
|
330
|
-
if (parts.length !== 2 || !parts[0] || parts[1] !== state) {
|
|
331
|
-
throw new Error("Invalid code or state mismatch. Please try again.");
|
|
332
|
-
}
|
|
333
|
-
return exchangeAnthropicCode(parts[0], parts[1], verifier);
|
|
334
|
-
}
|
|
335
|
-
async function exchangeAnthropicCode(code, state, verifier) {
|
|
336
|
-
const data = await postTokenRequest(
|
|
337
|
-
{
|
|
338
|
-
grant_type: "authorization_code",
|
|
339
|
-
client_id: CLIENT_ID2,
|
|
340
|
-
code,
|
|
341
|
-
state,
|
|
342
|
-
redirect_uri: REDIRECT_URI,
|
|
343
|
-
code_verifier: verifier
|
|
344
|
-
},
|
|
345
|
-
"token exchange"
|
|
346
|
-
);
|
|
347
|
-
return toCredentials(data);
|
|
348
|
-
}
|
|
349
|
-
async function refreshAnthropicToken(refreshToken) {
|
|
350
|
-
const data = await postTokenRequest(
|
|
351
|
-
{
|
|
352
|
-
grant_type: "refresh_token",
|
|
353
|
-
client_id: CLIENT_ID2,
|
|
354
|
-
refresh_token: refreshToken
|
|
355
|
-
},
|
|
356
|
-
"token refresh"
|
|
357
|
-
);
|
|
358
|
-
return toCredentials(data);
|
|
252
|
+
function claudeCodeConfigDir(opts = {}) {
|
|
253
|
+
return opts.home ? path.join(opts.home, "config") : getAppPaths().claudeCodeConfigDir;
|
|
359
254
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
var TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
367
|
-
var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
|
|
368
|
-
var SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
369
|
-
var JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
370
|
-
var CALLBACK_TIMEOUT_MS = 12e4;
|
|
371
|
-
var MAX_PASTE_ATTEMPTS = 3;
|
|
372
|
-
var MIN_RAW_CODE_LENGTH = 10;
|
|
373
|
-
async function loginOpenAI(callbacks) {
|
|
374
|
-
const { verifier, challenge } = await generatePKCE();
|
|
375
|
-
const state = crypto3.randomBytes(16).toString("hex");
|
|
376
|
-
const url = new URL(AUTHORIZE_URL2);
|
|
377
|
-
url.searchParams.set("response_type", "code");
|
|
378
|
-
url.searchParams.set("client_id", CLIENT_ID3);
|
|
379
|
-
url.searchParams.set("redirect_uri", REDIRECT_URI2);
|
|
380
|
-
url.searchParams.set("scope", SCOPE);
|
|
381
|
-
url.searchParams.set("code_challenge", challenge);
|
|
382
|
-
url.searchParams.set("code_challenge_method", "S256");
|
|
383
|
-
url.searchParams.set("state", state);
|
|
384
|
-
url.searchParams.set("prompt", "login");
|
|
385
|
-
url.searchParams.set("id_token_add_organizations", "true");
|
|
386
|
-
url.searchParams.set("codex_cli_simplified_flow", "true");
|
|
387
|
-
url.searchParams.set("originator", "ggcoder");
|
|
388
|
-
const code = await acquireAuthorizationCode(url.toString(), state, callbacks);
|
|
389
|
-
const creds = await exchangeOpenAICode(code, verifier);
|
|
390
|
-
const accountId = getAccountId(creds.accessToken);
|
|
391
|
-
if (!accountId) {
|
|
392
|
-
throw new Error("Failed to extract accountId from OpenAI token.");
|
|
393
|
-
}
|
|
394
|
-
creds.accountId = accountId;
|
|
395
|
-
return creds;
|
|
396
|
-
}
|
|
397
|
-
function parseAuthorizationInput(input) {
|
|
398
|
-
const value = input.trim();
|
|
399
|
-
if (!value) return {};
|
|
255
|
+
function claudeCodeBinaryPath(opts = {}) {
|
|
256
|
+
const { manifest, asset } = resolveAsset(opts);
|
|
257
|
+
return path.join(claudeCodeHome(opts), manifest.claudeCodeVersion, asset.bin);
|
|
258
|
+
}
|
|
259
|
+
function resolveClaudeCodeBinary(opts = {}) {
|
|
260
|
+
let binary;
|
|
400
261
|
try {
|
|
401
|
-
|
|
402
|
-
return {
|
|
403
|
-
code: url.searchParams.get("code") ?? void 0,
|
|
404
|
-
state: url.searchParams.get("state") ?? void 0
|
|
405
|
-
};
|
|
262
|
+
binary = claudeCodeBinaryPath(opts);
|
|
406
263
|
} catch {
|
|
264
|
+
return null;
|
|
407
265
|
}
|
|
408
|
-
if (value.includes("#")) {
|
|
409
|
-
const [code, state] = value.split("#", 2);
|
|
410
|
-
return { code, state };
|
|
411
|
-
}
|
|
412
|
-
if (value.includes("code=")) {
|
|
413
|
-
const params = new URLSearchParams(value);
|
|
414
|
-
return {
|
|
415
|
-
code: params.get("code") ?? void 0,
|
|
416
|
-
state: params.get("state") ?? void 0
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
if (/\s/.test(value) || value.length < MIN_RAW_CODE_LENGTH) return {};
|
|
420
|
-
return { code: value };
|
|
421
|
-
}
|
|
422
|
-
function decodeJwt(token) {
|
|
423
266
|
try {
|
|
424
|
-
|
|
425
|
-
if (parts.length !== 3) return null;
|
|
426
|
-
const decoded = atob(parts[1]);
|
|
427
|
-
return JSON.parse(decoded);
|
|
267
|
+
return fs2.statSync(binary).isFile() ? binary : null;
|
|
428
268
|
} catch {
|
|
429
269
|
return null;
|
|
430
270
|
}
|
|
431
271
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
272
|
+
var installs = /* @__PURE__ */ new Map();
|
|
273
|
+
async function ensureClaudeCodeBinary(opts = {}) {
|
|
274
|
+
const existing = resolveClaudeCodeBinary(opts);
|
|
275
|
+
if (existing) return existing;
|
|
276
|
+
const target = claudeCodeBinaryPath(opts);
|
|
277
|
+
let install = installs.get(target);
|
|
278
|
+
if (!install) {
|
|
279
|
+
install = installClaudeCode(opts).finally(() => installs.delete(target));
|
|
280
|
+
installs.set(target, install);
|
|
281
|
+
}
|
|
282
|
+
return install;
|
|
283
|
+
}
|
|
284
|
+
async function installClaudeCode(opts) {
|
|
285
|
+
const { manifest, asset } = resolveAsset(opts);
|
|
286
|
+
const home = claudeCodeHome(opts);
|
|
287
|
+
const versionDir = path.join(home, manifest.claudeCodeVersion);
|
|
288
|
+
const binary = path.join(versionDir, asset.bin);
|
|
289
|
+
await fsp2.mkdir(home, { recursive: true });
|
|
290
|
+
const tag = `${process.pid}-${crypto2.randomBytes(4).toString("hex")}`;
|
|
291
|
+
const tarball = path.join(home, `.download-${tag}.tgz`);
|
|
292
|
+
const staging = path.join(home, `.staging-${tag}`);
|
|
442
293
|
try {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if (err instanceof FatalLoginError) {
|
|
458
|
-
reject(err);
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
pasteError = err;
|
|
462
|
-
failIfExhausted();
|
|
463
|
-
});
|
|
464
|
-
});
|
|
465
|
-
} finally {
|
|
466
|
-
done.abort();
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
var FatalLoginError = class extends Error {
|
|
470
|
-
};
|
|
471
|
-
async function promptForCode(expectedState, callbacks, signal) {
|
|
472
|
-
let message = "If the browser is on another machine (SSH/headless), paste the callback URL or code here:";
|
|
473
|
-
for (let attempt = 0; attempt < MAX_PASTE_ATTEMPTS; attempt++) {
|
|
474
|
-
if (signal.aborted) throw new Error("Authorization code already received.");
|
|
475
|
-
const raw = await callbacks.onPromptCode(message, signal);
|
|
476
|
-
if (signal.aborted) throw new Error("Authorization code already received.");
|
|
477
|
-
const parsed = parseAuthorizationInput(raw);
|
|
478
|
-
if (parsed.code && parsed.state && parsed.state !== expectedState) {
|
|
479
|
-
throw new FatalLoginError("Authorization state mismatch \u2014 start the login again.");
|
|
294
|
+
await download(asset, tarball, opts);
|
|
295
|
+
opts.onProgress?.({ phase: "extract" });
|
|
296
|
+
await fsp2.mkdir(staging, { recursive: true });
|
|
297
|
+
const found = await extractFileFromTarGz(
|
|
298
|
+
tarball,
|
|
299
|
+
`package/${asset.bin}`,
|
|
300
|
+
path.join(staging, asset.bin)
|
|
301
|
+
);
|
|
302
|
+
if (!found) throw new Error(`The Claude Code download has no ${asset.bin} binary.`);
|
|
303
|
+
await fsp2.chmod(path.join(staging, asset.bin), 493);
|
|
304
|
+
try {
|
|
305
|
+
await fsp2.rename(staging, versionDir);
|
|
306
|
+
} catch (err) {
|
|
307
|
+
if (!fs2.existsSync(binary)) throw err;
|
|
480
308
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
async function listenForCallback(expectedState, signal) {
|
|
487
|
-
return new Promise((resolve, reject) => {
|
|
488
|
-
let receivedCode = null;
|
|
489
|
-
const server = http.createServer((req, res) => {
|
|
490
|
-
const url = new URL(req.url || "", "http://localhost");
|
|
491
|
-
if (url.pathname !== "/auth/callback") {
|
|
492
|
-
res.statusCode = 404;
|
|
493
|
-
res.end("Not found");
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
if (url.searchParams.get("state") !== expectedState) {
|
|
497
|
-
res.statusCode = 400;
|
|
498
|
-
res.end("State mismatch");
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
receivedCode = url.searchParams.get("code");
|
|
502
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
503
|
-
res.end("<html><body><h1>Login successful!</h1><p>You can close this tab.</p></body></html>");
|
|
504
|
-
server.close();
|
|
505
|
-
});
|
|
506
|
-
server.on("error", (err) => {
|
|
507
|
-
reject(err);
|
|
309
|
+
await removeOldVersions(home, manifest.claudeCodeVersion);
|
|
310
|
+
return binary;
|
|
311
|
+
} finally {
|
|
312
|
+
await fsp2.rm(tarball, { force: true }).catch(() => {
|
|
508
313
|
});
|
|
509
|
-
|
|
510
|
-
const timeout = setTimeout(() => {
|
|
511
|
-
if (!receivedCode) server.close();
|
|
512
|
-
}, CALLBACK_TIMEOUT_MS);
|
|
513
|
-
timeout.unref();
|
|
514
|
-
const onAbort = () => {
|
|
515
|
-
server.close();
|
|
516
|
-
};
|
|
517
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
518
|
-
server.on("close", () => {
|
|
519
|
-
clearTimeout(timeout);
|
|
520
|
-
signal.removeEventListener("abort", onAbort);
|
|
521
|
-
if (receivedCode) {
|
|
522
|
-
resolve(receivedCode);
|
|
523
|
-
} else {
|
|
524
|
-
reject(new Error("Server closed without receiving code"));
|
|
525
|
-
}
|
|
314
|
+
await fsp2.rm(staging, { recursive: true, force: true }).catch(() => {
|
|
526
315
|
});
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
async function exchangeOpenAICode(code, verifier) {
|
|
530
|
-
const response = await fetch(TOKEN_URL, {
|
|
531
|
-
method: "POST",
|
|
532
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
533
|
-
body: new URLSearchParams({
|
|
534
|
-
grant_type: "authorization_code",
|
|
535
|
-
client_id: CLIENT_ID3,
|
|
536
|
-
code,
|
|
537
|
-
redirect_uri: REDIRECT_URI2,
|
|
538
|
-
code_verifier: verifier
|
|
539
|
-
}),
|
|
540
|
-
signal: oauthRequestSignal()
|
|
541
|
-
});
|
|
542
|
-
if (!response.ok) {
|
|
543
|
-
const text = await response.text();
|
|
544
|
-
throw new Error(`OpenAI token exchange failed (${response.status}): ${text}`);
|
|
545
316
|
}
|
|
546
|
-
const data = await response.json();
|
|
547
|
-
return {
|
|
548
|
-
accessToken: data.access_token,
|
|
549
|
-
refreshToken: data.refresh_token,
|
|
550
|
-
expiresAt: Date.now() + data.expires_in * 1e3
|
|
551
|
-
};
|
|
552
317
|
}
|
|
553
|
-
async function
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
grant_type: "refresh_token",
|
|
559
|
-
refresh_token: refreshToken,
|
|
560
|
-
client_id: CLIENT_ID3
|
|
561
|
-
}),
|
|
562
|
-
signal: oauthRequestSignal()
|
|
563
|
-
});
|
|
564
|
-
if (!response.ok) {
|
|
565
|
-
const text = await response.text();
|
|
566
|
-
throw new Error(`OpenAI token refresh failed (${response.status}): ${text}`);
|
|
318
|
+
async function download(asset, dest, opts) {
|
|
319
|
+
const doFetch = opts.fetch ?? fetch;
|
|
320
|
+
const res = await doFetch(asset.url, { signal: opts.signal });
|
|
321
|
+
if (!res.ok || !res.body) {
|
|
322
|
+
throw new Error(`Claude Code download failed (HTTP ${res.status}).`);
|
|
567
323
|
}
|
|
568
|
-
const
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
324
|
+
const totalBytes = Number(res.headers.get("content-length")) || void 0;
|
|
325
|
+
const hash = crypto2.createHash("sha512");
|
|
326
|
+
const out = await fsp2.open(dest, "w");
|
|
327
|
+
let receivedBytes = 0;
|
|
328
|
+
let lastReported = -1;
|
|
329
|
+
try {
|
|
330
|
+
opts.onProgress?.({ phase: "download", receivedBytes, totalBytes });
|
|
331
|
+
for await (const chunk of res.body) {
|
|
332
|
+
if (opts.signal?.aborted) throw opts.signal.reason ?? new Error("Download cancelled.");
|
|
333
|
+
hash.update(chunk);
|
|
334
|
+
await out.write(chunk);
|
|
335
|
+
receivedBytes += chunk.byteLength;
|
|
336
|
+
const step = totalBytes ? Math.floor(receivedBytes / totalBytes * 100) : Math.floor(receivedBytes / 4194304);
|
|
337
|
+
if (step !== lastReported) {
|
|
338
|
+
lastReported = step;
|
|
339
|
+
opts.onProgress?.({ phase: "download", receivedBytes, totalBytes });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} finally {
|
|
343
|
+
await out.close();
|
|
577
344
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
var CLIENT_ID_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_ID";
|
|
585
|
-
var CLIENT_SECRET_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_SECRET";
|
|
586
|
-
var DEFAULT_CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
|
|
587
|
-
var DEFAULT_CLIENT_SECRET = "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";
|
|
588
|
-
var AUTHORIZE_URL3 = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
589
|
-
var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
590
|
-
var CODE_ASSIST_BASE_URL = "https://cloudcode-pa.googleapis.com";
|
|
591
|
-
var CODE_ASSIST_API_VERSION = "v1internal";
|
|
592
|
-
var CODE_ASSIST_POST_RETRIES = 3;
|
|
593
|
-
var CODE_ASSIST_POST_RETRY_DELAY_MS = 100;
|
|
594
|
-
var SCOPE2 = [
|
|
595
|
-
"https://www.googleapis.com/auth/cloud-platform",
|
|
596
|
-
"https://www.googleapis.com/auth/userinfo.email",
|
|
597
|
-
"https://www.googleapis.com/auth/userinfo.profile"
|
|
598
|
-
].join(" ");
|
|
599
|
-
var USER_TIER_FREE = "free-tier";
|
|
600
|
-
var USER_TIER_LEGACY = "legacy-tier";
|
|
601
|
-
var USER_TIER_STANDARD = "standard-tier";
|
|
602
|
-
var VALIDATION_REQUIRED_REASON = "VALIDATION_REQUIRED";
|
|
603
|
-
var VPC_SC_REASON = "SECURITY_POLICY_VIOLATED";
|
|
604
|
-
var CodeAssistHttpError = class extends Error {
|
|
605
|
-
status;
|
|
606
|
-
body;
|
|
607
|
-
constructor(label, status, body) {
|
|
608
|
-
super(`Gemini Code Assist ${label} failed (${status}): ${body}`);
|
|
609
|
-
this.name = "CodeAssistHttpError";
|
|
610
|
-
this.status = status;
|
|
611
|
-
this.body = body;
|
|
345
|
+
opts.onProgress?.({ phase: "verify" });
|
|
346
|
+
const expected = asset.integrity.replace(/^sha512-/, "");
|
|
347
|
+
if (hash.digest("base64") !== expected) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
"The Claude Code download didn't match its pinned checksum, so it was discarded."
|
|
350
|
+
);
|
|
612
351
|
}
|
|
613
|
-
}
|
|
614
|
-
async function
|
|
615
|
-
|
|
616
|
-
const { verifier, challenge } = await generatePKCE();
|
|
617
|
-
const state = crypto4.randomBytes(32).toString("hex");
|
|
618
|
-
const redirectUri = await getLoopbackRedirectUri();
|
|
619
|
-
const url = new URL(AUTHORIZE_URL3);
|
|
620
|
-
url.searchParams.set("response_type", "code");
|
|
621
|
-
url.searchParams.set("client_id", clientId2);
|
|
622
|
-
url.searchParams.set("redirect_uri", redirectUri);
|
|
623
|
-
url.searchParams.set("scope", SCOPE2);
|
|
624
|
-
url.searchParams.set("access_type", "offline");
|
|
625
|
-
url.searchParams.set("prompt", "consent");
|
|
626
|
-
url.searchParams.set("code_challenge", challenge);
|
|
627
|
-
url.searchParams.set("code_challenge_method", "S256");
|
|
628
|
-
url.searchParams.set("state", state);
|
|
629
|
-
let code;
|
|
352
|
+
}
|
|
353
|
+
async function removeOldVersions(home, keep) {
|
|
354
|
+
let entries;
|
|
630
355
|
try {
|
|
631
|
-
|
|
356
|
+
entries = await fsp2.readdir(home, { withFileTypes: true });
|
|
632
357
|
} catch {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
);
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
if (parsed.state && parsed.state !== state) {
|
|
642
|
-
throw new Error("Invalid state. Please try again.");
|
|
643
|
-
}
|
|
644
|
-
code = parsed.code;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
for (const entry of entries) {
|
|
361
|
+
if (!entry.isDirectory() || entry.name === keep || entry.name === "config") continue;
|
|
362
|
+
if (!/^\d+\.\d+\.\d+/.test(entry.name)) continue;
|
|
363
|
+
await fsp2.rm(path.join(home, entry.name), { recursive: true, force: true }).catch(() => {
|
|
364
|
+
});
|
|
645
365
|
}
|
|
646
|
-
const creds = await exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret);
|
|
647
|
-
callbacks.onStatus("Setting up Gemini Code Assist access...");
|
|
648
|
-
const projectId = await setupCodeAssistProject(creds.accessToken, callbacks);
|
|
649
|
-
return {
|
|
650
|
-
...creds,
|
|
651
|
-
projectId
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
async function refreshGeminiToken(refreshToken) {
|
|
655
|
-
const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
|
|
656
|
-
const data = await postTokenRequest2({
|
|
657
|
-
grant_type: "refresh_token",
|
|
658
|
-
refresh_token: refreshToken,
|
|
659
|
-
client_id: clientId2,
|
|
660
|
-
client_secret: clientSecret
|
|
661
|
-
});
|
|
662
|
-
return {
|
|
663
|
-
accessToken: data.access_token,
|
|
664
|
-
refreshToken: data.refresh_token ?? refreshToken,
|
|
665
|
-
expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
function getGeminiOAuthClientCredentials() {
|
|
669
|
-
const clientId2 = process.env[CLIENT_ID_ENV]?.trim() || DEFAULT_CLIENT_ID;
|
|
670
|
-
const clientSecret = process.env[CLIENT_SECRET_ENV]?.trim() || DEFAULT_CLIENT_SECRET;
|
|
671
|
-
return { clientId: clientId2, clientSecret };
|
|
672
366
|
}
|
|
673
|
-
|
|
367
|
+
function formatClaudeCodeInstallProgress(progress) {
|
|
368
|
+
if (progress.phase === "verify") return "Checking the Claude Code download\u2026";
|
|
369
|
+
if (progress.phase === "extract") return "Unpacking Claude Code\u2026";
|
|
370
|
+
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
371
|
+
if (!progress.totalBytes) return `Downloading Claude Code\u2026 ${mb(progress.receivedBytes)} MB`;
|
|
372
|
+
const pct = Math.floor(progress.receivedBytes / progress.totalBytes * 100);
|
|
373
|
+
return `Downloading Claude Code\u2026 ${pct}% (${mb(progress.receivedBytes)} of ${mb(progress.totalBytes)} MB)`;
|
|
374
|
+
}
|
|
375
|
+
var STRIPPED_ENV = [
|
|
376
|
+
"ANTHROPIC_API_KEY",
|
|
377
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
378
|
+
"ANTHROPIC_BASE_URL",
|
|
379
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
380
|
+
"CLAUDE_CODE_OAUTH_REFRESH_TOKEN",
|
|
381
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
382
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
383
|
+
"CLAUDE_CODE_USE_FOUNDRY",
|
|
384
|
+
"NODE_OPTIONS"
|
|
385
|
+
];
|
|
386
|
+
function claudeCodeEnv(opts = {}, base = process.env) {
|
|
387
|
+
const env = {};
|
|
388
|
+
for (const [key, value] of Object.entries(base)) {
|
|
389
|
+
if (value !== void 0 && !STRIPPED_ENV.includes(key)) env[key] = value;
|
|
390
|
+
}
|
|
391
|
+
env.CLAUDE_CONFIG_DIR = claudeCodeConfigDir(opts);
|
|
392
|
+
env.DISABLE_AUTOUPDATER = "1";
|
|
393
|
+
return env;
|
|
394
|
+
}
|
|
395
|
+
function runCommand(opts) {
|
|
396
|
+
if (opts.binary) return { command: opts.binary, prefix: opts.binaryArgs ?? [] };
|
|
397
|
+
const binary = resolveClaudeCodeBinary(opts);
|
|
398
|
+
if (!binary) throw new Error("Claude Code isn't installed yet. Sign in with Claude first.");
|
|
399
|
+
return { command: binary, prefix: [] };
|
|
400
|
+
}
|
|
401
|
+
function runClaude(args, opts) {
|
|
402
|
+
const { command, prefix } = runCommand(opts);
|
|
674
403
|
return new Promise((resolve, reject) => {
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
resolve(`http://127.0.0.1:${addr.port}/oauth2callback`);
|
|
681
|
-
} else {
|
|
682
|
-
reject(new Error("Failed to allocate OAuth callback port."));
|
|
683
|
-
}
|
|
684
|
-
});
|
|
404
|
+
const child = spawn(command, [...prefix, ...args], {
|
|
405
|
+
env: claudeCodeEnv(opts),
|
|
406
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
407
|
+
windowsHide: true,
|
|
408
|
+
signal: opts.signal
|
|
685
409
|
});
|
|
686
|
-
|
|
410
|
+
let stdout = "";
|
|
411
|
+
let stderr = "";
|
|
412
|
+
child.stdout.on("data", (d) => stdout += d.toString());
|
|
413
|
+
child.stderr.on("data", (d) => stderr += d.toString());
|
|
414
|
+
child.on("error", reject);
|
|
415
|
+
child.on("close", (code) => resolve({ code, stdout, stderr }));
|
|
687
416
|
});
|
|
688
417
|
}
|
|
689
|
-
function
|
|
690
|
-
|
|
691
|
-
|
|
418
|
+
async function claudeCodeAuthStatus(opts = {}) {
|
|
419
|
+
if (!opts.binary && !resolveClaudeCodeBinary(opts)) return { loggedIn: false };
|
|
420
|
+
const { stdout } = await runClaude(["auth", "status", "--json"], opts);
|
|
692
421
|
try {
|
|
693
|
-
const
|
|
422
|
+
const parsed = JSON.parse(stdout);
|
|
694
423
|
return {
|
|
695
|
-
|
|
696
|
-
|
|
424
|
+
loggedIn: parsed.loggedIn === true,
|
|
425
|
+
...typeof parsed.authMethod === "string" ? { authMethod: parsed.authMethod } : {},
|
|
426
|
+
...typeof parsed.email === "string" ? { email: parsed.email } : {},
|
|
427
|
+
...typeof parsed.subscriptionType === "string" ? { subscriptionType: parsed.subscriptionType } : {}
|
|
697
428
|
};
|
|
698
429
|
} catch {
|
|
430
|
+
return { loggedIn: false };
|
|
699
431
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
432
|
+
}
|
|
433
|
+
async function logoutClaudeCode(opts = {}) {
|
|
434
|
+
if (!opts.binary && !resolveClaudeCodeBinary(opts)) return;
|
|
435
|
+
const { code, stderr } = await runClaude(["auth", "logout"], opts);
|
|
436
|
+
if (code !== 0) throw new Error(stderr.trim() || `claude auth logout exited with ${code}`);
|
|
437
|
+
}
|
|
438
|
+
var OPEN_URL_HOOK = `const arg = String(process.argv[1] || "");
|
|
439
|
+
const file = process.env.GG_CLAUDE_CODE_URL_FILE;
|
|
440
|
+
if (file && /https?:/.test(arg)) {
|
|
441
|
+
require("node:fs").appendFileSync(file, arg + "\\n");
|
|
442
|
+
process.exit(0);
|
|
443
|
+
}
|
|
444
|
+
`;
|
|
445
|
+
var CAPTURE_WAIT_MS = 3e3;
|
|
446
|
+
function stripAnsi(text) {
|
|
447
|
+
return text.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[0-9;?]*[A-Za-z]/g, "");
|
|
448
|
+
}
|
|
449
|
+
function claudeCodeCallbackUrl(manualUrl, captured) {
|
|
450
|
+
const port = /localhost(?::|%3A)(\d{2,5})(?:\/|%2F)callback/i.exec(captured)?.[1];
|
|
451
|
+
if (!port) return null;
|
|
452
|
+
try {
|
|
453
|
+
const url = new URL(manualUrl);
|
|
454
|
+
url.searchParams.set("redirect_uri", `http://localhost:${port}/callback`);
|
|
455
|
+
return url.toString();
|
|
456
|
+
} catch {
|
|
457
|
+
return null;
|
|
706
458
|
}
|
|
707
|
-
return { code: value };
|
|
708
459
|
}
|
|
709
|
-
async function
|
|
710
|
-
const
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
460
|
+
async function loginClaudeCode(callbacks, opts = {}) {
|
|
461
|
+
const { command, prefix } = runCommand(opts);
|
|
462
|
+
const home = claudeCodeHome(opts);
|
|
463
|
+
await fsp2.mkdir(claudeCodeConfigDir(opts), { recursive: true });
|
|
464
|
+
const tag = `${process.pid}-${crypto2.randomBytes(4).toString("hex")}`;
|
|
465
|
+
const hookFile = path.join(home, `.open-url-${tag}.cjs`);
|
|
466
|
+
const urlFile = path.join(home, `.open-url-${tag}.txt`);
|
|
467
|
+
await fsp2.writeFile(hookFile, OPEN_URL_HOOK);
|
|
468
|
+
const env = {
|
|
469
|
+
...claudeCodeEnv(opts),
|
|
470
|
+
BROWSER: process.execPath,
|
|
471
|
+
NODE_OPTIONS: `--require ${JSON.stringify(hookFile)}`,
|
|
472
|
+
GG_CLAUDE_CODE_URL_FILE: urlFile
|
|
473
|
+
};
|
|
474
|
+
const promptAbort = new AbortController();
|
|
475
|
+
let timer;
|
|
476
|
+
try {
|
|
477
|
+
await new Promise((resolve, reject) => {
|
|
478
|
+
const child = spawn(command, [...prefix, "auth", "login", "--claudeai"], {
|
|
479
|
+
env,
|
|
480
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
481
|
+
windowsHide: true
|
|
482
|
+
});
|
|
483
|
+
let stdout = "";
|
|
484
|
+
let stderr = "";
|
|
485
|
+
let manualUrl = null;
|
|
486
|
+
let opened = false;
|
|
487
|
+
const onAbort = () => child.kill();
|
|
488
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
489
|
+
const open = (url, needsCode) => {
|
|
490
|
+
if (opened) return;
|
|
491
|
+
opened = true;
|
|
492
|
+
clearInterval(timer);
|
|
493
|
+
callbacks.onOpenUrl(url);
|
|
494
|
+
callbacks.onStatus("Finish signing in to Claude in your browser\u2026");
|
|
495
|
+
if (!needsCode) return;
|
|
496
|
+
void callbacks.onPromptCode("Paste the code shown after signing in to Claude:", promptAbort.signal).then((code) => {
|
|
497
|
+
const trimmed = code.trim();
|
|
498
|
+
if (trimmed && !child.stdin.destroyed) child.stdin.write(`${trimmed}
|
|
499
|
+
`);
|
|
500
|
+
}).catch(() => {
|
|
501
|
+
});
|
|
502
|
+
};
|
|
503
|
+
child.stdout.on("data", (d) => {
|
|
504
|
+
stdout += d.toString();
|
|
505
|
+
if (manualUrl) return;
|
|
506
|
+
const match = /visit:\s*(https:\/\/\S+)/.exec(stripAnsi(stdout));
|
|
507
|
+
if (!match) return;
|
|
508
|
+
manualUrl = match[1];
|
|
509
|
+
const started = Date.now();
|
|
510
|
+
timer = setInterval(() => {
|
|
511
|
+
let captured = "";
|
|
512
|
+
try {
|
|
513
|
+
captured = fs2.readFileSync(urlFile, "utf8");
|
|
514
|
+
} catch {
|
|
515
|
+
}
|
|
516
|
+
const callbackUrl = captured ? claudeCodeCallbackUrl(manualUrl, captured) : null;
|
|
517
|
+
if (callbackUrl) open(callbackUrl, false);
|
|
518
|
+
else if (Date.now() - started > (opts.browserWaitMs ?? CAPTURE_WAIT_MS))
|
|
519
|
+
open(manualUrl, true);
|
|
520
|
+
}, 100);
|
|
521
|
+
});
|
|
522
|
+
child.stderr.on("data", (d) => stderr += d.toString());
|
|
523
|
+
child.on("error", reject);
|
|
524
|
+
child.on("close", (code) => {
|
|
525
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
526
|
+
if (opts.signal?.aborted) reject(new Error("Sign-in cancelled."));
|
|
527
|
+
else if (code === 0) resolve();
|
|
528
|
+
else {
|
|
529
|
+
const detail = stripAnsi(stderr).trim().split("\n").pop()?.trim();
|
|
530
|
+
reject(new Error(detail || `Claude sign-in failed (exit ${code}).`));
|
|
531
|
+
}
|
|
532
|
+
});
|
|
730
533
|
});
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
534
|
+
} finally {
|
|
535
|
+
clearInterval(timer);
|
|
536
|
+
promptAbort.abort();
|
|
537
|
+
await fsp2.rm(hookFile, { force: true }).catch(() => {
|
|
735
538
|
});
|
|
736
|
-
|
|
737
|
-
if (!receivedCode) server.close();
|
|
738
|
-
}, 12e4);
|
|
739
|
-
timeout.unref();
|
|
740
|
-
server.on("close", () => {
|
|
741
|
-
clearTimeout(timeout);
|
|
742
|
-
if (receivedCode) {
|
|
743
|
-
resolve(receivedCode);
|
|
744
|
-
} else {
|
|
745
|
-
reject(new Error("Server closed without receiving code."));
|
|
746
|
-
}
|
|
539
|
+
await fsp2.rm(urlFile, { force: true }).catch(() => {
|
|
747
540
|
});
|
|
748
|
-
}
|
|
541
|
+
}
|
|
749
542
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
543
|
+
|
|
544
|
+
// src/oauth/openai.ts
|
|
545
|
+
import http from "http";
|
|
546
|
+
import crypto3 from "crypto";
|
|
547
|
+
import fs3 from "fs/promises";
|
|
548
|
+
import path2 from "path";
|
|
549
|
+
|
|
550
|
+
// src/oauth/pkce.ts
|
|
551
|
+
function base64urlEncode(bytes) {
|
|
552
|
+
let binary = "";
|
|
553
|
+
for (const byte of bytes) {
|
|
554
|
+
binary += String.fromCharCode(byte);
|
|
761
555
|
}
|
|
762
|
-
return
|
|
763
|
-
accessToken: data.access_token,
|
|
764
|
-
refreshToken: data.refresh_token,
|
|
765
|
-
expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3
|
|
766
|
-
};
|
|
556
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
767
557
|
}
|
|
768
|
-
async function
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
558
|
+
async function generatePKCE() {
|
|
559
|
+
const verifierBytes = new Uint8Array(32);
|
|
560
|
+
crypto.getRandomValues(verifierBytes);
|
|
561
|
+
const verifier = base64urlEncode(verifierBytes);
|
|
562
|
+
const data = new TextEncoder().encode(verifier);
|
|
563
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
564
|
+
const challenge = base64urlEncode(new Uint8Array(hashBuffer));
|
|
565
|
+
return { verifier, challenge };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/oauth/request-timeout.ts
|
|
569
|
+
var OAUTH_REQUEST_TIMEOUT_MS = 3e4;
|
|
570
|
+
function oauthRequestSignal() {
|
|
571
|
+
return AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/oauth/openai.ts
|
|
575
|
+
var ISSUER = "https://auth.openai.com";
|
|
576
|
+
var AUTHORIZE_URL = `${ISSUER}/api/accounts/authorize`;
|
|
577
|
+
var TOKEN_URL = `${ISSUER}/api/accounts/oauth/token`;
|
|
578
|
+
var REVOKE_URL = `${ISSUER}/api/accounts/oauth/revoke`;
|
|
579
|
+
var JWKS_URL = `${ISSUER}/.well-known/jwks.json`;
|
|
580
|
+
var REGISTRATION_CLIENT_ID = "dynamic_agent_client";
|
|
581
|
+
var AGENT_NAME = "GG Coder";
|
|
582
|
+
var RESOURCE = "https://api.openai.com/v1";
|
|
583
|
+
var CHATGPT_PLAN_SCOPE = "chatgpt.tokens.use.direct";
|
|
584
|
+
var SCOPES = `openid profile email offline_access resource.invoke ${CHATGPT_PLAN_SCOPE}`;
|
|
585
|
+
var CHATGPT_PLAN_BASE_URL = "https://api.openai.com/v1";
|
|
586
|
+
var CHATGPT_USAGE_URL = "https://chatgpt.com/settings/usage";
|
|
587
|
+
var CALLBACK_PATH = "/auth/callback";
|
|
588
|
+
var PREFERRED_PORT = 1455;
|
|
589
|
+
var CALLBACK_TIMEOUT_MS = 12e4;
|
|
590
|
+
var MAX_PASTE_ATTEMPTS = 3;
|
|
591
|
+
var ID_TOKEN_CLOCK_SKEW_S = 60;
|
|
592
|
+
var CHATGPT_PLAN_NOT_ENABLED_MESSAGE = "ChatGPT plan use isn't enabled for GG Coder. Allow Use your ChatGPT plan, or add an OpenAI API key.";
|
|
593
|
+
var ChatGPTPlanNotEnabledError = class extends Error {
|
|
594
|
+
constructor() {
|
|
595
|
+
super(CHATGPT_PLAN_NOT_ENABLED_MESSAGE);
|
|
596
|
+
this.name = "ChatGPTPlanNotEnabledError";
|
|
778
597
|
}
|
|
779
|
-
|
|
598
|
+
};
|
|
599
|
+
function defaultHostIdFile() {
|
|
600
|
+
return path2.join(getAppPaths().agentDir, "openai-host-id");
|
|
780
601
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
if (envProject && /^\d+$/.test(envProject)) {
|
|
784
|
-
throw new Error("GOOGLE_CLOUD_PROJECT must be a project ID, not a numeric project number.");
|
|
785
|
-
}
|
|
786
|
-
const coreMetadata = {
|
|
787
|
-
ideType: "IDE_UNSPECIFIED",
|
|
788
|
-
platform: "PLATFORM_UNSPECIFIED",
|
|
789
|
-
pluginType: "GEMINI"
|
|
790
|
-
};
|
|
791
|
-
const projectMetadata = {
|
|
792
|
-
...coreMetadata,
|
|
793
|
-
...envProject ? { duetProject: envProject } : {}
|
|
794
|
-
};
|
|
795
|
-
let loadRes;
|
|
796
|
-
while (true) {
|
|
797
|
-
loadRes = await loadCodeAssist(accessToken, envProject, projectMetadata);
|
|
798
|
-
const validation = getValidationRequiredTier(loadRes);
|
|
799
|
-
if (!validation) break;
|
|
800
|
-
callbacks.onStatus(
|
|
801
|
-
`Gemini Code Assist requires account validation${validation.reasonMessage ? `: ${validation.reasonMessage}` : ""}`
|
|
802
|
-
);
|
|
803
|
-
callbacks.onOpenUrl(validation.validationUrl);
|
|
804
|
-
const answer = await callbacks.onPromptCode(
|
|
805
|
-
"Complete validation in the browser, then press Enter to retry (or type cancel):"
|
|
806
|
-
);
|
|
807
|
-
if (answer.trim().toLowerCase() === "cancel") {
|
|
808
|
-
throw new Error("Gemini Code Assist account validation was cancelled.");
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
if (loadRes.currentTier) {
|
|
812
|
-
const project2 = loadRes.cloudaicompanionProject ?? envProject;
|
|
813
|
-
if (!project2) throwProjectError(loadRes);
|
|
814
|
-
return project2;
|
|
815
|
-
}
|
|
816
|
-
const tier = getOnboardTier(loadRes);
|
|
817
|
-
const onboardReq = tier.id === USER_TIER_FREE ? {
|
|
818
|
-
tierId: tier.id,
|
|
819
|
-
cloudaicompanionProject: void 0,
|
|
820
|
-
metadata: coreMetadata
|
|
821
|
-
} : {
|
|
822
|
-
tierId: tier.id,
|
|
823
|
-
cloudaicompanionProject: envProject,
|
|
824
|
-
metadata: projectMetadata
|
|
825
|
-
};
|
|
826
|
-
let operation = await codeAssistPost(
|
|
827
|
-
accessToken,
|
|
828
|
-
"onboardUser",
|
|
829
|
-
onboardReq
|
|
830
|
-
);
|
|
831
|
-
while (!operation.done && operation.name) {
|
|
832
|
-
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
833
|
-
operation = await codeAssistGet(accessToken, operation.name);
|
|
834
|
-
}
|
|
835
|
-
const project = operation.response?.cloudaicompanionProject?.id ?? envProject;
|
|
836
|
-
if (!project) throwProjectError(loadRes);
|
|
837
|
-
return project;
|
|
602
|
+
function defaultRegistrationFile() {
|
|
603
|
+
return path2.join(getAppPaths().agentDir, "openai-chatgpt-client.json");
|
|
838
604
|
}
|
|
839
|
-
async function
|
|
605
|
+
async function writePrivateFile(file, content) {
|
|
606
|
+
await fs3.mkdir(path2.dirname(file), { recursive: true });
|
|
607
|
+
const tmp = `${file}.${process.pid}.${crypto3.randomUUID().slice(0, 8)}.tmp`;
|
|
840
608
|
try {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
metadata
|
|
844
|
-
});
|
|
609
|
+
await fs3.writeFile(tmp, content, { encoding: "utf-8", mode: 384 });
|
|
610
|
+
await fs3.rename(tmp, file);
|
|
845
611
|
} catch (err) {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
}
|
|
849
|
-
if (err instanceof CodeAssistHttpError && err.status === 403 && envProject === "cloudshell-gca") {
|
|
850
|
-
throw new Error(
|
|
851
|
-
"Access to the default Cloud Shell Gemini project was denied.\nPlease set your own Google Cloud project by running:\ngcloud config set project [PROJECT_ID]\nor setting export GOOGLE_CLOUD_PROJECT=...",
|
|
852
|
-
{ cause: err }
|
|
853
|
-
);
|
|
854
|
-
}
|
|
612
|
+
await fs3.unlink(tmp).catch(() => {
|
|
613
|
+
});
|
|
855
614
|
throw err;
|
|
856
615
|
}
|
|
857
616
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
);
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
const defaultTier = response.allowedTiers?.find((tier) => tier.isDefault);
|
|
865
|
-
return defaultTier ?? { id: USER_TIER_LEGACY, name: "" };
|
|
617
|
+
var HOST_ID_PATTERN = /^urn:uuid:[0-9a-f-]{36}$/i;
|
|
618
|
+
async function getChatGPTHostId(file = defaultHostIdFile()) {
|
|
619
|
+
const existing = await fs3.readFile(file, "utf-8").catch(() => "");
|
|
620
|
+
if (HOST_ID_PATTERN.test(existing.trim())) return existing.trim();
|
|
621
|
+
await writePrivateFile(file, `urn:uuid:${crypto3.randomUUID()}`);
|
|
622
|
+
return (await fs3.readFile(file, "utf-8")).trim();
|
|
866
623
|
}
|
|
867
|
-
function
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
624
|
+
async function readRegistration(file) {
|
|
625
|
+
try {
|
|
626
|
+
const value = JSON.parse(await fs3.readFile(file, "utf-8"));
|
|
627
|
+
if (typeof value.clientId === "string" && typeof value.subject === "string") {
|
|
628
|
+
return { clientId: value.clientId, subject: value.subject };
|
|
629
|
+
}
|
|
630
|
+
} catch {
|
|
871
631
|
}
|
|
872
|
-
|
|
873
|
-
"Gemini requires a Google Cloud project for this account. Set GOOGLE_CLOUD_PROJECT and try again."
|
|
874
|
-
);
|
|
632
|
+
return void 0;
|
|
875
633
|
}
|
|
876
|
-
async function
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
634
|
+
async function loginChatGPT(callbacks, options = {}) {
|
|
635
|
+
const hostId = await getChatGPTHostId(options.hostIdFile);
|
|
636
|
+
const registrationFile = options.registrationFile ?? defaultRegistrationFile();
|
|
637
|
+
const registration = await readRegistration(registrationFile);
|
|
638
|
+
const { verifier, challenge } = await generatePKCE();
|
|
639
|
+
const state = crypto3.randomBytes(16).toString("hex");
|
|
640
|
+
const nonce = crypto3.randomBytes(16).toString("hex");
|
|
641
|
+
const listener = await startCallbackListener(state, Boolean(registration));
|
|
642
|
+
try {
|
|
643
|
+
const redirectUri = `http://127.0.0.1:${listener.port}${CALLBACK_PATH}`;
|
|
644
|
+
const url = new URL(AUTHORIZE_URL);
|
|
645
|
+
url.searchParams.set("client_id", registration?.clientId ?? REGISTRATION_CLIENT_ID);
|
|
646
|
+
if (!registration) url.searchParams.set("agent_name_hint", AGENT_NAME);
|
|
647
|
+
url.searchParams.set("ext_agent_host_id", hostId);
|
|
648
|
+
if (registration && options.previous?.idToken) {
|
|
649
|
+
url.searchParams.set("id_token_hint", options.previous.idToken);
|
|
650
|
+
}
|
|
651
|
+
url.searchParams.set("response_type", "code");
|
|
652
|
+
url.searchParams.set("redirect_uri", redirectUri);
|
|
653
|
+
url.searchParams.set("scope", SCOPES);
|
|
654
|
+
url.searchParams.set("resource", RESOURCE);
|
|
655
|
+
url.searchParams.set("state", state);
|
|
656
|
+
url.searchParams.set("nonce", nonce);
|
|
657
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
658
|
+
url.searchParams.set("code_challenge", challenge);
|
|
659
|
+
const callback = await acquireCallback(url.toString(), state, listener, callbacks);
|
|
660
|
+
if (callback.get("error")) {
|
|
661
|
+
if (callback.get("error") === "access_denied") throw new ChatGPTPlanNotEnabledError();
|
|
662
|
+
throw new Error(
|
|
663
|
+
`ChatGPT sign-in failed: ${callback.get("error_description") ?? callback.get("error")}`
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
const code = callback.get("code");
|
|
667
|
+
if (!code) throw new Error("ChatGPT sign-in returned no authorization code.");
|
|
668
|
+
const issued = callback.get("client_id") ?? void 0;
|
|
669
|
+
if (registration && issued && issued !== registration.clientId) {
|
|
670
|
+
throw new Error("ChatGPT returned a different app registration. Start the sign-in again.");
|
|
671
|
+
}
|
|
672
|
+
const clientId = registration?.clientId ?? issued;
|
|
673
|
+
if (!clientId || clientId === REGISTRATION_CLIENT_ID) {
|
|
674
|
+
throw new Error(
|
|
675
|
+
"ChatGPT didn't finish registering GG Coder. Start the sign-in again, and paste the full callback URL if you're on another machine."
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
const tokens = await exchangeCode({ code, verifier, clientId, redirectUri });
|
|
679
|
+
const claims = await verifyIdToken(tokens.idToken, { clientId, nonce });
|
|
680
|
+
const subject = String(claims.sub ?? "");
|
|
681
|
+
if (!subject) throw new Error("ChatGPT ID token has no subject.");
|
|
682
|
+
if (registration && registration.subject !== subject) {
|
|
683
|
+
await fs3.unlink(registrationFile).catch(() => {
|
|
883
684
|
});
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
685
|
+
await revokeChatGPTSignIn({ refreshToken: tokens.refreshToken, clientId });
|
|
686
|
+
throw new Error(
|
|
687
|
+
"That was a different ChatGPT account. Continue with ChatGPT again to connect it."
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
if (!tokens.scopes.includes(CHATGPT_PLAN_SCOPE)) {
|
|
691
|
+
await revokeChatGPTSignIn({ refreshToken: tokens.refreshToken, clientId });
|
|
692
|
+
throw new ChatGPTPlanNotEnabledError();
|
|
693
|
+
}
|
|
694
|
+
if (!registration) {
|
|
695
|
+
await writePrivateFile(registrationFile, JSON.stringify({ clientId, subject }));
|
|
889
696
|
}
|
|
890
|
-
|
|
697
|
+
return {
|
|
698
|
+
accessToken: tokens.accessToken,
|
|
699
|
+
refreshToken: tokens.refreshToken,
|
|
700
|
+
expiresAt: Date.now() + tokens.expiresIn * 1e3,
|
|
701
|
+
expiresIn: tokens.expiresIn,
|
|
702
|
+
accountId: subject,
|
|
703
|
+
clientId,
|
|
704
|
+
idToken: tokens.idToken,
|
|
705
|
+
scopes: tokens.scopes,
|
|
706
|
+
baseUrl: CHATGPT_PLAN_BASE_URL
|
|
707
|
+
};
|
|
708
|
+
} finally {
|
|
709
|
+
listener.close();
|
|
891
710
|
}
|
|
892
|
-
throw lastError ?? new Error(`Gemini Code Assist ${method} failed.`);
|
|
893
711
|
}
|
|
894
|
-
async function
|
|
895
|
-
|
|
896
|
-
|
|
712
|
+
async function fetchChatGPTPlanModels(accessToken, fetchFn = fetch) {
|
|
713
|
+
const response = await fetchFn(`${CHATGPT_PLAN_BASE_URL}/models`, {
|
|
714
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
715
|
+
signal: oauthRequestSignal()
|
|
897
716
|
});
|
|
717
|
+
if (!response.ok) throw new Error(`ChatGPT model list failed (${response.status})`);
|
|
718
|
+
const data = await response.json();
|
|
719
|
+
return (data.models ?? []).filter((m) => typeof m.slug === "string" && (m.visibility ?? "list") === "list").map((m) => m.slug);
|
|
898
720
|
}
|
|
899
|
-
async function
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
signal: oauthRequestSignal()
|
|
721
|
+
async function startCallbackListener(expectedState, anyPort) {
|
|
722
|
+
let settle;
|
|
723
|
+
const result = new Promise((resolve, reject) => {
|
|
724
|
+
settle = { resolve, reject };
|
|
904
725
|
});
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
726
|
+
result.catch(() => {
|
|
727
|
+
});
|
|
728
|
+
const server = http.createServer((req, res) => {
|
|
729
|
+
const url = new URL(req.url || "", "http://127.0.0.1");
|
|
730
|
+
if (url.pathname !== CALLBACK_PATH) {
|
|
731
|
+
res.statusCode = 404;
|
|
732
|
+
res.end("Not found");
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (url.searchParams.get("state") !== expectedState) {
|
|
736
|
+
res.statusCode = 400;
|
|
737
|
+
res.end("State mismatch");
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
741
|
+
res.end(
|
|
742
|
+
"<html><body><h1>You're signed in to GG Coder.</h1><p>You can close this tab.</p></body></html>"
|
|
743
|
+
);
|
|
744
|
+
settle?.resolve(url.searchParams);
|
|
745
|
+
server.close();
|
|
746
|
+
});
|
|
747
|
+
const listen = (port2) => new Promise((resolve, reject) => {
|
|
748
|
+
const onError = (err) => reject(err);
|
|
749
|
+
server.once("error", onError);
|
|
750
|
+
server.listen(port2, "127.0.0.1", () => {
|
|
751
|
+
server.off("error", onError);
|
|
752
|
+
resolve(server.address().port);
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
let port;
|
|
756
|
+
for (const candidate of anyPort ? [PREFERRED_PORT, 0] : [PREFERRED_PORT]) {
|
|
757
|
+
try {
|
|
758
|
+
port = await listen(candidate);
|
|
759
|
+
break;
|
|
760
|
+
} catch {
|
|
761
|
+
}
|
|
908
762
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
return
|
|
763
|
+
if (port === void 0) {
|
|
764
|
+
settle?.reject(new Error("Could not start the local sign-in listener."));
|
|
765
|
+
return { port: PREFERRED_PORT, result, close: () => {
|
|
766
|
+
} };
|
|
767
|
+
}
|
|
768
|
+
const timeout = setTimeout(() => server.close(), CALLBACK_TIMEOUT_MS);
|
|
769
|
+
timeout.unref();
|
|
770
|
+
server.on("close", () => {
|
|
771
|
+
clearTimeout(timeout);
|
|
772
|
+
settle?.reject(new Error("Sign-in listener closed without a callback."));
|
|
773
|
+
});
|
|
774
|
+
return { port, result, close: () => server.close() };
|
|
921
775
|
}
|
|
922
|
-
function
|
|
923
|
-
|
|
776
|
+
async function acquireCallback(authUrl, expectedState, listener, callbacks) {
|
|
777
|
+
callbacks.onOpenUrl(authUrl);
|
|
778
|
+
callbacks.onStatus("Waiting for ChatGPT sign-in in your browser...");
|
|
779
|
+
const done = new AbortController();
|
|
780
|
+
try {
|
|
781
|
+
return await new Promise((resolve, reject) => {
|
|
782
|
+
let listenerFailed = false;
|
|
783
|
+
let pasteError;
|
|
784
|
+
const failIfExhausted = () => {
|
|
785
|
+
if (!listenerFailed || pasteError === void 0) return;
|
|
786
|
+
reject(
|
|
787
|
+
pasteError instanceof Error ? pasteError : new Error("Could not complete the sign-in.")
|
|
788
|
+
);
|
|
789
|
+
};
|
|
790
|
+
listener.result.then(resolve, () => {
|
|
791
|
+
listenerFailed = true;
|
|
792
|
+
failIfExhausted();
|
|
793
|
+
});
|
|
794
|
+
promptForCallback(expectedState, callbacks, done.signal).then(resolve, (err) => {
|
|
795
|
+
if (err instanceof FatalLoginError) {
|
|
796
|
+
reject(err);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
pasteError = err;
|
|
800
|
+
failIfExhausted();
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
} finally {
|
|
804
|
+
done.abort();
|
|
805
|
+
listener.close();
|
|
806
|
+
}
|
|
924
807
|
}
|
|
925
|
-
|
|
808
|
+
var FatalLoginError = class extends Error {
|
|
809
|
+
};
|
|
810
|
+
function parseCallbackInput(input) {
|
|
811
|
+
const value = input.trim();
|
|
812
|
+
if (!value) return void 0;
|
|
926
813
|
try {
|
|
927
|
-
|
|
928
|
-
if (!parsed || typeof parsed !== "object" || !("error" in parsed)) return false;
|
|
929
|
-
const details = parsed.error?.details;
|
|
930
|
-
return Array.isArray(details) ? details.some(
|
|
931
|
-
(detail) => detail != null && typeof detail === "object" && "reason" in detail && detail.reason === VPC_SC_REASON
|
|
932
|
-
) : false;
|
|
814
|
+
return new URL(value).searchParams;
|
|
933
815
|
} catch {
|
|
934
|
-
return false;
|
|
935
816
|
}
|
|
817
|
+
if (/[?&]?(code|error)=/.test(value)) {
|
|
818
|
+
return new URLSearchParams(value.replace(/^.*\?/, ""));
|
|
819
|
+
}
|
|
820
|
+
return void 0;
|
|
936
821
|
}
|
|
937
|
-
function
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
"
|
|
941
|
-
|
|
942
|
-
"
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
var DEFAULT_TOKEN_LIFETIME_SECONDS = 3600;
|
|
954
|
-
function issuer() {
|
|
955
|
-
return (process.env.XAI_OAUTH_ISSUER ?? DEFAULT_ISSUER).replace(/\/+$/, "");
|
|
956
|
-
}
|
|
957
|
-
function clientId() {
|
|
958
|
-
return process.env.XAI_OAUTH_CLIENT_ID ?? DEFAULT_CLIENT_ID2;
|
|
959
|
-
}
|
|
960
|
-
function grokCliBaseUrl() {
|
|
961
|
-
return (process.env.XAI_CLI_BASE_URL ?? DEFAULT_CLI_BASE_URL).replace(/\/+$/, "");
|
|
962
|
-
}
|
|
963
|
-
function grokCliVersion() {
|
|
964
|
-
const raw = process.env.GROK_CLI_VERSION ?? DEFAULT_GROK_CLI_VERSION;
|
|
965
|
-
const cleaned = raw.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
966
|
-
return cleaned.length > 0 ? cleaned : DEFAULT_GROK_CLI_VERSION;
|
|
967
|
-
}
|
|
968
|
-
function grokCliHeaders(modelId) {
|
|
969
|
-
return {
|
|
970
|
-
"X-XAI-Token-Auth": "xai-grok-cli",
|
|
971
|
-
"x-grok-client-version": grokCliVersion(),
|
|
972
|
-
"x-grok-client-identifier": "ggcoder",
|
|
973
|
-
...modelId ? { "x-grok-model-override": modelId } : {}
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
function isGrokCliEndpoint(baseUrl) {
|
|
977
|
-
if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
|
|
978
|
-
const normalized = baseUrl.replace(/\/+$/, "");
|
|
979
|
-
return normalized === grokCliBaseUrl() || /(^|\.)grok\.com/i.test(normalized);
|
|
822
|
+
async function promptForCallback(expectedState, callbacks, signal) {
|
|
823
|
+
let message = "If the browser is on another machine (SSH/headless), paste the full callback URL here:";
|
|
824
|
+
for (let attempt = 0; attempt < MAX_PASTE_ATTEMPTS; attempt++) {
|
|
825
|
+
if (signal.aborted) throw new Error("Callback already received.");
|
|
826
|
+
const raw = await callbacks.onPromptCode(message, signal);
|
|
827
|
+
if (signal.aborted) throw new Error("Callback already received.");
|
|
828
|
+
const params = parseCallbackInput(raw);
|
|
829
|
+
if (params && (params.get("code") || params.get("error"))) {
|
|
830
|
+
if (params.get("state") !== expectedState) {
|
|
831
|
+
throw new FatalLoginError("Sign-in state mismatch \u2014 start the sign-in again.");
|
|
832
|
+
}
|
|
833
|
+
return params;
|
|
834
|
+
}
|
|
835
|
+
message = "That wasn't a callback URL. Paste the full URL from the browser's address bar:";
|
|
836
|
+
}
|
|
837
|
+
throw new Error("No callback URL found in input.");
|
|
980
838
|
}
|
|
981
|
-
async function
|
|
982
|
-
|
|
839
|
+
async function postForm(url, form) {
|
|
840
|
+
return fetch(url, {
|
|
983
841
|
method: "POST",
|
|
984
|
-
headers: {
|
|
985
|
-
|
|
986
|
-
Accept: "application/json",
|
|
987
|
-
"User-Agent": `grok-cli/${grokCliVersion()}`
|
|
988
|
-
},
|
|
989
|
-
body: new URLSearchParams(params).toString(),
|
|
842
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
843
|
+
body: new URLSearchParams(form),
|
|
990
844
|
signal: oauthRequestSignal()
|
|
991
845
|
});
|
|
992
|
-
let data = {};
|
|
993
|
-
try {
|
|
994
|
-
const parsed = await response.json();
|
|
995
|
-
if (parsed && typeof parsed === "object") data = parsed;
|
|
996
|
-
} catch {
|
|
997
|
-
}
|
|
998
|
-
return { status: response.status, data };
|
|
999
|
-
}
|
|
1000
|
-
function errorDetail2(data) {
|
|
1001
|
-
const desc = data.error_description ?? data.message ?? data.error;
|
|
1002
|
-
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
1003
|
-
}
|
|
1004
|
-
function jwtExpirySeconds(token) {
|
|
1005
|
-
const payload = token.split(".")[1];
|
|
1006
|
-
if (!payload) return void 0;
|
|
1007
|
-
try {
|
|
1008
|
-
const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString(
|
|
1009
|
-
"utf-8"
|
|
1010
|
-
);
|
|
1011
|
-
const claims = JSON.parse(json);
|
|
1012
|
-
if (!claims || typeof claims !== "object") return void 0;
|
|
1013
|
-
const exp = claims.exp;
|
|
1014
|
-
return typeof exp === "number" && Number.isFinite(exp) && exp > 0 ? exp : void 0;
|
|
1015
|
-
} catch {
|
|
1016
|
-
return void 0;
|
|
1017
|
-
}
|
|
1018
846
|
}
|
|
1019
|
-
function
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
);
|
|
847
|
+
async function exchangeCode(input) {
|
|
848
|
+
const response = await postForm(TOKEN_URL, {
|
|
849
|
+
grant_type: "authorization_code",
|
|
850
|
+
client_id: input.clientId,
|
|
851
|
+
code: input.code,
|
|
852
|
+
code_verifier: input.verifier,
|
|
853
|
+
redirect_uri: input.redirectUri,
|
|
854
|
+
resource: RESOURCE
|
|
855
|
+
});
|
|
856
|
+
if (!response.ok) {
|
|
857
|
+
const text = await response.text().catch(() => "");
|
|
858
|
+
throw new Error(`ChatGPT token exchange failed (${response.status}): ${text.slice(0, 300)}`);
|
|
1030
859
|
}
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
expiresIn = responseExpiresIn;
|
|
1035
|
-
} else {
|
|
1036
|
-
const exp = jwtExpirySeconds(accessToken);
|
|
1037
|
-
const fromJwt = exp !== void 0 ? exp - Math.floor(Date.now() / 1e3) : 0;
|
|
1038
|
-
expiresIn = fromJwt > 0 ? fromJwt : DEFAULT_TOKEN_LIFETIME_SECONDS;
|
|
860
|
+
const data = await response.json();
|
|
861
|
+
if (!data.access_token || !data.refresh_token || !data.id_token) {
|
|
862
|
+
throw new Error("ChatGPT token response is missing tokens.");
|
|
1039
863
|
}
|
|
1040
864
|
return {
|
|
1041
|
-
accessToken,
|
|
1042
|
-
refreshToken,
|
|
1043
|
-
|
|
1044
|
-
expiresIn,
|
|
1045
|
-
|
|
865
|
+
accessToken: data.access_token,
|
|
866
|
+
refreshToken: data.refresh_token,
|
|
867
|
+
idToken: data.id_token,
|
|
868
|
+
expiresIn: data.expires_in ?? 3600,
|
|
869
|
+
scopes: (data.scope ?? "").split(/\s+/).filter(Boolean)
|
|
1046
870
|
};
|
|
1047
871
|
}
|
|
1048
|
-
async function
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
872
|
+
async function refreshChatGPTToken(creds) {
|
|
873
|
+
if (!creds.clientId) {
|
|
874
|
+
throw new Error("ChatGPT token refresh failed (401): no issued client ID saved");
|
|
875
|
+
}
|
|
876
|
+
const response = await postForm(TOKEN_URL, {
|
|
877
|
+
grant_type: "refresh_token",
|
|
878
|
+
client_id: creds.clientId,
|
|
879
|
+
refresh_token: creds.refreshToken,
|
|
880
|
+
resource: RESOURCE
|
|
1052
881
|
});
|
|
1053
|
-
if (
|
|
1054
|
-
|
|
882
|
+
if (!response.ok) {
|
|
883
|
+
const text = await response.text().catch(() => "");
|
|
884
|
+
throw new Error(`ChatGPT token refresh failed (${response.status}): ${text.slice(0, 300)}`);
|
|
1055
885
|
}
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
1060
|
-
throw new Error("Grok device authorization response missing user_code/device_code.");
|
|
886
|
+
const data = await response.json();
|
|
887
|
+
if (!data.access_token || !data.refresh_token) {
|
|
888
|
+
throw new Error("ChatGPT token refresh returned no tokens.");
|
|
1061
889
|
}
|
|
890
|
+
const expiresIn = data.expires_in ?? 3600;
|
|
1062
891
|
return {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
// rather than imposing our own budget — polling a code we know is dead only
|
|
1070
|
-
// burns requests, and a longer local window would keep a user waiting past
|
|
1071
|
-
// the point the code can ever succeed.
|
|
1072
|
-
expiresInMs: (Number(data.expires_in) || 0) > 0 ? Number(data.expires_in) * 1e3 : DEVICE_TIMEOUT_FALLBACK_MS
|
|
892
|
+
accessToken: data.access_token,
|
|
893
|
+
refreshToken: data.refresh_token,
|
|
894
|
+
expiresAt: Date.now() + expiresIn * 1e3,
|
|
895
|
+
expiresIn,
|
|
896
|
+
...data.id_token ? { idToken: data.id_token } : {},
|
|
897
|
+
...data.scope ? { scopes: data.scope.split(/\s+/).filter(Boolean) } : {}
|
|
1073
898
|
};
|
|
1074
899
|
}
|
|
1075
|
-
async function
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
switch (errorCode) {
|
|
1089
|
-
case "authorization_pending":
|
|
1090
|
-
return { kind: "pending" };
|
|
1091
|
-
case "slow_down":
|
|
1092
|
-
return { kind: "slow_down" };
|
|
1093
|
-
case "expired_token":
|
|
1094
|
-
return { kind: "expired" };
|
|
1095
|
-
case "access_denied":
|
|
1096
|
-
return { kind: "denied" };
|
|
1097
|
-
default:
|
|
1098
|
-
throw new Error(`Grok token polling failed (${status}): ${errorDetail2(data)}`);
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
function sleep2(ms) {
|
|
1102
|
-
return new Promise((resolve) => {
|
|
1103
|
-
setTimeout(resolve, ms);
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
async function loginXai(callbacks) {
|
|
1107
|
-
const auth = await requestDeviceAuthorization2();
|
|
1108
|
-
callbacks.onStatus(
|
|
1109
|
-
`Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
|
|
1110
|
-
);
|
|
1111
|
-
callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
|
|
1112
|
-
callbacks.onStatus("Waiting for you to authorize in the browser...");
|
|
1113
|
-
const deadline = Date.now() + auth.expiresInMs;
|
|
1114
|
-
let interval = Math.max(auth.interval, 1);
|
|
1115
|
-
while (Date.now() < deadline) {
|
|
1116
|
-
const remaining = deadline - Date.now();
|
|
1117
|
-
await sleep2(Math.min(interval * 1e3, remaining));
|
|
1118
|
-
const result = await pollDeviceToken2(auth.deviceCode);
|
|
1119
|
-
if (result.kind === "success") return result.creds;
|
|
1120
|
-
if (result.kind === "denied") {
|
|
1121
|
-
throw new Error("Grok authorization was denied.");
|
|
1122
|
-
}
|
|
1123
|
-
if (result.kind === "expired") {
|
|
1124
|
-
throw new Error("Grok device code expired. Please run login again.");
|
|
900
|
+
async function revokeChatGPTSignIn(creds, options = {}) {
|
|
901
|
+
if (!creds.clientId || !creds.refreshToken) return false;
|
|
902
|
+
const attempts = options.attempts ?? 3;
|
|
903
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
904
|
+
try {
|
|
905
|
+
const response = await postForm(REVOKE_URL, {
|
|
906
|
+
token: creds.refreshToken,
|
|
907
|
+
token_type_hint: "refresh_token",
|
|
908
|
+
client_id: creds.clientId
|
|
909
|
+
});
|
|
910
|
+
if (response.ok) return true;
|
|
911
|
+
if (response.status < 500) return false;
|
|
912
|
+
} catch {
|
|
1125
913
|
}
|
|
1126
|
-
if (
|
|
1127
|
-
|
|
914
|
+
if (attempt < attempts - 1) {
|
|
915
|
+
await new Promise((r) => setTimeout(r, (options.backoffMs ?? 500) * 2 ** attempt));
|
|
1128
916
|
}
|
|
1129
917
|
}
|
|
1130
|
-
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
function decodeSegment(segment) {
|
|
921
|
+
return JSON.parse(Buffer.from(segment, "base64url").toString("utf-8"));
|
|
922
|
+
}
|
|
923
|
+
async function verifyIdToken(idToken, expected) {
|
|
924
|
+
const [headerSeg, payloadSeg, signatureSeg] = idToken.split(".");
|
|
925
|
+
if (!headerSeg || !payloadSeg || !signatureSeg) throw new Error("Malformed ChatGPT ID token.");
|
|
926
|
+
const header = decodeSegment(headerSeg);
|
|
927
|
+
if (header.alg !== "RS256") throw new Error("Unexpected ChatGPT ID token algorithm.");
|
|
928
|
+
const response = await fetch(JWKS_URL, { signal: oauthRequestSignal() });
|
|
929
|
+
if (!response.ok) throw new Error(`Could not load OpenAI signing keys (${response.status}).`);
|
|
930
|
+
const { keys = [] } = await response.json();
|
|
931
|
+
const jwk = keys.find((k) => k.kid === header.kid);
|
|
932
|
+
if (!jwk) throw new Error("ChatGPT ID token is signed with an unknown key.");
|
|
933
|
+
const valid = crypto3.verify(
|
|
934
|
+
"RSA-SHA256",
|
|
935
|
+
Buffer.from(`${headerSeg}.${payloadSeg}`),
|
|
936
|
+
crypto3.createPublicKey({ key: jwk, format: "jwk" }),
|
|
937
|
+
Buffer.from(signatureSeg, "base64url")
|
|
938
|
+
);
|
|
939
|
+
if (!valid) throw new Error("ChatGPT ID token signature is invalid.");
|
|
940
|
+
const claims = decodeSegment(payloadSeg);
|
|
941
|
+
const now = Math.floor((expected.now ?? Date.now()) / 1e3);
|
|
942
|
+
const aud = claims.aud;
|
|
943
|
+
const audiences = Array.isArray(aud) ? aud : [aud];
|
|
944
|
+
if (claims.iss !== ISSUER) throw new Error("ChatGPT ID token has the wrong issuer.");
|
|
945
|
+
if (!audiences.includes(expected.clientId)) {
|
|
946
|
+
throw new Error("ChatGPT ID token was issued to another app.");
|
|
947
|
+
}
|
|
948
|
+
if (typeof claims.exp !== "number" || claims.exp + ID_TOKEN_CLOCK_SKEW_S < now) {
|
|
949
|
+
throw new Error("ChatGPT ID token has expired.");
|
|
1140
950
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
951
|
+
if (claims.nonce !== expected.nonce) throw new Error("ChatGPT ID token nonce mismatch.");
|
|
952
|
+
return claims;
|
|
1143
953
|
}
|
|
1144
954
|
|
|
1145
955
|
// src/file-lock.ts
|
|
1146
|
-
import
|
|
956
|
+
import fs4 from "fs/promises";
|
|
1147
957
|
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
1148
958
|
var STALE_TIMEOUT_MS = 1e4;
|
|
1149
959
|
var RETRY_INTERVAL_MS = 50;
|
|
@@ -1164,7 +974,7 @@ async function acquireLock(lockPath) {
|
|
|
1164
974
|
while (true) {
|
|
1165
975
|
try {
|
|
1166
976
|
const info = { pid: process.pid, timestamp: Date.now() };
|
|
1167
|
-
await
|
|
977
|
+
await fs4.writeFile(lockPath, JSON.stringify(info), { flag: "wx" });
|
|
1168
978
|
return;
|
|
1169
979
|
} catch (err) {
|
|
1170
980
|
const code = err.code;
|
|
@@ -1177,26 +987,26 @@ async function acquireLock(lockPath) {
|
|
|
1177
987
|
continue;
|
|
1178
988
|
}
|
|
1179
989
|
try {
|
|
1180
|
-
const content = await
|
|
990
|
+
const content = await fs4.readFile(lockPath, "utf-8");
|
|
1181
991
|
const info = JSON.parse(content);
|
|
1182
992
|
const isProcessAlive = isAlive(info.pid);
|
|
1183
993
|
const isStale = Date.now() - info.timestamp > STALE_TIMEOUT_MS;
|
|
1184
994
|
if (!isProcessAlive || isStale) {
|
|
1185
|
-
await
|
|
995
|
+
await fs4.unlink(lockPath).catch(() => {
|
|
1186
996
|
});
|
|
1187
997
|
continue;
|
|
1188
998
|
}
|
|
1189
999
|
} catch {
|
|
1190
|
-
const stat = await
|
|
1000
|
+
const stat = await fs4.stat(lockPath).catch(() => null);
|
|
1191
1001
|
if (!stat) continue;
|
|
1192
1002
|
if (Date.now() - stat.mtimeMs > STALE_TIMEOUT_MS) {
|
|
1193
|
-
await
|
|
1003
|
+
await fs4.unlink(lockPath).catch(() => {
|
|
1194
1004
|
});
|
|
1195
1005
|
continue;
|
|
1196
1006
|
}
|
|
1197
1007
|
}
|
|
1198
1008
|
if (Date.now() - startTime > MAX_WAIT_MS) {
|
|
1199
|
-
await
|
|
1009
|
+
await fs4.unlink(lockPath).catch(() => {
|
|
1200
1010
|
});
|
|
1201
1011
|
continue;
|
|
1202
1012
|
}
|
|
@@ -1206,14 +1016,14 @@ async function acquireLock(lockPath) {
|
|
|
1206
1016
|
}
|
|
1207
1017
|
async function lockFileExists(lockPath) {
|
|
1208
1018
|
try {
|
|
1209
|
-
await
|
|
1019
|
+
await fs4.stat(lockPath);
|
|
1210
1020
|
return true;
|
|
1211
1021
|
} catch (err) {
|
|
1212
1022
|
return err.code !== "ENOENT";
|
|
1213
1023
|
}
|
|
1214
1024
|
}
|
|
1215
1025
|
async function releaseLock(lockPath) {
|
|
1216
|
-
await
|
|
1026
|
+
await fs4.unlink(lockPath).catch(() => {
|
|
1217
1027
|
});
|
|
1218
1028
|
}
|
|
1219
1029
|
function isAlive(pid) {
|
|
@@ -1227,8 +1037,8 @@ function isAlive(pid) {
|
|
|
1227
1037
|
}
|
|
1228
1038
|
|
|
1229
1039
|
// src/logger.ts
|
|
1230
|
-
import
|
|
1231
|
-
import
|
|
1040
|
+
import fs5 from "fs";
|
|
1041
|
+
import path3 from "path";
|
|
1232
1042
|
import { randomBytes } from "crypto";
|
|
1233
1043
|
import { environmentSecrets, redactText, redactValue } from "@kenkaiiii/gg-ai";
|
|
1234
1044
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -1245,14 +1055,14 @@ var cleanups = [];
|
|
|
1245
1055
|
var exactSecrets = [];
|
|
1246
1056
|
function rotateIfNeeded(filePath, force = false) {
|
|
1247
1057
|
try {
|
|
1248
|
-
const st =
|
|
1058
|
+
const st = fs5.statSync(filePath);
|
|
1249
1059
|
if (!force && st.size <= MAX_BYTES - MIN_HEADROOM_BYTES) return;
|
|
1250
1060
|
const rotated = `${filePath}.1`;
|
|
1251
1061
|
try {
|
|
1252
|
-
|
|
1062
|
+
fs5.unlinkSync(rotated);
|
|
1253
1063
|
} catch {
|
|
1254
1064
|
}
|
|
1255
|
-
|
|
1065
|
+
fs5.renameSync(filePath, rotated);
|
|
1256
1066
|
} catch {
|
|
1257
1067
|
}
|
|
1258
1068
|
}
|
|
@@ -1260,18 +1070,18 @@ function rotateOpenLog() {
|
|
|
1260
1070
|
if (fd === null || logPath === null) return false;
|
|
1261
1071
|
const sizeBefore = fileBytes;
|
|
1262
1072
|
try {
|
|
1263
|
-
|
|
1073
|
+
fs5.closeSync(fd);
|
|
1264
1074
|
} catch {
|
|
1265
1075
|
}
|
|
1266
1076
|
fd = null;
|
|
1267
1077
|
rotateIfNeeded(logPath, true);
|
|
1268
1078
|
try {
|
|
1269
|
-
fd =
|
|
1270
|
-
fileBytes =
|
|
1079
|
+
fd = fs5.openSync(logPath, "a");
|
|
1080
|
+
fileBytes = fs5.fstatSync(fd).size;
|
|
1271
1081
|
} catch {
|
|
1272
1082
|
if (fd !== null) {
|
|
1273
1083
|
try {
|
|
1274
|
-
|
|
1084
|
+
fs5.closeSync(fd);
|
|
1275
1085
|
} catch {
|
|
1276
1086
|
}
|
|
1277
1087
|
}
|
|
@@ -1287,22 +1097,22 @@ function openLog(filePath, name) {
|
|
|
1287
1097
|
appName = name;
|
|
1288
1098
|
exactSecrets = environmentSecrets(process.env);
|
|
1289
1099
|
try {
|
|
1290
|
-
|
|
1100
|
+
fs5.mkdirSync(path3.dirname(filePath), { recursive: true, mode: 448 });
|
|
1291
1101
|
} catch {
|
|
1292
1102
|
}
|
|
1293
1103
|
rotateIfNeeded(filePath);
|
|
1294
1104
|
try {
|
|
1295
|
-
fd =
|
|
1105
|
+
fd = fs5.openSync(filePath, "a");
|
|
1296
1106
|
} catch {
|
|
1297
1107
|
fd = null;
|
|
1298
1108
|
fileBytes = 0;
|
|
1299
1109
|
return false;
|
|
1300
1110
|
}
|
|
1301
1111
|
try {
|
|
1302
|
-
fileBytes =
|
|
1112
|
+
fileBytes = fs5.fstatSync(fd).size;
|
|
1303
1113
|
} catch {
|
|
1304
1114
|
try {
|
|
1305
|
-
|
|
1115
|
+
fs5.closeSync(fd);
|
|
1306
1116
|
} catch {
|
|
1307
1117
|
}
|
|
1308
1118
|
fd = null;
|
|
@@ -1315,7 +1125,7 @@ function openLog(filePath, name) {
|
|
|
1315
1125
|
sessionId = randomBytes(4).toString("hex");
|
|
1316
1126
|
try {
|
|
1317
1127
|
if (fileBytes < MAX_BYTES) {
|
|
1318
|
-
const written =
|
|
1128
|
+
const written = fs5.writeSync(fd, "\n");
|
|
1319
1129
|
fileBytes += written;
|
|
1320
1130
|
processBytes += written;
|
|
1321
1131
|
}
|
|
@@ -1350,9 +1160,9 @@ function log(level, category, message, data) {
|
|
|
1350
1160
|
`;
|
|
1351
1161
|
try {
|
|
1352
1162
|
if (fileBytes + Buffer.byteLength(capLine) <= MAX_BYTES) {
|
|
1353
|
-
fileBytes +=
|
|
1163
|
+
fileBytes += fs5.writeSync(fd, capLine);
|
|
1354
1164
|
}
|
|
1355
|
-
|
|
1165
|
+
fs5.closeSync(fd);
|
|
1356
1166
|
} catch {
|
|
1357
1167
|
}
|
|
1358
1168
|
fd = null;
|
|
@@ -1362,7 +1172,7 @@ function log(level, category, message, data) {
|
|
|
1362
1172
|
if (fileBytes + lineBytes > MAX_BYTES && !rotationFailed && !rotateOpenLog()) return;
|
|
1363
1173
|
if (fd === null) return;
|
|
1364
1174
|
try {
|
|
1365
|
-
const written =
|
|
1175
|
+
const written = fs5.writeSync(fd, line);
|
|
1366
1176
|
fileBytes += written;
|
|
1367
1177
|
processBytes += written;
|
|
1368
1178
|
} catch {
|
|
@@ -1375,7 +1185,7 @@ function closeLogger(opts) {
|
|
|
1375
1185
|
if (fd !== null) {
|
|
1376
1186
|
if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
|
|
1377
1187
|
try {
|
|
1378
|
-
if (fd !== null)
|
|
1188
|
+
if (fd !== null) fs5.closeSync(fd);
|
|
1379
1189
|
} catch {
|
|
1380
1190
|
}
|
|
1381
1191
|
}
|
|
@@ -1391,7 +1201,7 @@ function closeLogger(opts) {
|
|
|
1391
1201
|
}
|
|
1392
1202
|
|
|
1393
1203
|
// src/hidden-providers.ts
|
|
1394
|
-
var HIDDEN_PROVIDERS = [
|
|
1204
|
+
var HIDDEN_PROVIDERS = [];
|
|
1395
1205
|
function isHiddenProvider(provider) {
|
|
1396
1206
|
return HIDDEN_PROVIDERS.includes(provider);
|
|
1397
1207
|
}
|
|
@@ -1482,22 +1292,76 @@ async function detectXiaomiTokenPlanBaseUrl(apiKey, options = {}) {
|
|
|
1482
1292
|
}
|
|
1483
1293
|
|
|
1484
1294
|
// src/auth-storage.ts
|
|
1485
|
-
var
|
|
1486
|
-
var
|
|
1487
|
-
var
|
|
1295
|
+
var OPENAI_CHATGPT_KEY = "openai-chatgpt";
|
|
1296
|
+
var KIMI_CODE_KEY = "kimi-code";
|
|
1297
|
+
var RETIRED_SIGN_INS = [
|
|
1298
|
+
{
|
|
1299
|
+
provider: "anthropic",
|
|
1300
|
+
storageKey: "anthropic",
|
|
1301
|
+
message: "Your old Claude sign-in no longer works. Sign in with Claude again to keep using your subscription."
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
provider: "openai",
|
|
1305
|
+
storageKey: "openai",
|
|
1306
|
+
message: "Your old ChatGPT sign-in no longer works. Continue with ChatGPT again, or add an OpenAI API key."
|
|
1307
|
+
},
|
|
1488
1308
|
{
|
|
1489
1309
|
provider: "moonshot",
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
apiKeyLabel: "Moonshot API key",
|
|
1493
|
-
restoreHint: 'Run "ggcoder login" and choose Kimi OAuth to restore OAuth auth.'
|
|
1310
|
+
storageKey: "moonshot-oauth",
|
|
1311
|
+
message: "Kimi sign-in was removed. Paste a Kimi Code plan key from the Kimi Code Console, or a Moonshot API key."
|
|
1494
1312
|
},
|
|
1495
1313
|
{
|
|
1496
1314
|
provider: "xai",
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1315
|
+
storageKey: "xai-oauth",
|
|
1316
|
+
message: "Grok sign-in was removed: xAI offers SuperGrok and X Premium usage only in apps it partners with. Add an xAI API key instead."
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
provider: "gemini",
|
|
1320
|
+
storageKey: "gemini",
|
|
1321
|
+
message: "Google sign-in was removed. Add a Gemini API key from Google AI Studio instead."
|
|
1322
|
+
}
|
|
1323
|
+
];
|
|
1324
|
+
function isRetiredEntry(retired, value) {
|
|
1325
|
+
if (!value) return false;
|
|
1326
|
+
switch (retired.storageKey) {
|
|
1327
|
+
case "anthropic":
|
|
1328
|
+
return isLegacyClaudeOAuth(value);
|
|
1329
|
+
// These slots now hold API keys; only a refreshable sign-in token is retired.
|
|
1330
|
+
case "openai":
|
|
1331
|
+
case "gemini":
|
|
1332
|
+
return Boolean(value.refreshToken) || Boolean(value.accountId) || Boolean(value.projectId);
|
|
1333
|
+
default:
|
|
1334
|
+
return true;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
function isRetiredStorageKey(data, storageKey) {
|
|
1338
|
+
return RETIRED_SIGN_INS.some(
|
|
1339
|
+
(r) => r.storageKey === storageKey && isRetiredEntry(r, data[storageKey])
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
function usableEntry(data, storageKey) {
|
|
1343
|
+
return isRetiredStorageKey(data, storageKey) ? void 0 : data[storageKey];
|
|
1344
|
+
}
|
|
1345
|
+
function storageKeyProvider(storageKey) {
|
|
1346
|
+
if (storageKey === OPENAI_CHATGPT_KEY) return "openai";
|
|
1347
|
+
if (storageKey === KIMI_CODE_KEY) return "moonshot";
|
|
1348
|
+
if (storageKey === XIAOMI_CREDITS_KEY) return "xiaomi";
|
|
1349
|
+
if (storageKey === GEMINI_API_KEY) return "gemini";
|
|
1350
|
+
return storageKey;
|
|
1351
|
+
}
|
|
1352
|
+
function apiKeyStorageKeys(provider) {
|
|
1353
|
+
if (provider === "moonshot") return [KIMI_CODE_KEY, "moonshot"];
|
|
1354
|
+
if (provider === "xiaomi") return ["xiaomi", XIAOMI_CREDITS_KEY];
|
|
1355
|
+
if (provider === "gemini") return [GEMINI_API_KEY];
|
|
1356
|
+
return [provider];
|
|
1357
|
+
}
|
|
1358
|
+
var DUAL_AUTH_PROVIDERS = [
|
|
1359
|
+
{
|
|
1360
|
+
provider: "openai",
|
|
1361
|
+
oauthKey: OPENAI_CHATGPT_KEY,
|
|
1362
|
+
oauthLabel: "ChatGPT plan",
|
|
1363
|
+
apiKeyLabel: "OpenAI API key",
|
|
1364
|
+
restoreHint: 'Run "ggcoder login" and choose Continue with ChatGPT to use your ChatGPT plan again.'
|
|
1501
1365
|
}
|
|
1502
1366
|
];
|
|
1503
1367
|
function dualAuthProvider(provider) {
|
|
@@ -1522,14 +1386,20 @@ function activeBaseUrlEntry(data, provider) {
|
|
|
1522
1386
|
const dual = dualAuthProvider(provider);
|
|
1523
1387
|
if (dual) {
|
|
1524
1388
|
const oauth = data[dual.oauthKey];
|
|
1389
|
+
const apiKey = usableEntry(data, dual.provider);
|
|
1525
1390
|
if (oauth) {
|
|
1526
1391
|
const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
|
|
1527
|
-
if (Date.now() < exhaustedUntil &&
|
|
1392
|
+
if (Date.now() < exhaustedUntil && apiKey) return apiKey;
|
|
1528
1393
|
return oauth;
|
|
1529
1394
|
}
|
|
1530
|
-
return
|
|
1395
|
+
return apiKey;
|
|
1396
|
+
}
|
|
1397
|
+
const key = apiKeyStorageKeys(provider).find((k) => usableEntry(data, k));
|
|
1398
|
+
const entry = key ? data[key] : void 0;
|
|
1399
|
+
if (key === KIMI_CODE_KEY && entry && !entry.baseUrl) {
|
|
1400
|
+
return { ...entry, baseUrl: KIMI_CODE_DEFAULT_BASE_URL };
|
|
1531
1401
|
}
|
|
1532
|
-
return
|
|
1402
|
+
return entry;
|
|
1533
1403
|
}
|
|
1534
1404
|
var MINIMAX_REGION_RETRY_MS = 5 * 60 * 1e3;
|
|
1535
1405
|
var miniMaxRegionProbes = /* @__PURE__ */ new Map();
|
|
@@ -1540,9 +1410,17 @@ function cachedMiniMaxRegion(apiKey) {
|
|
|
1540
1410
|
miniMaxRegionProbes.set(apiKey, { at: Date.now(), result });
|
|
1541
1411
|
return result;
|
|
1542
1412
|
}
|
|
1413
|
+
var kimiRegionProbes = /* @__PURE__ */ new Map();
|
|
1414
|
+
function cachedKimiRegion(apiKey) {
|
|
1415
|
+
const cached = kimiRegionProbes.get(apiKey);
|
|
1416
|
+
if (cached && Date.now() - cached.at < MINIMAX_REGION_RETRY_MS) return cached.result;
|
|
1417
|
+
const result = detectKimiCodeBaseUrl(apiKey).catch(() => void 0);
|
|
1418
|
+
kimiRegionProbes.set(apiKey, { at: Date.now(), result });
|
|
1419
|
+
return result;
|
|
1420
|
+
}
|
|
1543
1421
|
function readStoredBaseUrlSync(authFile, provider) {
|
|
1544
1422
|
try {
|
|
1545
|
-
const data = JSON.parse(
|
|
1423
|
+
const data = JSON.parse(readFileSync(authFile, "utf-8"));
|
|
1546
1424
|
return activeBaseUrlEntry(data, provider)?.baseUrl;
|
|
1547
1425
|
} catch {
|
|
1548
1426
|
return void 0;
|
|
@@ -1555,19 +1433,7 @@ function refreshThresholdMs(creds) {
|
|
|
1555
1433
|
return Math.max(MIN_REFRESH_THRESHOLD_MS, lifetimeMs * REFRESH_THRESHOLD_RATIO);
|
|
1556
1434
|
}
|
|
1557
1435
|
var USAGE_EXHAUSTED_DEFAULT_MS = 15 * 60 * 1e3;
|
|
1558
|
-
var
|
|
1559
|
-
"glm",
|
|
1560
|
-
"moonshot",
|
|
1561
|
-
"xiaomi",
|
|
1562
|
-
"minimax",
|
|
1563
|
-
"deepseek",
|
|
1564
|
-
"openrouter",
|
|
1565
|
-
"huggingface",
|
|
1566
|
-
"sakana",
|
|
1567
|
-
"xai",
|
|
1568
|
-
// Local endpoints: a fixed (usually placeholder) key, never refreshable.
|
|
1569
|
-
"local"
|
|
1570
|
-
]);
|
|
1436
|
+
var REFRESHABLE_STORAGE_KEYS = /* @__PURE__ */ new Set([OPENAI_CHATGPT_KEY]);
|
|
1571
1437
|
var AuthStorage = class {
|
|
1572
1438
|
data = {};
|
|
1573
1439
|
filePath;
|
|
@@ -1588,10 +1454,12 @@ var AuthStorage = class {
|
|
|
1588
1454
|
refreshLocks = /* @__PURE__ */ new Map();
|
|
1589
1455
|
detectMiniMaxRegion;
|
|
1590
1456
|
detectXiaomiCluster;
|
|
1457
|
+
detectKimiRegion;
|
|
1591
1458
|
constructor(filePath, options) {
|
|
1592
1459
|
this.filePath = filePath ?? getAppPaths().authFile;
|
|
1593
1460
|
this.detectMiniMaxRegion = options?.detectMiniMaxBaseUrl ?? cachedMiniMaxRegion;
|
|
1594
1461
|
this.detectXiaomiCluster = options?.detectXiaomiTokenPlanBaseUrl ?? detectXiaomiTokenPlanBaseUrl;
|
|
1462
|
+
this.detectKimiRegion = options?.detectKimiCodeBaseUrl ?? cachedKimiRegion;
|
|
1595
1463
|
}
|
|
1596
1464
|
/** Path to the on-disk auth file. Useful for status output. */
|
|
1597
1465
|
get path() {
|
|
@@ -1619,27 +1487,78 @@ var AuthStorage = class {
|
|
|
1619
1487
|
return keys.find((key) => Boolean(this.data[key]));
|
|
1620
1488
|
}
|
|
1621
1489
|
/**
|
|
1622
|
-
* True if the user has any usable auth for the logical provider
|
|
1623
|
-
*
|
|
1624
|
-
* satisfies it.
|
|
1490
|
+
* True if the user has any usable auth for the logical provider: the plan
|
|
1491
|
+
* sign-in or any of its API keys. A retired sign-in doesn't count.
|
|
1625
1492
|
*/
|
|
1626
1493
|
async hasProviderAuth(provider) {
|
|
1627
1494
|
if (isHiddenProvider(provider)) return false;
|
|
1628
1495
|
await this.ensureFresh();
|
|
1629
1496
|
const dual = dualAuthProvider(provider);
|
|
1630
1497
|
if (dual) {
|
|
1631
|
-
return Boolean(this.data[dual.oauthKey] || this.data
|
|
1632
|
-
}
|
|
1633
|
-
if (provider === "xiaomi") {
|
|
1634
|
-
return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
|
|
1498
|
+
return Boolean(this.data[dual.oauthKey] || usableEntry(this.data, dual.provider));
|
|
1635
1499
|
}
|
|
1636
|
-
if (provider === "
|
|
1637
|
-
return Boolean(this.data
|
|
1500
|
+
if (provider === "anthropic") {
|
|
1501
|
+
return Boolean(this.data.anthropic) && !isLegacyClaudeOAuth(this.data.anthropic) || isClaudeCodeAuthMarker(this.data[CLAUDE_CODE_AUTH_KEY]);
|
|
1638
1502
|
}
|
|
1639
1503
|
if (provider === "local") {
|
|
1640
1504
|
return Object.keys(this.data).some((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX));
|
|
1641
1505
|
}
|
|
1642
|
-
return Boolean(this.data
|
|
1506
|
+
return apiKeyStorageKeys(provider).some((key) => Boolean(usableEntry(this.data, key)));
|
|
1507
|
+
}
|
|
1508
|
+
/** True if any of `provider`'s API keys is stored. A retired sign-in in a key slot doesn't count. */
|
|
1509
|
+
async hasApiKey(provider) {
|
|
1510
|
+
await this.ensureFresh();
|
|
1511
|
+
if (provider === "anthropic" && isLegacyClaudeOAuth(this.data.anthropic)) return false;
|
|
1512
|
+
return apiKeyStorageKeys(provider).some((key) => Boolean(usableEntry(this.data, key)));
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* `provider`'s first stored API key, never its plan sign-in. For calls that
|
|
1516
|
+
* only an API key may make (OpenAI's Images API isn't on the ChatGPT plan).
|
|
1517
|
+
*/
|
|
1518
|
+
async resolveApiKey(provider) {
|
|
1519
|
+
await this.ensureFresh();
|
|
1520
|
+
for (const key of apiKeyStorageKeys(provider)) {
|
|
1521
|
+
const creds = usableEntry(this.data, key);
|
|
1522
|
+
if (creds && !(provider === "anthropic" && isLegacyClaudeOAuth(creds))) return creds;
|
|
1523
|
+
}
|
|
1524
|
+
throw new NotLoggedInError(provider);
|
|
1525
|
+
}
|
|
1526
|
+
/** True when the user signed in with Claude through Claude Code (marker only, no token). */
|
|
1527
|
+
async hasClaudeCodeLogin() {
|
|
1528
|
+
await this.ensureFresh();
|
|
1529
|
+
return isClaudeCodeAuthMarker(this.data[CLAUDE_CODE_AUTH_KEY]);
|
|
1530
|
+
}
|
|
1531
|
+
/** Record a Claude Code sign-in. Claude Code keeps the token; auth.json gets only the marker. */
|
|
1532
|
+
async setClaudeCodeLogin() {
|
|
1533
|
+
await this.mutateLatest((latest) => {
|
|
1534
|
+
latest[CLAUDE_CODE_AUTH_KEY] = { ...CLAUDE_CODE_AUTH_MARKER };
|
|
1535
|
+
if (isLegacyClaudeOAuth(latest.anthropic)) delete latest.anthropic;
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
/** Retired sign-ins still on disk, so the UI can ask the user to reconnect. */
|
|
1539
|
+
async legacySignIns() {
|
|
1540
|
+
await this.ensureFresh();
|
|
1541
|
+
return RETIRED_SIGN_INS.filter((r) => isRetiredEntry(r, this.data[r.storageKey]));
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Remove `provider`'s retired sign-in, if any. True when one was removed, so
|
|
1545
|
+
* the caller can tell the user once to reconnect.
|
|
1546
|
+
*/
|
|
1547
|
+
async takeLegacySignIn(provider) {
|
|
1548
|
+
const retired = RETIRED_SIGN_INS.find((r) => r.provider === provider);
|
|
1549
|
+
if (!retired) return false;
|
|
1550
|
+
await this.ensureFresh();
|
|
1551
|
+
if (!isRetiredEntry(retired, this.data[retired.storageKey])) return false;
|
|
1552
|
+
let removed = false;
|
|
1553
|
+
await this.mutateLatest((latest) => {
|
|
1554
|
+
if (!isRetiredEntry(retired, latest[retired.storageKey])) return;
|
|
1555
|
+
delete latest[retired.storageKey];
|
|
1556
|
+
removed = true;
|
|
1557
|
+
});
|
|
1558
|
+
return removed;
|
|
1559
|
+
}
|
|
1560
|
+
async clearClaudeCodeLogin() {
|
|
1561
|
+
await this.clearCredentials(CLAUDE_CODE_AUTH_KEY);
|
|
1643
1562
|
}
|
|
1644
1563
|
/** Endpoint ids that currently have a `local:<id>` credential stored. */
|
|
1645
1564
|
async listLocalEndpointIds() {
|
|
@@ -1674,18 +1593,18 @@ var AuthStorage = class {
|
|
|
1674
1593
|
const oauthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
1675
1594
|
if (dual && oauthCreds) {
|
|
1676
1595
|
const exhaustedUntil = oauthCreds.usageExhaustedUntil ?? 0;
|
|
1677
|
-
const apiKeyActive = Date.now() < exhaustedUntil && Boolean(this.data
|
|
1596
|
+
const apiKeyActive = Date.now() < exhaustedUntil && Boolean(usableEntry(this.data, dual.provider));
|
|
1678
1597
|
if (!apiKeyActive) return false;
|
|
1679
1598
|
}
|
|
1680
|
-
return
|
|
1599
|
+
return !REFRESHABLE_STORAGE_KEYS.has(provider);
|
|
1681
1600
|
}
|
|
1682
1601
|
/**
|
|
1683
1602
|
* The base URL on the credential that is active right now, if any.
|
|
1684
1603
|
* Synchronous — call only after load()/resolveCredentials() populated the
|
|
1685
|
-
* snapshot. For a dual-auth provider this is the
|
|
1686
|
-
*
|
|
1687
|
-
*
|
|
1688
|
-
*
|
|
1604
|
+
* snapshot. For a dual-auth provider this is the plan endpoint whenever the
|
|
1605
|
+
* sign-in is the one resolveCredentials would serve (i.e. not currently
|
|
1606
|
+
* usage-exhausted with an API key configured); for a plan/pay-per-token key
|
|
1607
|
+
* split it is the plan key's endpoint when one is stored.
|
|
1689
1608
|
*/
|
|
1690
1609
|
getStoredBaseUrl(provider) {
|
|
1691
1610
|
return activeBaseUrlEntry(this.data, provider)?.baseUrl;
|
|
@@ -1694,7 +1613,7 @@ var AuthStorage = class {
|
|
|
1694
1613
|
const first = !this.loaded;
|
|
1695
1614
|
await withFileLock(this.filePath, async () => {
|
|
1696
1615
|
try {
|
|
1697
|
-
const content = await
|
|
1616
|
+
const content = await fs6.readFile(this.filePath, "utf-8");
|
|
1698
1617
|
this.data = JSON.parse(content);
|
|
1699
1618
|
if (first) {
|
|
1700
1619
|
log("INFO", "auth", `Loaded credentials from ${this.filePath}`, {
|
|
@@ -1740,7 +1659,7 @@ var AuthStorage = class {
|
|
|
1740
1659
|
}
|
|
1741
1660
|
let changed;
|
|
1742
1661
|
try {
|
|
1743
|
-
const stat = await
|
|
1662
|
+
const stat = await fs6.stat(this.filePath);
|
|
1744
1663
|
changed = stat.ino !== this.snapshotIno || stat.mtimeMs !== this.snapshotMtimeMs || stat.size !== this.snapshotSize;
|
|
1745
1664
|
} catch {
|
|
1746
1665
|
changed = this.snapshotSize !== -1;
|
|
@@ -1753,7 +1672,7 @@ var AuthStorage = class {
|
|
|
1753
1672
|
*/
|
|
1754
1673
|
async rememberSnapshot() {
|
|
1755
1674
|
try {
|
|
1756
|
-
const stat = await
|
|
1675
|
+
const stat = await fs6.stat(this.filePath);
|
|
1757
1676
|
this.snapshotMtimeMs = stat.mtimeMs;
|
|
1758
1677
|
this.snapshotSize = stat.size;
|
|
1759
1678
|
this.snapshotIno = stat.ino;
|
|
@@ -1808,9 +1727,34 @@ var AuthStorage = class {
|
|
|
1808
1727
|
});
|
|
1809
1728
|
return { ...creds, baseUrl };
|
|
1810
1729
|
}
|
|
1811
|
-
|
|
1730
|
+
/**
|
|
1731
|
+
* A Kimi Code key saved without an endpoint gets the host that issued it
|
|
1732
|
+
* (`api.kimi.com` or `api.kimi.ai`), saved so the check runs once.
|
|
1733
|
+
*/
|
|
1734
|
+
async withKimiRegion(creds) {
|
|
1735
|
+
const baseUrl = await this.detectKimiRegion(creds.accessToken);
|
|
1736
|
+
if (!baseUrl) return { ...creds, baseUrl: KIMI_CODE_DEFAULT_BASE_URL };
|
|
1737
|
+
log("INFO", "auth", `Kimi Code key belongs to ${baseUrl} \u2014 saved for future requests`);
|
|
1738
|
+
await this.mutateLatest((latest) => {
|
|
1739
|
+
const current = latest[KIMI_CODE_KEY];
|
|
1740
|
+
if (current?.accessToken === creds.accessToken && !current.baseUrl) {
|
|
1741
|
+
current.baseUrl = baseUrl;
|
|
1742
|
+
}
|
|
1743
|
+
});
|
|
1744
|
+
return { ...creds, baseUrl };
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Store `creds` under `storageKey`. Connecting a provider again also removes
|
|
1748
|
+
* its retired sign-in, which is what the reconnect notice asked for.
|
|
1749
|
+
*/
|
|
1750
|
+
async setCredentials(storageKey, creds) {
|
|
1751
|
+
const provider = storageKeyProvider(storageKey);
|
|
1812
1752
|
await this.mutateLatest((latest) => {
|
|
1813
|
-
|
|
1753
|
+
for (const retired of RETIRED_SIGN_INS) {
|
|
1754
|
+
if (retired.provider !== provider || retired.storageKey === storageKey) continue;
|
|
1755
|
+
if (isRetiredEntry(retired, latest[retired.storageKey])) delete latest[retired.storageKey];
|
|
1756
|
+
}
|
|
1757
|
+
latest[storageKey] = creds;
|
|
1814
1758
|
});
|
|
1815
1759
|
}
|
|
1816
1760
|
/**
|
|
@@ -1847,9 +1791,9 @@ var AuthStorage = class {
|
|
|
1847
1791
|
* Mark the credential stored under `storageKey` as usage-exhausted until
|
|
1848
1792
|
* `resetsAt` (unix SECONDS, from the provider's rate-limit response) or a
|
|
1849
1793
|
* 15-minute default when no reset time is known. While the mark is in the
|
|
1850
|
-
* future, `resolveCredentials("
|
|
1851
|
-
*
|
|
1852
|
-
*
|
|
1794
|
+
* future, `resolveCredentials("openai")` serves the OpenAI API key instead of
|
|
1795
|
+
* the ChatGPT plan sign-in (when both are configured) — the plan stays the
|
|
1796
|
+
* preferred credential and is retried automatically once the mark
|
|
1853
1797
|
* lapses. Persisted to auth.json so a restart (or another gg-app window)
|
|
1854
1798
|
* doesn't burn a request rediscovering the same exhausted window. No-op if
|
|
1855
1799
|
* nothing is stored under `storageKey`.
|
|
@@ -1894,12 +1838,12 @@ var AuthStorage = class {
|
|
|
1894
1838
|
await this.ensureFresh();
|
|
1895
1839
|
}
|
|
1896
1840
|
const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : providerStorageKeys(provider);
|
|
1897
|
-
if (!directStorageKeys.some((key) => Boolean(this.data
|
|
1841
|
+
if (!directStorageKeys.some((key) => Boolean(usableEntry(this.data, key))) && !(provider === "anthropic" && this.data[CLAUDE_CODE_AUTH_KEY])) {
|
|
1898
1842
|
await this.reloadLatest();
|
|
1899
1843
|
}
|
|
1900
1844
|
if (opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider)) {
|
|
1901
1845
|
for (const key of opts.storageKeys) {
|
|
1902
|
-
const creds2 = this.data
|
|
1846
|
+
const creds2 = usableEntry(this.data, key);
|
|
1903
1847
|
if (!creds2) continue;
|
|
1904
1848
|
if (key !== provider && dualAuthProviderByOAuthKey(key)) {
|
|
1905
1849
|
return await this.resolveCredentials(key, {
|
|
@@ -1913,21 +1857,29 @@ var AuthStorage = class {
|
|
|
1913
1857
|
...opts.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
|
|
1914
1858
|
});
|
|
1915
1859
|
}
|
|
1860
|
+
if (key === KIMI_CODE_KEY && !creds2.baseUrl) return await this.withKimiRegion(creds2);
|
|
1916
1861
|
return creds2;
|
|
1917
1862
|
}
|
|
1918
1863
|
throw new NotLoggedInError(provider);
|
|
1919
1864
|
}
|
|
1865
|
+
if (provider === "anthropic" && (!this.data.anthropic || isLegacyClaudeOAuth(this.data.anthropic))) {
|
|
1866
|
+
if (isClaudeCodeAuthMarker(this.data[CLAUDE_CODE_AUTH_KEY])) {
|
|
1867
|
+
return { accessToken: "", refreshToken: "", expiresAt: Number.MAX_SAFE_INTEGER };
|
|
1868
|
+
}
|
|
1869
|
+
if (this.data.anthropic) throw new NotLoggedInError(provider);
|
|
1870
|
+
}
|
|
1920
1871
|
const dual = dualAuthProvider(provider);
|
|
1921
1872
|
const dualOAuthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
1873
|
+
const dualApiKey = dual ? usableEntry(this.data, dual.provider) : void 0;
|
|
1922
1874
|
if (dual && dualOAuthCreds) {
|
|
1923
1875
|
const exhaustedUntil = dualOAuthCreds.usageExhaustedUntil ?? 0;
|
|
1924
|
-
if (Date.now() < exhaustedUntil &&
|
|
1876
|
+
if (Date.now() < exhaustedUntil && dualApiKey) {
|
|
1925
1877
|
log(
|
|
1926
1878
|
"WARN",
|
|
1927
1879
|
"auth",
|
|
1928
1880
|
`${dual.oauthLabel} usage window is exhausted \u2014 using the ${dual.apiKeyLabel} until ${new Date(exhaustedUntil).toISOString()} (OAuth resumes automatically).`
|
|
1929
1881
|
);
|
|
1930
|
-
return
|
|
1882
|
+
return dualApiKey;
|
|
1931
1883
|
}
|
|
1932
1884
|
try {
|
|
1933
1885
|
return await this.resolveCredentials(dual.oauthKey, {
|
|
@@ -1935,23 +1887,24 @@ var AuthStorage = class {
|
|
|
1935
1887
|
...opts?.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
|
|
1936
1888
|
});
|
|
1937
1889
|
} catch (err) {
|
|
1938
|
-
if (err instanceof NotLoggedInError &&
|
|
1890
|
+
if (err instanceof NotLoggedInError && dualApiKey) {
|
|
1939
1891
|
log(
|
|
1940
1892
|
"WARN",
|
|
1941
1893
|
"auth",
|
|
1942
1894
|
`${dual.oauthLabel} credential is no longer valid \u2014 falling back to the ${dual.apiKeyLabel}. ${dual.restoreHint}`
|
|
1943
1895
|
);
|
|
1944
|
-
return
|
|
1896
|
+
return dualApiKey;
|
|
1945
1897
|
}
|
|
1946
1898
|
throw err;
|
|
1947
1899
|
}
|
|
1948
1900
|
}
|
|
1949
|
-
const creds = this.data
|
|
1901
|
+
const creds = usableEntry(this.data, provider);
|
|
1950
1902
|
if (!creds) {
|
|
1951
1903
|
throw new NotLoggedInError(provider);
|
|
1952
1904
|
}
|
|
1953
|
-
if (
|
|
1905
|
+
if (!REFRESHABLE_STORAGE_KEYS.has(provider)) {
|
|
1954
1906
|
if (provider === "minimax" && !creds.baseUrl) return await this.withMiniMaxRegion(creds);
|
|
1907
|
+
if (provider === KIMI_CODE_KEY && !creds.baseUrl) return await this.withKimiRegion(creds);
|
|
1955
1908
|
return creds;
|
|
1956
1909
|
}
|
|
1957
1910
|
if (!opts?.forceRefresh && Date.now() < creds.expiresAt - refreshThresholdMs(creds)) {
|
|
@@ -1979,13 +1932,14 @@ var AuthStorage = class {
|
|
|
1979
1932
|
this.data = latest;
|
|
1980
1933
|
return latestCreds;
|
|
1981
1934
|
}
|
|
1982
|
-
const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : provider === XAI_OAUTH_KEY ? refreshXaiToken : refreshOpenAIToken;
|
|
1983
1935
|
let refreshed;
|
|
1984
1936
|
try {
|
|
1985
|
-
refreshed = await
|
|
1937
|
+
refreshed = await refreshChatGPTToken(latestCreds);
|
|
1986
1938
|
} catch (err) {
|
|
1987
1939
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1988
|
-
const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh
|
|
1940
|
+
const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh|token_expired|refresh_token_(?:expired|invalidated|reused)/i.test(
|
|
1941
|
+
msg
|
|
1942
|
+
) || /unauthorized/i.test(msg);
|
|
1989
1943
|
if (isAuthFailure) {
|
|
1990
1944
|
const current = await readAuthData(this.filePath);
|
|
1991
1945
|
const currentCreds = current[provider];
|
|
@@ -2005,15 +1959,10 @@ var AuthStorage = class {
|
|
|
2005
1959
|
}
|
|
2006
1960
|
throw err;
|
|
2007
1961
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
refreshed.projectId = latestCreds.projectId;
|
|
2013
|
-
}
|
|
2014
|
-
if (!refreshed.baseUrl && latestCreds.baseUrl) {
|
|
2015
|
-
refreshed.baseUrl = latestCreds.baseUrl;
|
|
2016
|
-
}
|
|
1962
|
+
refreshed = {
|
|
1963
|
+
...pick(latestCreds, ["accountId", "baseUrl", "clientId", "idToken", "scopes"]),
|
|
1964
|
+
...refreshed
|
|
1965
|
+
};
|
|
2017
1966
|
latest[provider] = refreshed;
|
|
2018
1967
|
this.data = latest;
|
|
2019
1968
|
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
@@ -2036,9 +1985,14 @@ var AuthStorage = class {
|
|
|
2036
1985
|
return creds.accessToken;
|
|
2037
1986
|
}
|
|
2038
1987
|
};
|
|
1988
|
+
function pick(value, keys) {
|
|
1989
|
+
const out = {};
|
|
1990
|
+
for (const key of keys) if (value[key] !== void 0) out[key] = value[key];
|
|
1991
|
+
return out;
|
|
1992
|
+
}
|
|
2039
1993
|
async function readAuthData(filePath) {
|
|
2040
1994
|
try {
|
|
2041
|
-
const content = await
|
|
1995
|
+
const content = await fs6.readFile(filePath, "utf-8");
|
|
2042
1996
|
return JSON.parse(content);
|
|
2043
1997
|
} catch (error) {
|
|
2044
1998
|
if (error.code === "ENOENT") return {};
|
|
@@ -2046,12 +2000,12 @@ async function readAuthData(filePath) {
|
|
|
2046
2000
|
}
|
|
2047
2001
|
}
|
|
2048
2002
|
async function atomicWriteFile(filePath, content) {
|
|
2049
|
-
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${
|
|
2003
|
+
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${crypto4.randomUUID().slice(0, 8)}.tmp`;
|
|
2050
2004
|
try {
|
|
2051
|
-
await
|
|
2052
|
-
await
|
|
2005
|
+
await fs6.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
|
|
2006
|
+
await fs6.rename(tmpPath, filePath);
|
|
2053
2007
|
} catch (err) {
|
|
2054
|
-
await
|
|
2008
|
+
await fs6.unlink(tmpPath).catch(() => {
|
|
2055
2009
|
});
|
|
2056
2010
|
throw err;
|
|
2057
2011
|
}
|
|
@@ -2083,7 +2037,8 @@ var CODEX_FAST_BILLING = {
|
|
|
2083
2037
|
detail: "Codex Fast mode uses your ChatGPT plan limits at 2.5x the standard rate (purchased credits at 2x).",
|
|
2084
2038
|
paid: false
|
|
2085
2039
|
};
|
|
2086
|
-
var GEMINI_AUTH_KEYS = [GEMINI_API_KEY
|
|
2040
|
+
var GEMINI_AUTH_KEYS = [GEMINI_API_KEY];
|
|
2041
|
+
var KIMI_AUTH_KEYS = [KIMI_CODE_KEY, "moonshot"];
|
|
2087
2042
|
var MODELS = [
|
|
2088
2043
|
// ── Anthropic ──────────────────────────────────────────
|
|
2089
2044
|
// NOTE: Claude Mythos 5.1 (`claude-mythos-5-1`, 2026-09-01) is kept commented
|
|
@@ -2213,14 +2168,13 @@ var MODELS = [
|
|
|
2213
2168
|
{
|
|
2214
2169
|
// GPT-6 Astra — "Our most capable model for complex, demanding work."
|
|
2215
2170
|
// (Codex catalog priority 2, listed for every ChatGPT plan, requires a
|
|
2216
|
-
// Codex client >= 0.153.0
|
|
2217
|
-
// 1.05M on the public Responses API, 272K on the ChatGPT
|
|
2171
|
+
// Codex client >= 0.153.0). Same split as 5.6:
|
|
2172
|
+
// 1.05M on the public Responses API, 272K on the ChatGPT plan route
|
|
2218
2173
|
// (openai/codex models.json, `gpt-6-astra`). Reasoning ladder low → medium
|
|
2219
2174
|
// → high → xhigh → max → ultra; `ultra` is the Codex orchestration preset
|
|
2220
2175
|
// (multi_agent v2) and is Codex-only — the public API tops out at `max`.
|
|
2221
|
-
//
|
|
2222
|
-
//
|
|
2223
|
-
// Codex route is the supported way to use it as an agent.
|
|
2176
|
+
// OpenAI requires the Responses API for tool calling on Astra, so both
|
|
2177
|
+
// the ChatGPT plan and API keys ride gg-ai's Responses transport.
|
|
2224
2178
|
id: "gpt-6-astra",
|
|
2225
2179
|
name: "GPT-6 Astra",
|
|
2226
2180
|
provider: "openai",
|
|
@@ -2254,18 +2208,16 @@ var MODELS = [
|
|
|
2254
2208
|
billing: CODEX_FAST_BILLING
|
|
2255
2209
|
},
|
|
2256
2210
|
// GPT-6 Sol + Luna — released 2026-09-22 below Astra, replacing the whole
|
|
2257
|
-
// GPT-5.6 family (Sol/Terra/Luna; there
|
|
2211
|
+
// GPT-5.6 family (Sol/Terra/Luna; there was no GPT-6 Terra — OpenAI's Codex
|
|
2258
2212
|
// catalog upgrades 5.6 Terra to 6 Sol). GPT-6.1 Sol (2026-09-29) then replaced
|
|
2259
|
-
// GPT-6 Sol. Same window split as Astra: 1.05M on the public Responses API,
|
|
2213
|
+
// GPT-6 Sol, and GPT-6.1 Terra (2026-10-26) sits between Sol and Luna. Same window split as Astra: 1.05M on the public Responses API,
|
|
2260
2214
|
// 272K on the Codex route; 128K output, text+image input, freeform
|
|
2261
2215
|
// apply_patch, responses-lite transport. The GPT-5.6 ids and `gpt-6-sol` are
|
|
2262
2216
|
// retired — a saved session on one falls back to the provider default on
|
|
2263
2217
|
// next start.
|
|
2264
2218
|
{
|
|
2265
2219
|
// GPT-6.1 Sol — "Latest workhorse model for coding and everyday work."
|
|
2266
|
-
// (Codex priority 1, default low).
|
|
2267
|
-
// but the ChatGPT backend only serves it from 0.159.0 (see
|
|
2268
|
-
// CODEX_CLIENT_VERSION). $2/$10 MTok, cached input $0.10. Ladder low →
|
|
2220
|
+
// (Codex priority 1, default low). $2/$10 MTok, cached input $0.10. Ladder low →
|
|
2269
2221
|
// medium → high → xhigh → max → ultra; ultra is the Codex orchestration
|
|
2270
2222
|
// preset (xhigh effort on the wire + proactive local subagent delegation).
|
|
2271
2223
|
id: "gpt-6.1-sol",
|
|
@@ -2298,6 +2250,40 @@ var MODELS = [
|
|
|
2298
2250
|
maxThinkingLevel: "ultra",
|
|
2299
2251
|
billing: CODEX_FAST_BILLING
|
|
2300
2252
|
},
|
|
2253
|
+
{
|
|
2254
|
+
// GPT-6.1 Terra (Codex priority 7, default low, client >= 0.153.0). $1/$5
|
|
2255
|
+
// MTok, cached input $0.05. Unlike Sol/Astra the Codex route gets a 500K
|
|
2256
|
+
// window (catalog `context_window`). Same ladder and Ultra preset as Sol.
|
|
2257
|
+
id: "gpt-6.1-terra",
|
|
2258
|
+
name: "GPT-6.1 Terra",
|
|
2259
|
+
provider: "openai",
|
|
2260
|
+
contextWindow: 105e4,
|
|
2261
|
+
codexContextWindow: 5e5,
|
|
2262
|
+
maxOutputTokens: 128e3,
|
|
2263
|
+
supportsThinking: true,
|
|
2264
|
+
defaultThinkingLevel: "low",
|
|
2265
|
+
supportsImages: true,
|
|
2266
|
+
supportsVideo: false,
|
|
2267
|
+
costTier: "medium",
|
|
2268
|
+
maxThinkingLevel: "ultra"
|
|
2269
|
+
},
|
|
2270
|
+
{
|
|
2271
|
+
// Codex Fast mode for 6.1 Terra ("1.5x speed") — see `gpt-6-astra-fast`
|
|
2272
|
+
// for the wire mapping and billing.
|
|
2273
|
+
id: "gpt-6.1-terra-fast",
|
|
2274
|
+
name: "GPT-6.1 Terra Fast",
|
|
2275
|
+
provider: "openai",
|
|
2276
|
+
contextWindow: 105e4,
|
|
2277
|
+
codexContextWindow: 5e5,
|
|
2278
|
+
maxOutputTokens: 128e3,
|
|
2279
|
+
supportsThinking: true,
|
|
2280
|
+
defaultThinkingLevel: "low",
|
|
2281
|
+
supportsImages: true,
|
|
2282
|
+
supportsVideo: false,
|
|
2283
|
+
costTier: "medium",
|
|
2284
|
+
maxThinkingLevel: "ultra",
|
|
2285
|
+
billing: CODEX_FAST_BILLING
|
|
2286
|
+
},
|
|
2301
2287
|
{
|
|
2302
2288
|
// Luna — "Fast and affordable model for easier tasks." (Codex priority 4,
|
|
2303
2289
|
// default medium, needs a Codex client >= 0.155.0). $0.10/$0.50 MTok.
|
|
@@ -2418,8 +2404,8 @@ var MODELS = [
|
|
|
2418
2404
|
maxThinkingLevel: "high",
|
|
2419
2405
|
authStorageKeys: GEMINI_AUTH_KEYS
|
|
2420
2406
|
},
|
|
2421
|
-
// Keep 3.1 Flash Lite first for the
|
|
2422
|
-
//
|
|
2407
|
+
// Keep 3.1 Flash Lite first for the default and fast-model routing.
|
|
2408
|
+
// Gemini 3.7
|
|
2423
2409
|
// Flash is retired: Google deprecated it on 2026-10-08 and routes every
|
|
2424
2410
|
// `gemini-3.7-flash` request to 3.8 Flash (ai.google.dev/gemini-api/docs/
|
|
2425
2411
|
// changelog), so a saved session on it falls back to the provider default.
|
|
@@ -2452,9 +2438,7 @@ var MODELS = [
|
|
|
2452
2438
|
authStorageKeys: GEMINI_AUTH_KEYS
|
|
2453
2439
|
},
|
|
2454
2440
|
{
|
|
2455
|
-
// Wire name `gemini-3-flash
|
|
2456
|
-
// display string `gemini-3.5-flash` with a 404, so gemini-cli keeps this
|
|
2457
|
-
// alternative name (SECONDARY_GEMINI_3_5_FLASH_MODEL) for that endpoint.
|
|
2441
|
+
// Wire name `gemini-3-flash`, the id Google serves 3.5 Flash under.
|
|
2458
2442
|
id: "gemini-3-flash",
|
|
2459
2443
|
name: "Gemini 3.5 Flash",
|
|
2460
2444
|
provider: "gemini",
|
|
@@ -2469,9 +2453,7 @@ var MODELS = [
|
|
|
2469
2453
|
authStorageKeys: GEMINI_AUTH_KEYS
|
|
2470
2454
|
},
|
|
2471
2455
|
{
|
|
2472
|
-
// Gemini 3.1 Pro is public preview
|
|
2473
|
-
// enablement, so free/personal OAuth accounts 404 on it (see
|
|
2474
|
-
// ACCOUNT_GATED_MODELS in gg-ai's gemini provider).
|
|
2456
|
+
// Gemini 3.1 Pro is public preview.
|
|
2475
2457
|
id: "gemini-3.1-pro-preview",
|
|
2476
2458
|
name: "Gemini 3.1 Pro (Preview)",
|
|
2477
2459
|
provider: "gemini",
|
|
@@ -2488,8 +2470,8 @@ var MODELS = [
|
|
|
2488
2470
|
// ── Moonshot (Kimi) ────────────────────────────────────
|
|
2489
2471
|
// K3 is Kimi's 2.8T-parameter flagship for long-horizon coding, knowledge
|
|
2490
2472
|
// work, and deep reasoning. Its effort ladder is server-declared as
|
|
2491
|
-
// low/high/max on both the public API (default max) and the Kimi
|
|
2492
|
-
//
|
|
2473
|
+
// low/high/max on both the public API (default max) and the Kimi Code plan
|
|
2474
|
+
// endpoint (default high); thinking can also be fully disabled.
|
|
2493
2475
|
{
|
|
2494
2476
|
id: "kimi-k3",
|
|
2495
2477
|
name: "Kimi K3",
|
|
@@ -2503,13 +2485,14 @@ var MODELS = [
|
|
|
2503
2485
|
supportsVideo: true,
|
|
2504
2486
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
2505
2487
|
costTier: "high",
|
|
2506
|
-
maxThinkingLevel: "max"
|
|
2488
|
+
maxThinkingLevel: "max",
|
|
2489
|
+
authStorageKeys: KIMI_AUTH_KEYS
|
|
2507
2490
|
},
|
|
2508
|
-
// K2.8 Preview (2026-09-11) is served only on the Kimi
|
|
2509
|
-
//
|
|
2491
|
+
// K2.8 Preview (2026-09-11) is served only on the Kimi Code plan endpoint,
|
|
2492
|
+
// under its rolling `kimi-for-coding` id (live /models, 2026-09-28:
|
|
2510
2493
|
// display_name "K2.8 Preview", 1M context, image + video input, efforts
|
|
2511
|
-
// low/high/max default max). The public API
|
|
2512
|
-
//
|
|
2494
|
+
// low/high/max default max). The public Moonshot API does not serve it, so
|
|
2495
|
+
// it resolves from a Kimi Code plan key only.
|
|
2513
2496
|
{
|
|
2514
2497
|
id: "kimi-for-coding",
|
|
2515
2498
|
name: "Kimi K2.8 Preview",
|
|
@@ -2522,7 +2505,7 @@ var MODELS = [
|
|
|
2522
2505
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
2523
2506
|
costTier: "medium",
|
|
2524
2507
|
maxThinkingLevel: "max",
|
|
2525
|
-
authStorageKeys: [
|
|
2508
|
+
authStorageKeys: [KIMI_CODE_KEY]
|
|
2526
2509
|
},
|
|
2527
2510
|
// K2.7 Code is requested by its pinned id (not the `kimi-for-coding` alias
|
|
2528
2511
|
// that moved to K2.8), so it stays the real K2.7 on both endpoints.
|
|
@@ -2537,7 +2520,8 @@ var MODELS = [
|
|
|
2537
2520
|
supportsVideo: true,
|
|
2538
2521
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
2539
2522
|
costTier: "medium",
|
|
2540
|
-
maxThinkingLevel: "high"
|
|
2523
|
+
maxThinkingLevel: "high",
|
|
2524
|
+
authStorageKeys: KIMI_AUTH_KEYS
|
|
2541
2525
|
},
|
|
2542
2526
|
// K2.7 Code HighSpeed — the same model as K2.7 Code served at ~180 tok/s
|
|
2543
2527
|
// (up to ~260 on short contexts), 256K context (platform.kimi.ai model list,
|
|
@@ -2556,7 +2540,8 @@ var MODELS = [
|
|
|
2556
2540
|
supportsVideo: true,
|
|
2557
2541
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
2558
2542
|
costTier: "medium",
|
|
2559
|
-
maxThinkingLevel: "high"
|
|
2543
|
+
maxThinkingLevel: "high",
|
|
2544
|
+
authStorageKeys: KIMI_AUTH_KEYS
|
|
2560
2545
|
},
|
|
2561
2546
|
// ── Z.AI (GLM) ─────────────────────────────────────────
|
|
2562
2547
|
// Two GLM entries, both live on the coding endpoint (verified against its
|
|
@@ -2969,26 +2954,40 @@ function getSummaryModel(provider, currentModelId) {
|
|
|
2969
2954
|
}
|
|
2970
2955
|
|
|
2971
2956
|
export {
|
|
2972
|
-
|
|
2973
|
-
|
|
2957
|
+
KIMI_CODE_DEFAULT_BASE_URL,
|
|
2958
|
+
KIMI_CODE_OVERSEAS_BASE_URL,
|
|
2959
|
+
detectKimiCodeBaseUrl,
|
|
2974
2960
|
isKimiCodingEndpoint,
|
|
2975
|
-
|
|
2976
|
-
|
|
2961
|
+
CLAUDE_CODE_MANIFEST,
|
|
2962
|
+
CLAUDE_CODE_AUTH_KEY,
|
|
2963
|
+
CLAUDE_CODE_AUTH_MARKER,
|
|
2964
|
+
isClaudeCodeAuthMarker,
|
|
2965
|
+
isLegacyClaudeOAuth,
|
|
2966
|
+
claudeCodePlatformKey,
|
|
2967
|
+
claudeCodeHome,
|
|
2968
|
+
claudeCodeConfigDir,
|
|
2969
|
+
claudeCodeBinaryPath,
|
|
2970
|
+
resolveClaudeCodeBinary,
|
|
2971
|
+
ensureClaudeCodeBinary,
|
|
2972
|
+
formatClaudeCodeInstallProgress,
|
|
2973
|
+
claudeCodeEnv,
|
|
2974
|
+
claudeCodeAuthStatus,
|
|
2975
|
+
logoutClaudeCode,
|
|
2976
|
+
claudeCodeCallbackUrl,
|
|
2977
|
+
loginClaudeCode,
|
|
2977
2978
|
generatePKCE,
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
loginXai,
|
|
2991
|
-
refreshXaiToken,
|
|
2979
|
+
CHATGPT_PLAN_SCOPE,
|
|
2980
|
+
CHATGPT_PLAN_BASE_URL,
|
|
2981
|
+
CHATGPT_USAGE_URL,
|
|
2982
|
+
CHATGPT_PLAN_NOT_ENABLED_MESSAGE,
|
|
2983
|
+
ChatGPTPlanNotEnabledError,
|
|
2984
|
+
getChatGPTHostId,
|
|
2985
|
+
loginChatGPT,
|
|
2986
|
+
fetchChatGPTPlanModels,
|
|
2987
|
+
parseCallbackInput,
|
|
2988
|
+
refreshChatGPTToken,
|
|
2989
|
+
revokeChatGPTSignIn,
|
|
2990
|
+
verifyIdToken,
|
|
2992
2991
|
withFileLock,
|
|
2993
2992
|
openLog,
|
|
2994
2993
|
getSessionId,
|
|
@@ -3003,8 +3002,11 @@ export {
|
|
|
3003
3002
|
detectMiniMaxBaseUrl,
|
|
3004
3003
|
XIAOMI_TOKEN_PLAN_BASE_URLS,
|
|
3005
3004
|
detectXiaomiTokenPlanBaseUrl,
|
|
3006
|
-
|
|
3007
|
-
|
|
3005
|
+
OPENAI_CHATGPT_KEY,
|
|
3006
|
+
KIMI_CODE_KEY,
|
|
3007
|
+
RETIRED_SIGN_INS,
|
|
3008
|
+
storageKeyProvider,
|
|
3009
|
+
apiKeyStorageKeys,
|
|
3008
3010
|
dualAuthProvider,
|
|
3009
3011
|
dualAuthProviderByOAuthKey,
|
|
3010
3012
|
oauthStorageKey,
|
|
@@ -3039,4 +3041,4 @@ export {
|
|
|
3039
3041
|
getDefaultThinkingLevel,
|
|
3040
3042
|
getSummaryModel
|
|
3041
3043
|
};
|
|
3042
|
-
//# sourceMappingURL=chunk-
|
|
3044
|
+
//# sourceMappingURL=chunk-J7W5HS6X.js.map
|