@kenkaiiii/gg-core 5.94.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-JLEBGMG4.js → chunk-J7W5HS6X.js} +622 -1105
- package/dist/chunk-J7W5HS6X.js.map +1 -0
- package/dist/index.cjs +816 -1310
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -157
- package/dist/index.d.ts +165 -157
- package/dist/index.js +153 -160
- package/dist/index.js.map +1 -1
- package/dist/model-registry.cjs +57 -75
- 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 +1 -1
- package/package.json +2 -2
- package/dist/chunk-JLEBGMG4.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -32,29 +32,38 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AUTH_PROVIDERS: () => AUTH_PROVIDERS,
|
|
34
34
|
AuthStorage: () => AuthStorage,
|
|
35
|
+
CHATGPT_PLAN_BASE_URL: () => CHATGPT_PLAN_BASE_URL,
|
|
36
|
+
CHATGPT_PLAN_NOT_ENABLED_MESSAGE: () => CHATGPT_PLAN_NOT_ENABLED_MESSAGE,
|
|
37
|
+
CHATGPT_PLAN_SCOPE: () => CHATGPT_PLAN_SCOPE,
|
|
38
|
+
CHATGPT_USAGE_URL: () => CHATGPT_USAGE_URL,
|
|
35
39
|
CLAUDE_CODE_AUTH_KEY: () => CLAUDE_CODE_AUTH_KEY,
|
|
36
40
|
CLAUDE_CODE_AUTH_MARKER: () => CLAUDE_CODE_AUTH_MARKER,
|
|
37
41
|
CLAUDE_CODE_MANIFEST: () => CLAUDE_CODE_MANIFEST,
|
|
42
|
+
ChatGPTPlanNotEnabledError: () => ChatGPTPlanNotEnabledError,
|
|
38
43
|
DEFAULT_LOCAL_ENDPOINTS: () => DEFAULT_LOCAL_ENDPOINTS,
|
|
39
44
|
DEFAULT_MAX_VIDEO_BYTES: () => DEFAULT_MAX_VIDEO_BYTES,
|
|
40
45
|
FALLBACK_CONTEXT_WINDOW: () => FALLBACK_CONTEXT_WINDOW,
|
|
41
46
|
GEMINI_API_KEY: () => GEMINI_API_KEY,
|
|
42
47
|
GEMINI_API_KEY_BASE_URL: () => GEMINI_API_KEY_BASE_URL,
|
|
43
48
|
HIDDEN_PROVIDERS: () => HIDDEN_PROVIDERS,
|
|
49
|
+
KIMI_CODE_DEFAULT_BASE_URL: () => KIMI_CODE_DEFAULT_BASE_URL,
|
|
50
|
+
KIMI_CODE_KEY: () => KIMI_CODE_KEY,
|
|
51
|
+
KIMI_CODE_OVERSEAS_BASE_URL: () => KIMI_CODE_OVERSEAS_BASE_URL,
|
|
44
52
|
LOCAL_API_KEY_PLACEHOLDER: () => LOCAL_API_KEY_PLACEHOLDER,
|
|
45
53
|
LOCAL_AUTH_KEY_PREFIX: () => LOCAL_AUTH_KEY_PREFIX,
|
|
46
54
|
MINIMAX_CN_BASE_URL: () => MINIMAX_CN_BASE_URL,
|
|
47
55
|
MINIMAX_GLOBAL_BASE_URL: () => MINIMAX_GLOBAL_BASE_URL,
|
|
48
56
|
MODELS: () => MODELS,
|
|
49
|
-
MOONSHOT_OAUTH_KEY: () => MOONSHOT_OAUTH_KEY,
|
|
50
57
|
NotLoggedInError: () => NotLoggedInError,
|
|
58
|
+
OPENAI_CHATGPT_KEY: () => OPENAI_CHATGPT_KEY,
|
|
51
59
|
OPENROUTER_DEFAULT_BASE_URL: () => OPENROUTER_DEFAULT_BASE_URL,
|
|
52
60
|
PLAN_MODE_THINKING_CAP: () => PLAN_MODE_THINKING_CAP,
|
|
61
|
+
RETIRED_SIGN_INS: () => RETIRED_SIGN_INS,
|
|
53
62
|
SubscriptionUsageError: () => SubscriptionUsageError,
|
|
54
63
|
TelegramBot: () => TelegramBot,
|
|
55
|
-
XAI_OAUTH_KEY: () => XAI_OAUTH_KEY,
|
|
56
64
|
XIAOMI_CREDITS_KEY: () => XIAOMI_CREDITS_KEY,
|
|
57
65
|
XIAOMI_TOKEN_PLAN_BASE_URLS: () => XIAOMI_TOKEN_PLAN_BASE_URLS,
|
|
66
|
+
apiKeyStorageKeys: () => apiKeyStorageKeys,
|
|
58
67
|
authPriorityNote: () => authPriorityNote,
|
|
59
68
|
clampThinkingForPlanMode: () => clampThinkingForPlanMode,
|
|
60
69
|
claudeCodeAuthStatus: () => claudeCodeAuthStatus,
|
|
@@ -71,6 +80,7 @@ __export(index_exports, {
|
|
|
71
80
|
createAutoUpdater: () => createAutoUpdater,
|
|
72
81
|
decodeOggOpus: () => decodeOggOpus,
|
|
73
82
|
describeAuthMethods: () => describeAuthMethods,
|
|
83
|
+
detectKimiCodeBaseUrl: () => detectKimiCodeBaseUrl,
|
|
74
84
|
detectMiniMaxBaseUrl: () => detectMiniMaxBaseUrl,
|
|
75
85
|
detectXiaomiTokenPlanBaseUrl: () => detectXiaomiTokenPlanBaseUrl,
|
|
76
86
|
discoverLocalModels: () => discoverLocalModels,
|
|
@@ -79,6 +89,7 @@ __export(index_exports, {
|
|
|
79
89
|
dualAuthProviderByOAuthKey: () => dualAuthProviderByOAuthKey,
|
|
80
90
|
endpointRoot: () => endpointRoot,
|
|
81
91
|
ensureClaudeCodeBinary: () => ensureClaudeCodeBinary,
|
|
92
|
+
fetchChatGPTPlanModels: () => fetchChatGPTPlanModels,
|
|
82
93
|
fetchSubscriptionUsage: () => fetchSubscriptionUsage,
|
|
83
94
|
findProbedModel: () => findProbedModel,
|
|
84
95
|
formatClaudeCodeInstallProgress: () => formatClaudeCodeInstallProgress,
|
|
@@ -89,6 +100,7 @@ __export(index_exports, {
|
|
|
89
100
|
getAuthProvider: () => getAuthProvider,
|
|
90
101
|
getAuthStorageKey: () => getAuthStorageKey,
|
|
91
102
|
getAuthStorageKeys: () => getAuthStorageKeys,
|
|
103
|
+
getChatGPTHostId: () => getChatGPTHostId,
|
|
92
104
|
getContextWindow: () => getContextWindow,
|
|
93
105
|
getDefaultModel: () => getDefaultModel,
|
|
94
106
|
getDefaultThinkingLevel: () => getDefaultThinkingLevel,
|
|
@@ -104,10 +116,7 @@ __export(index_exports, {
|
|
|
104
116
|
getSupportedThinkingLevels: () => getSupportedThinkingLevels,
|
|
105
117
|
getToolResultCharLimit: () => getToolResultCharLimit,
|
|
106
118
|
getVideoByteLimit: () => getVideoByteLimit,
|
|
107
|
-
grokCliBaseUrl: () => grokCliBaseUrl,
|
|
108
|
-
grokCliHeaders: () => grokCliHeaders,
|
|
109
119
|
isClaudeCodeAuthMarker: () => isClaudeCodeAuthMarker,
|
|
110
|
-
isGrokCliEndpoint: () => isGrokCliEndpoint,
|
|
111
120
|
isHiddenProvider: () => isHiddenProvider,
|
|
112
121
|
isKimiCodingEndpoint: () => isKimiCodingEndpoint,
|
|
113
122
|
isLegacyClaudeOAuth: () => isLegacyClaudeOAuth,
|
|
@@ -116,21 +125,17 @@ __export(index_exports, {
|
|
|
116
125
|
isModelLoaded: () => isModelLoaded,
|
|
117
126
|
isPaidModel: () => isPaidModel,
|
|
118
127
|
isThinkingLevelSupported: () => isThinkingLevelSupported,
|
|
119
|
-
kimiCodeBaseUrl: () => kimiCodeBaseUrl,
|
|
120
|
-
kimiCodingHeaders: () => kimiCodingHeaders,
|
|
121
128
|
localAuthStorageKey: () => localAuthStorageKey,
|
|
122
129
|
localModelShortName: () => localModelShortName,
|
|
123
130
|
log: () => log,
|
|
131
|
+
loginChatGPT: () => loginChatGPT,
|
|
124
132
|
loginClaudeCode: () => loginClaudeCode,
|
|
125
|
-
loginGemini: () => loginGemini,
|
|
126
|
-
loginKimi: () => loginKimi,
|
|
127
|
-
loginOpenAI: () => loginOpenAI,
|
|
128
|
-
loginXai: () => loginXai,
|
|
129
133
|
logoutClaudeCode: () => logoutClaudeCode,
|
|
130
134
|
mergeClaudeCodeRateLimit: () => mergeClaudeCodeRateLimit,
|
|
131
135
|
oauthStorageKey: () => oauthStorageKey,
|
|
132
136
|
ollamaServedContext: () => ollamaServedContext,
|
|
133
137
|
openLog: () => openLog,
|
|
138
|
+
parseCallbackInput: () => parseCallbackInput,
|
|
134
139
|
parseLocalModelId: () => parseLocalModelId,
|
|
135
140
|
parseOpenRouterCatalog: () => parseOpenRouterCatalog,
|
|
136
141
|
probeEndpoint: () => probeEndpoint,
|
|
@@ -138,32 +143,63 @@ __export(index_exports, {
|
|
|
138
143
|
readClaudeCodeUsage: () => readClaudeCodeUsage,
|
|
139
144
|
readStoredBaseUrlSync: () => readStoredBaseUrlSync,
|
|
140
145
|
recordClaudeCodeRateLimit: () => recordClaudeCodeRateLimit,
|
|
141
|
-
|
|
142
|
-
refreshKimiToken: () => refreshKimiToken,
|
|
143
|
-
refreshOpenAIToken: () => refreshOpenAIToken,
|
|
144
|
-
refreshXaiToken: () => refreshXaiToken,
|
|
146
|
+
refreshChatGPTToken: () => refreshChatGPTToken,
|
|
145
147
|
registerLogCleanup: () => registerLogCleanup,
|
|
146
148
|
registerRuntimeModels: () => registerRuntimeModels,
|
|
149
|
+
requiredKeyLabel: () => requiredKeyLabel,
|
|
147
150
|
resample: () => resample,
|
|
148
151
|
resolveClaudeCodeBinary: () => resolveClaudeCodeBinary,
|
|
152
|
+
revokeChatGPTSignIn: () => revokeChatGPTSignIn,
|
|
149
153
|
setProgressCallback: () => setProgressCallback,
|
|
150
154
|
setProviderCatalog: () => setProviderCatalog,
|
|
151
155
|
signInWithClaudeCode: () => signInWithClaudeCode,
|
|
152
156
|
signOutOfClaudeCode: () => signOutOfClaudeCode,
|
|
157
|
+
storageKeyProvider: () => storageKeyProvider,
|
|
153
158
|
toModelInfo: () => toModelInfo,
|
|
154
159
|
transcribeVoice: () => transcribeVoice,
|
|
155
160
|
usesOpenAICodexTransport: () => usesOpenAICodexTransport,
|
|
161
|
+
verifyIdToken: () => verifyIdToken,
|
|
156
162
|
withFileLock: () => withFileLock,
|
|
157
163
|
withoutPaidTier: () => withoutPaidTier
|
|
158
164
|
});
|
|
159
165
|
module.exports = __toCommonJS(index_exports);
|
|
160
166
|
|
|
161
|
-
// src/
|
|
162
|
-
var
|
|
163
|
-
var
|
|
164
|
-
var
|
|
165
|
-
var
|
|
166
|
-
|
|
167
|
+
// src/kimi-region.ts
|
|
168
|
+
var import_gg_ai = require("@kenkaiiii/gg-ai");
|
|
169
|
+
var KIMI_CODE_DEFAULT_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
170
|
+
var KIMI_CODE_OVERSEAS_BASE_URL = "https://api.kimi.ai/coding/v1";
|
|
171
|
+
var KIMI_CODE_BASE_URLS = [KIMI_CODE_DEFAULT_BASE_URL, KIMI_CODE_OVERSEAS_BASE_URL];
|
|
172
|
+
async function probeHost(baseUrl, apiKey, fetchImpl, timeoutMs) {
|
|
173
|
+
try {
|
|
174
|
+
const res = await fetchImpl(`${baseUrl}/models`, {
|
|
175
|
+
headers: { Authorization: `Bearer ${apiKey}`, "User-Agent": import_gg_ai.GG_CODER_USER_AGENT },
|
|
176
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
177
|
+
});
|
|
178
|
+
if (res.ok) return "authorized";
|
|
179
|
+
if (res.status === 401 || res.status === 403) return "unauthorized";
|
|
180
|
+
return "inconclusive";
|
|
181
|
+
} catch {
|
|
182
|
+
return "inconclusive";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function detectKimiCodeBaseUrl(apiKey, options = {}) {
|
|
186
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
187
|
+
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
188
|
+
const results = await Promise.all(
|
|
189
|
+
KIMI_CODE_BASE_URLS.map((url) => probeHost(url, apiKey, fetchImpl, timeoutMs))
|
|
190
|
+
);
|
|
191
|
+
const index = results.indexOf("authorized");
|
|
192
|
+
return index === -1 ? void 0 : KIMI_CODE_BASE_URLS[index];
|
|
193
|
+
}
|
|
194
|
+
function isKimiCodingEndpoint(baseUrl) {
|
|
195
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
|
|
196
|
+
return /^https:\/\/api\.kimi\.(com|ai)\/coding(\/|$)/i.test(baseUrl);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/auth-storage.ts
|
|
200
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
201
|
+
var import_node_fs4 = require("fs");
|
|
202
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
167
203
|
|
|
168
204
|
// src/paths.ts
|
|
169
205
|
var import_node_path = __toESM(require("path"), 1);
|
|
@@ -194,240 +230,15 @@ function getAppPaths() {
|
|
|
194
230
|
};
|
|
195
231
|
}
|
|
196
232
|
|
|
197
|
-
// src/oauth/request-timeout.ts
|
|
198
|
-
var OAUTH_REQUEST_TIMEOUT_MS = 3e4;
|
|
199
|
-
function oauthRequestSignal() {
|
|
200
|
-
return AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// src/oauth/kimi.ts
|
|
204
|
-
var CLIENT_ID = "17e5f671-d194-4dfb-9706-5516cb48c098";
|
|
205
|
-
var DEFAULT_OAUTH_HOST = "https://auth.kimi.com";
|
|
206
|
-
var DEFAULT_CODING_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
207
|
-
var KIMI_PLATFORM = "kimi_code_cli";
|
|
208
|
-
var DEFAULT_KIMI_VERSION = "1.0.11";
|
|
209
|
-
var DEVICE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
210
|
-
function oauthHost() {
|
|
211
|
-
const host = process.env.KIMI_CODE_OAUTH_HOST ?? process.env.KIMI_OAUTH_HOST ?? DEFAULT_OAUTH_HOST;
|
|
212
|
-
return host.replace(/\/+$/, "");
|
|
213
|
-
}
|
|
214
|
-
function kimiCodeBaseUrl() {
|
|
215
|
-
return (process.env.KIMI_CODE_BASE_URL ?? DEFAULT_CODING_BASE_URL).replace(/\/+$/, "");
|
|
216
|
-
}
|
|
217
|
-
function kimiVersion() {
|
|
218
|
-
const v = process.env.KIMI_CODE_VERSION ?? DEFAULT_KIMI_VERSION;
|
|
219
|
-
return asciiHeader(v, DEFAULT_KIMI_VERSION);
|
|
220
|
-
}
|
|
221
|
-
function asciiHeader(value, fallback = "unknown") {
|
|
222
|
-
const cleaned = value.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
223
|
-
return cleaned.length > 0 ? cleaned : fallback;
|
|
224
|
-
}
|
|
225
|
-
function macOsProductVersion() {
|
|
226
|
-
try {
|
|
227
|
-
const version = (0, import_node_child_process.execFileSync)("/usr/bin/sw_vers", ["-productVersion"], {
|
|
228
|
-
encoding: "utf-8",
|
|
229
|
-
timeout: 1e3
|
|
230
|
-
}).trim();
|
|
231
|
-
return version.length > 0 ? version : void 0;
|
|
232
|
-
} catch {
|
|
233
|
-
return void 0;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
function deviceModel() {
|
|
237
|
-
const os2 = (0, import_node_os2.type)();
|
|
238
|
-
const version = (0, import_node_os2.release)();
|
|
239
|
-
const osArch = (0, import_node_os2.arch)();
|
|
240
|
-
if (os2 === "Darwin") return `macOS ${macOsProductVersion() ?? version} ${osArch}`;
|
|
241
|
-
if (os2 === "Windows_NT") return `Windows ${version} ${osArch}`;
|
|
242
|
-
return `${os2} ${version} ${osArch}`.trim();
|
|
243
|
-
}
|
|
244
|
-
function deviceId() {
|
|
245
|
-
const idPath = import_node_path2.default.join(getAppPaths().agentDir, "kimi_device_id");
|
|
246
|
-
if ((0, import_node_fs.existsSync)(idPath)) {
|
|
247
|
-
try {
|
|
248
|
-
const text = (0, import_node_fs.readFileSync)(idPath, "utf-8").trim();
|
|
249
|
-
if (text.length > 0) return text;
|
|
250
|
-
} catch {
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
const id = (0, import_node_crypto.randomUUID)();
|
|
254
|
-
try {
|
|
255
|
-
(0, import_node_fs.mkdirSync)(getAppPaths().agentDir, { recursive: true, mode: 448 });
|
|
256
|
-
(0, import_node_fs.writeFileSync)(idPath, id, { encoding: "utf-8", mode: 384 });
|
|
257
|
-
} catch {
|
|
258
|
-
}
|
|
259
|
-
return id;
|
|
260
|
-
}
|
|
261
|
-
function deviceHeaders() {
|
|
262
|
-
return {
|
|
263
|
-
"X-Msh-Platform": KIMI_PLATFORM,
|
|
264
|
-
"X-Msh-Version": kimiVersion(),
|
|
265
|
-
"X-Msh-Device-Name": asciiHeader((0, import_node_os2.hostname)()),
|
|
266
|
-
"X-Msh-Device-Model": asciiHeader(deviceModel()),
|
|
267
|
-
"X-Msh-Os-Version": asciiHeader((0, import_node_os2.release)()),
|
|
268
|
-
"X-Msh-Device-Id": deviceId()
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
function kimiCodingHeaders() {
|
|
272
|
-
return {
|
|
273
|
-
"User-Agent": `kimi-code-cli/${kimiVersion()}`,
|
|
274
|
-
...deviceHeaders()
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
function isKimiCodingEndpoint(baseUrl) {
|
|
278
|
-
if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
|
|
279
|
-
const normalized = baseUrl.replace(/\/+$/, "");
|
|
280
|
-
return normalized === kimiCodeBaseUrl() || /(^|\.)kimi\.com/i.test(normalized);
|
|
281
|
-
}
|
|
282
|
-
async function postForm(endpoint, params) {
|
|
283
|
-
const response = await fetch(`${oauthHost()}${endpoint}`, {
|
|
284
|
-
method: "POST",
|
|
285
|
-
headers: {
|
|
286
|
-
...deviceHeaders(),
|
|
287
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
288
|
-
Accept: "application/json"
|
|
289
|
-
},
|
|
290
|
-
body: new URLSearchParams(params).toString(),
|
|
291
|
-
signal: oauthRequestSignal()
|
|
292
|
-
});
|
|
293
|
-
let data = {};
|
|
294
|
-
try {
|
|
295
|
-
const parsed = await response.json();
|
|
296
|
-
if (parsed && typeof parsed === "object") data = parsed;
|
|
297
|
-
} catch {
|
|
298
|
-
}
|
|
299
|
-
return { status: response.status, data };
|
|
300
|
-
}
|
|
301
|
-
function errorDetail(data) {
|
|
302
|
-
const desc = data.error_description ?? data.message ?? data.error;
|
|
303
|
-
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
304
|
-
}
|
|
305
|
-
function credsFromTokenResponse(data, opts) {
|
|
306
|
-
const accessToken = data.access_token;
|
|
307
|
-
const responseRefreshToken = data.refresh_token;
|
|
308
|
-
const expiresIn = Number(data.expires_in);
|
|
309
|
-
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
310
|
-
throw new Error("Kimi OAuth response missing access_token.");
|
|
311
|
-
}
|
|
312
|
-
const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
|
|
313
|
-
if (refreshToken.length === 0) {
|
|
314
|
-
throw new Error("Kimi OAuth response missing refresh_token.");
|
|
315
|
-
}
|
|
316
|
-
if (!Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
317
|
-
throw new Error("Kimi OAuth response missing or invalid expires_in.");
|
|
318
|
-
}
|
|
319
|
-
return {
|
|
320
|
-
accessToken,
|
|
321
|
-
refreshToken,
|
|
322
|
-
expiresAt: Date.now() + expiresIn * 1e3,
|
|
323
|
-
expiresIn,
|
|
324
|
-
baseUrl: kimiCodeBaseUrl()
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
async function requestDeviceAuthorization() {
|
|
328
|
-
const { status, data } = await postForm("/api/oauth/device_authorization", {
|
|
329
|
-
client_id: CLIENT_ID
|
|
330
|
-
});
|
|
331
|
-
if (status !== 200) {
|
|
332
|
-
throw new Error(`Kimi device authorization failed (${status}): ${errorDetail(data)}`);
|
|
333
|
-
}
|
|
334
|
-
const userCode = data.user_code;
|
|
335
|
-
const deviceCode = data.device_code;
|
|
336
|
-
const verificationUriComplete = data.verification_uri_complete;
|
|
337
|
-
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
338
|
-
throw new Error("Kimi device authorization response missing user_code/device_code.");
|
|
339
|
-
}
|
|
340
|
-
return {
|
|
341
|
-
userCode,
|
|
342
|
-
deviceCode,
|
|
343
|
-
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
344
|
-
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
345
|
-
interval: Number(data.interval ?? 5) || 5
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
async function pollDeviceToken(deviceCode) {
|
|
349
|
-
const { status, data } = await postForm("/api/oauth/token", {
|
|
350
|
-
client_id: CLIENT_ID,
|
|
351
|
-
device_code: deviceCode,
|
|
352
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
353
|
-
});
|
|
354
|
-
if (status === 200 && typeof data.access_token === "string") {
|
|
355
|
-
return { kind: "success", creds: credsFromTokenResponse(data) };
|
|
356
|
-
}
|
|
357
|
-
if (status >= 500) {
|
|
358
|
-
throw new Error(`Kimi token polling server error (${status}): ${errorDetail(data)}`);
|
|
359
|
-
}
|
|
360
|
-
const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
|
|
361
|
-
switch (errorCode) {
|
|
362
|
-
case "authorization_pending":
|
|
363
|
-
return { kind: "pending" };
|
|
364
|
-
case "slow_down":
|
|
365
|
-
return { kind: "slow_down" };
|
|
366
|
-
case "expired_token":
|
|
367
|
-
return { kind: "expired" };
|
|
368
|
-
case "access_denied":
|
|
369
|
-
return { kind: "denied" };
|
|
370
|
-
default:
|
|
371
|
-
throw new Error(`Kimi token polling failed (${status}): ${errorDetail(data)}`);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
function sleep(ms) {
|
|
375
|
-
return new Promise((resolve) => {
|
|
376
|
-
setTimeout(resolve, ms);
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
async function loginKimi(callbacks) {
|
|
380
|
-
const auth = await requestDeviceAuthorization();
|
|
381
|
-
callbacks.onStatus(
|
|
382
|
-
`Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
|
|
383
|
-
);
|
|
384
|
-
callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
|
|
385
|
-
callbacks.onStatus("Waiting for you to authorize in the browser...");
|
|
386
|
-
const deadline = Date.now() + DEVICE_TIMEOUT_MS;
|
|
387
|
-
let interval = Math.max(auth.interval, 1);
|
|
388
|
-
while (Date.now() < deadline) {
|
|
389
|
-
await sleep(interval * 1e3);
|
|
390
|
-
const result = await pollDeviceToken(auth.deviceCode);
|
|
391
|
-
if (result.kind === "success") return result.creds;
|
|
392
|
-
if (result.kind === "denied") {
|
|
393
|
-
throw new Error("Kimi authorization was denied.");
|
|
394
|
-
}
|
|
395
|
-
if (result.kind === "expired") {
|
|
396
|
-
throw new Error("Kimi device code expired. Please run login again.");
|
|
397
|
-
}
|
|
398
|
-
if (result.kind === "slow_down") {
|
|
399
|
-
interval += 5;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
throw new Error("Kimi login timed out. Please run login again.");
|
|
403
|
-
}
|
|
404
|
-
async function refreshKimiToken(refreshToken) {
|
|
405
|
-
const { status, data } = await postForm("/api/oauth/token", {
|
|
406
|
-
client_id: CLIENT_ID,
|
|
407
|
-
grant_type: "refresh_token",
|
|
408
|
-
refresh_token: refreshToken
|
|
409
|
-
});
|
|
410
|
-
if (status === 200 && typeof data.access_token === "string") {
|
|
411
|
-
return credsFromTokenResponse(data, { fallbackRefreshToken: refreshToken });
|
|
412
|
-
}
|
|
413
|
-
const errorCode = typeof data.error === "string" ? data.error : "";
|
|
414
|
-
throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// src/auth-storage.ts
|
|
418
|
-
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
419
|
-
var import_node_fs5 = require("fs");
|
|
420
|
-
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
421
|
-
|
|
422
233
|
// src/claude-code/runtime.ts
|
|
423
|
-
var
|
|
424
|
-
var
|
|
425
|
-
var
|
|
234
|
+
var import_node_child_process = require("child_process");
|
|
235
|
+
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
236
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
426
237
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
427
|
-
var
|
|
238
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
428
239
|
|
|
429
240
|
// src/claude-code/tar.ts
|
|
430
|
-
var
|
|
241
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
431
242
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
432
243
|
var import_node_zlib = __toESM(require("zlib"), 1);
|
|
433
244
|
var BLOCK = 512;
|
|
@@ -459,7 +270,7 @@ function paxPath(data) {
|
|
|
459
270
|
return void 0;
|
|
460
271
|
}
|
|
461
272
|
async function extractFileFromTarGz(tgzPath, entryName, destPath) {
|
|
462
|
-
const input =
|
|
273
|
+
const input = import_node_fs.default.createReadStream(tgzPath).pipe(import_node_zlib.default.createGunzip());
|
|
463
274
|
let pending = Buffer.alloc(0);
|
|
464
275
|
let remaining = 0;
|
|
465
276
|
let padding = 0;
|
|
@@ -505,7 +316,7 @@ async function extractFileFromTarGz(tgzPath, entryName, destPath) {
|
|
|
505
316
|
pos += BLOCK;
|
|
506
317
|
if (header.every((b) => b === 0)) continue;
|
|
507
318
|
const size = readSize(header, 124);
|
|
508
|
-
const
|
|
319
|
+
const type = String.fromCharCode(header[156] || 48);
|
|
509
320
|
let name = readString(header, 0, 100);
|
|
510
321
|
if (readString(header, 257, 6) === "ustar") {
|
|
511
322
|
const prefix = readString(header, 345, 155);
|
|
@@ -514,12 +325,12 @@ async function extractFileFromTarGz(tgzPath, entryName, destPath) {
|
|
|
514
325
|
remaining = size;
|
|
515
326
|
padding = (BLOCK - size % BLOCK) % BLOCK;
|
|
516
327
|
meta = [];
|
|
517
|
-
if (
|
|
518
|
-
else if (
|
|
328
|
+
if (type === "x") kind = "pax";
|
|
329
|
+
else if (type === "L") kind = "longname";
|
|
519
330
|
else {
|
|
520
331
|
const path7 = overridePath ?? name;
|
|
521
332
|
overridePath = void 0;
|
|
522
|
-
if ((
|
|
333
|
+
if ((type === "0" || type === "\0" || type === "7") && path7 === entryName) {
|
|
523
334
|
kind = "target";
|
|
524
335
|
out = await import_promises.default.open(destPath, "w", 493);
|
|
525
336
|
if (size === 0) {
|
|
@@ -609,10 +420,10 @@ function isMusl() {
|
|
|
609
420
|
return report != null && report.header?.glibcVersionRuntime === void 0;
|
|
610
421
|
}
|
|
611
422
|
function claudeCodePlatformKey(info = { platform: process.platform, arch: process.arch, musl: isMusl() }) {
|
|
612
|
-
const
|
|
613
|
-
if (!
|
|
614
|
-
if (info.platform === "darwin" || info.platform === "win32") return `${info.platform}-${
|
|
615
|
-
if (info.platform === "linux") return `linux-${
|
|
423
|
+
const arch = info.arch === "arm64" || info.arch === "x64" ? info.arch : null;
|
|
424
|
+
if (!arch) return null;
|
|
425
|
+
if (info.platform === "darwin" || info.platform === "win32") return `${info.platform}-${arch}`;
|
|
426
|
+
if (info.platform === "linux") return `linux-${arch}${info.musl ? "-musl" : ""}`;
|
|
616
427
|
return null;
|
|
617
428
|
}
|
|
618
429
|
function resolveAsset(opts) {
|
|
@@ -630,11 +441,11 @@ function claudeCodeHome(opts = {}) {
|
|
|
630
441
|
return opts.home ?? getAppPaths().claudeCodeDir;
|
|
631
442
|
}
|
|
632
443
|
function claudeCodeConfigDir(opts = {}) {
|
|
633
|
-
return opts.home ?
|
|
444
|
+
return opts.home ? import_node_path2.default.join(opts.home, "config") : getAppPaths().claudeCodeConfigDir;
|
|
634
445
|
}
|
|
635
446
|
function claudeCodeBinaryPath(opts = {}) {
|
|
636
447
|
const { manifest, asset } = resolveAsset(opts);
|
|
637
|
-
return
|
|
448
|
+
return import_node_path2.default.join(claudeCodeHome(opts), manifest.claudeCodeVersion, asset.bin);
|
|
638
449
|
}
|
|
639
450
|
function resolveClaudeCodeBinary(opts = {}) {
|
|
640
451
|
let binary;
|
|
@@ -644,7 +455,7 @@ function resolveClaudeCodeBinary(opts = {}) {
|
|
|
644
455
|
return null;
|
|
645
456
|
}
|
|
646
457
|
try {
|
|
647
|
-
return
|
|
458
|
+
return import_node_fs2.default.statSync(binary).isFile() ? binary : null;
|
|
648
459
|
} catch {
|
|
649
460
|
return null;
|
|
650
461
|
}
|
|
@@ -664,12 +475,12 @@ async function ensureClaudeCodeBinary(opts = {}) {
|
|
|
664
475
|
async function installClaudeCode(opts) {
|
|
665
476
|
const { manifest, asset } = resolveAsset(opts);
|
|
666
477
|
const home = claudeCodeHome(opts);
|
|
667
|
-
const versionDir =
|
|
668
|
-
const binary =
|
|
478
|
+
const versionDir = import_node_path2.default.join(home, manifest.claudeCodeVersion);
|
|
479
|
+
const binary = import_node_path2.default.join(versionDir, asset.bin);
|
|
669
480
|
await import_promises2.default.mkdir(home, { recursive: true });
|
|
670
|
-
const tag = `${process.pid}-${
|
|
671
|
-
const tarball =
|
|
672
|
-
const staging =
|
|
481
|
+
const tag = `${process.pid}-${import_node_crypto.default.randomBytes(4).toString("hex")}`;
|
|
482
|
+
const tarball = import_node_path2.default.join(home, `.download-${tag}.tgz`);
|
|
483
|
+
const staging = import_node_path2.default.join(home, `.staging-${tag}`);
|
|
673
484
|
try {
|
|
674
485
|
await download(asset, tarball, opts);
|
|
675
486
|
opts.onProgress?.({ phase: "extract" });
|
|
@@ -677,14 +488,14 @@ async function installClaudeCode(opts) {
|
|
|
677
488
|
const found = await extractFileFromTarGz(
|
|
678
489
|
tarball,
|
|
679
490
|
`package/${asset.bin}`,
|
|
680
|
-
|
|
491
|
+
import_node_path2.default.join(staging, asset.bin)
|
|
681
492
|
);
|
|
682
493
|
if (!found) throw new Error(`The Claude Code download has no ${asset.bin} binary.`);
|
|
683
|
-
await import_promises2.default.chmod(
|
|
494
|
+
await import_promises2.default.chmod(import_node_path2.default.join(staging, asset.bin), 493);
|
|
684
495
|
try {
|
|
685
496
|
await import_promises2.default.rename(staging, versionDir);
|
|
686
497
|
} catch (err) {
|
|
687
|
-
if (!
|
|
498
|
+
if (!import_node_fs2.default.existsSync(binary)) throw err;
|
|
688
499
|
}
|
|
689
500
|
await removeOldVersions(home, manifest.claudeCodeVersion);
|
|
690
501
|
return binary;
|
|
@@ -702,7 +513,7 @@ async function download(asset, dest, opts) {
|
|
|
702
513
|
throw new Error(`Claude Code download failed (HTTP ${res.status}).`);
|
|
703
514
|
}
|
|
704
515
|
const totalBytes = Number(res.headers.get("content-length")) || void 0;
|
|
705
|
-
const hash =
|
|
516
|
+
const hash = import_node_crypto.default.createHash("sha512");
|
|
706
517
|
const out = await import_promises2.default.open(dest, "w");
|
|
707
518
|
let receivedBytes = 0;
|
|
708
519
|
let lastReported = -1;
|
|
@@ -740,7 +551,7 @@ async function removeOldVersions(home, keep) {
|
|
|
740
551
|
for (const entry of entries) {
|
|
741
552
|
if (!entry.isDirectory() || entry.name === keep || entry.name === "config") continue;
|
|
742
553
|
if (!/^\d+\.\d+\.\d+/.test(entry.name)) continue;
|
|
743
|
-
await import_promises2.default.rm(
|
|
554
|
+
await import_promises2.default.rm(import_node_path2.default.join(home, entry.name), { recursive: true, force: true }).catch(() => {
|
|
744
555
|
});
|
|
745
556
|
}
|
|
746
557
|
}
|
|
@@ -781,7 +592,7 @@ function runCommand(opts) {
|
|
|
781
592
|
function runClaude(args, opts) {
|
|
782
593
|
const { command, prefix } = runCommand(opts);
|
|
783
594
|
return new Promise((resolve, reject) => {
|
|
784
|
-
const child = (0,
|
|
595
|
+
const child = (0, import_node_child_process.spawn)(command, [...prefix, ...args], {
|
|
785
596
|
env: claudeCodeEnv(opts),
|
|
786
597
|
stdio: ["ignore", "pipe", "pipe"],
|
|
787
598
|
windowsHide: true,
|
|
@@ -841,9 +652,9 @@ async function loginClaudeCode(callbacks, opts = {}) {
|
|
|
841
652
|
const { command, prefix } = runCommand(opts);
|
|
842
653
|
const home = claudeCodeHome(opts);
|
|
843
654
|
await import_promises2.default.mkdir(claudeCodeConfigDir(opts), { recursive: true });
|
|
844
|
-
const tag = `${process.pid}-${
|
|
845
|
-
const hookFile =
|
|
846
|
-
const urlFile =
|
|
655
|
+
const tag = `${process.pid}-${import_node_crypto.default.randomBytes(4).toString("hex")}`;
|
|
656
|
+
const hookFile = import_node_path2.default.join(home, `.open-url-${tag}.cjs`);
|
|
657
|
+
const urlFile = import_node_path2.default.join(home, `.open-url-${tag}.txt`);
|
|
847
658
|
await import_promises2.default.writeFile(hookFile, OPEN_URL_HOOK);
|
|
848
659
|
const env = {
|
|
849
660
|
...claudeCodeEnv(opts),
|
|
@@ -855,7 +666,7 @@ async function loginClaudeCode(callbacks, opts = {}) {
|
|
|
855
666
|
let timer;
|
|
856
667
|
try {
|
|
857
668
|
await new Promise((resolve, reject) => {
|
|
858
|
-
const child = (0,
|
|
669
|
+
const child = (0, import_node_child_process.spawn)(command, [...prefix, "auth", "login", "--claudeai"], {
|
|
859
670
|
env,
|
|
860
671
|
stdio: ["pipe", "pipe", "pipe"],
|
|
861
672
|
windowsHide: true
|
|
@@ -890,7 +701,7 @@ async function loginClaudeCode(callbacks, opts = {}) {
|
|
|
890
701
|
timer = setInterval(() => {
|
|
891
702
|
let captured = "";
|
|
892
703
|
try {
|
|
893
|
-
captured =
|
|
704
|
+
captured = import_node_fs2.default.readFileSync(urlFile, "utf8");
|
|
894
705
|
} catch {
|
|
895
706
|
}
|
|
896
707
|
const callbackUrl = captured ? claudeCodeCallbackUrl(manualUrl, captured) : null;
|
|
@@ -923,7 +734,9 @@ async function loginClaudeCode(callbacks, opts = {}) {
|
|
|
923
734
|
|
|
924
735
|
// src/oauth/openai.ts
|
|
925
736
|
var import_node_http = __toESM(require("http"), 1);
|
|
926
|
-
var
|
|
737
|
+
var import_node_crypto2 = __toESM(require("crypto"), 1);
|
|
738
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
739
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
927
740
|
|
|
928
741
|
// src/oauth/pkce.ts
|
|
929
742
|
function base64urlEncode(bytes) {
|
|
@@ -943,791 +756,396 @@ async function generatePKCE() {
|
|
|
943
756
|
return { verifier, challenge };
|
|
944
757
|
}
|
|
945
758
|
|
|
759
|
+
// src/oauth/request-timeout.ts
|
|
760
|
+
var OAUTH_REQUEST_TIMEOUT_MS = 3e4;
|
|
761
|
+
function oauthRequestSignal() {
|
|
762
|
+
return AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
|
|
763
|
+
}
|
|
764
|
+
|
|
946
765
|
// src/oauth/openai.ts
|
|
947
|
-
var
|
|
948
|
-
var AUTHORIZE_URL =
|
|
949
|
-
var TOKEN_URL =
|
|
950
|
-
var
|
|
951
|
-
var
|
|
952
|
-
var
|
|
766
|
+
var ISSUER = "https://auth.openai.com";
|
|
767
|
+
var AUTHORIZE_URL = `${ISSUER}/api/accounts/authorize`;
|
|
768
|
+
var TOKEN_URL = `${ISSUER}/api/accounts/oauth/token`;
|
|
769
|
+
var REVOKE_URL = `${ISSUER}/api/accounts/oauth/revoke`;
|
|
770
|
+
var JWKS_URL = `${ISSUER}/.well-known/jwks.json`;
|
|
771
|
+
var REGISTRATION_CLIENT_ID = "dynamic_agent_client";
|
|
772
|
+
var AGENT_NAME = "GG Coder";
|
|
773
|
+
var RESOURCE = "https://api.openai.com/v1";
|
|
774
|
+
var CHATGPT_PLAN_SCOPE = "chatgpt.tokens.use.direct";
|
|
775
|
+
var SCOPES = `openid profile email offline_access resource.invoke ${CHATGPT_PLAN_SCOPE}`;
|
|
776
|
+
var CHATGPT_PLAN_BASE_URL = "https://api.openai.com/v1";
|
|
777
|
+
var CHATGPT_USAGE_URL = "https://chatgpt.com/settings/usage";
|
|
778
|
+
var CALLBACK_PATH = "/auth/callback";
|
|
779
|
+
var PREFERRED_PORT = 1455;
|
|
953
780
|
var CALLBACK_TIMEOUT_MS = 12e4;
|
|
954
781
|
var MAX_PASTE_ATTEMPTS = 3;
|
|
955
|
-
var
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
url.searchParams.set("client_id", CLIENT_ID2);
|
|
962
|
-
url.searchParams.set("redirect_uri", REDIRECT_URI);
|
|
963
|
-
url.searchParams.set("scope", SCOPE);
|
|
964
|
-
url.searchParams.set("code_challenge", challenge);
|
|
965
|
-
url.searchParams.set("code_challenge_method", "S256");
|
|
966
|
-
url.searchParams.set("state", state);
|
|
967
|
-
url.searchParams.set("prompt", "login");
|
|
968
|
-
url.searchParams.set("id_token_add_organizations", "true");
|
|
969
|
-
url.searchParams.set("codex_cli_simplified_flow", "true");
|
|
970
|
-
url.searchParams.set("originator", "ggcoder");
|
|
971
|
-
const code = await acquireAuthorizationCode(url.toString(), state, callbacks);
|
|
972
|
-
const creds = await exchangeOpenAICode(code, verifier);
|
|
973
|
-
const accountId = getAccountId(creds.accessToken);
|
|
974
|
-
if (!accountId) {
|
|
975
|
-
throw new Error("Failed to extract accountId from OpenAI token.");
|
|
976
|
-
}
|
|
977
|
-
creds.accountId = accountId;
|
|
978
|
-
return creds;
|
|
979
|
-
}
|
|
980
|
-
function parseAuthorizationInput(input) {
|
|
981
|
-
const value = input.trim();
|
|
982
|
-
if (!value) return {};
|
|
983
|
-
try {
|
|
984
|
-
const url = new URL(value);
|
|
985
|
-
return {
|
|
986
|
-
code: url.searchParams.get("code") ?? void 0,
|
|
987
|
-
state: url.searchParams.get("state") ?? void 0
|
|
988
|
-
};
|
|
989
|
-
} catch {
|
|
990
|
-
}
|
|
991
|
-
if (value.includes("#")) {
|
|
992
|
-
const [code, state] = value.split("#", 2);
|
|
993
|
-
return { code, state };
|
|
994
|
-
}
|
|
995
|
-
if (value.includes("code=")) {
|
|
996
|
-
const params = new URLSearchParams(value);
|
|
997
|
-
return {
|
|
998
|
-
code: params.get("code") ?? void 0,
|
|
999
|
-
state: params.get("state") ?? void 0
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
if (/\s/.test(value) || value.length < MIN_RAW_CODE_LENGTH) return {};
|
|
1003
|
-
return { code: value };
|
|
1004
|
-
}
|
|
1005
|
-
function decodeJwt(token) {
|
|
1006
|
-
try {
|
|
1007
|
-
const parts = token.split(".");
|
|
1008
|
-
if (parts.length !== 3) return null;
|
|
1009
|
-
const decoded = atob(parts[1]);
|
|
1010
|
-
return JSON.parse(decoded);
|
|
1011
|
-
} catch {
|
|
1012
|
-
return null;
|
|
782
|
+
var ID_TOKEN_CLOCK_SKEW_S = 60;
|
|
783
|
+
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.";
|
|
784
|
+
var ChatGPTPlanNotEnabledError = class extends Error {
|
|
785
|
+
constructor() {
|
|
786
|
+
super(CHATGPT_PLAN_NOT_ENABLED_MESSAGE);
|
|
787
|
+
this.name = "ChatGPTPlanNotEnabledError";
|
|
1013
788
|
}
|
|
789
|
+
};
|
|
790
|
+
function defaultHostIdFile() {
|
|
791
|
+
return import_node_path3.default.join(getAppPaths().agentDir, "openai-host-id");
|
|
1014
792
|
}
|
|
1015
|
-
function
|
|
1016
|
-
|
|
1017
|
-
const auth = payload?.[JWT_CLAIM_PATH];
|
|
1018
|
-
const accountId = auth?.chatgpt_account_id;
|
|
1019
|
-
return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
|
|
793
|
+
function defaultRegistrationFile() {
|
|
794
|
+
return import_node_path3.default.join(getAppPaths().agentDir, "openai-chatgpt-client.json");
|
|
1020
795
|
}
|
|
1021
|
-
async function
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
const done = new AbortController();
|
|
796
|
+
async function writePrivateFile(file, content) {
|
|
797
|
+
await import_promises3.default.mkdir(import_node_path3.default.dirname(file), { recursive: true });
|
|
798
|
+
const tmp = `${file}.${process.pid}.${import_node_crypto2.default.randomUUID().slice(0, 8)}.tmp`;
|
|
1025
799
|
try {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
if (!serverFailed || pasteError === void 0) return;
|
|
1031
|
-
reject(
|
|
1032
|
-
pasteError instanceof Error ? pasteError : new Error("Could not obtain an authorization code.")
|
|
1033
|
-
);
|
|
1034
|
-
};
|
|
1035
|
-
listenForCallback(expectedState, done.signal).then(resolve, () => {
|
|
1036
|
-
serverFailed = true;
|
|
1037
|
-
failIfExhausted();
|
|
1038
|
-
});
|
|
1039
|
-
promptForCode(expectedState, callbacks, done.signal).then(resolve, (err) => {
|
|
1040
|
-
if (err instanceof FatalLoginError) {
|
|
1041
|
-
reject(err);
|
|
1042
|
-
return;
|
|
1043
|
-
}
|
|
1044
|
-
pasteError = err;
|
|
1045
|
-
failIfExhausted();
|
|
1046
|
-
});
|
|
800
|
+
await import_promises3.default.writeFile(tmp, content, { encoding: "utf-8", mode: 384 });
|
|
801
|
+
await import_promises3.default.rename(tmp, file);
|
|
802
|
+
} catch (err) {
|
|
803
|
+
await import_promises3.default.unlink(tmp).catch(() => {
|
|
1047
804
|
});
|
|
1048
|
-
|
|
1049
|
-
done.abort();
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
var FatalLoginError = class extends Error {
|
|
1053
|
-
};
|
|
1054
|
-
async function promptForCode(expectedState, callbacks, signal) {
|
|
1055
|
-
let message = "If the browser is on another machine (SSH/headless), paste the callback URL or code here:";
|
|
1056
|
-
for (let attempt = 0; attempt < MAX_PASTE_ATTEMPTS; attempt++) {
|
|
1057
|
-
if (signal.aborted) throw new Error("Authorization code already received.");
|
|
1058
|
-
const raw = await callbacks.onPromptCode(message, signal);
|
|
1059
|
-
if (signal.aborted) throw new Error("Authorization code already received.");
|
|
1060
|
-
const parsed = parseAuthorizationInput(raw);
|
|
1061
|
-
if (parsed.code && parsed.state && parsed.state !== expectedState) {
|
|
1062
|
-
throw new FatalLoginError("Authorization state mismatch \u2014 start the login again.");
|
|
1063
|
-
}
|
|
1064
|
-
if (parsed.code) return parsed.code;
|
|
1065
|
-
message = "That didn't contain an authorization code. Paste the full callback URL:";
|
|
805
|
+
throw err;
|
|
1066
806
|
}
|
|
1067
|
-
throw new Error("No authorization code found in input.");
|
|
1068
807
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
res.statusCode = 404;
|
|
1076
|
-
res.end("Not found");
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
if (url.searchParams.get("state") !== expectedState) {
|
|
1080
|
-
res.statusCode = 400;
|
|
1081
|
-
res.end("State mismatch");
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
receivedCode = url.searchParams.get("code");
|
|
1085
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1086
|
-
res.end("<html><body><h1>Login successful!</h1><p>You can close this tab.</p></body></html>");
|
|
1087
|
-
server.close();
|
|
1088
|
-
});
|
|
1089
|
-
server.on("error", (err) => {
|
|
1090
|
-
reject(err);
|
|
1091
|
-
});
|
|
1092
|
-
server.listen(1455, "127.0.0.1");
|
|
1093
|
-
const timeout = setTimeout(() => {
|
|
1094
|
-
if (!receivedCode) server.close();
|
|
1095
|
-
}, CALLBACK_TIMEOUT_MS);
|
|
1096
|
-
timeout.unref();
|
|
1097
|
-
const onAbort = () => {
|
|
1098
|
-
server.close();
|
|
1099
|
-
};
|
|
1100
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1101
|
-
server.on("close", () => {
|
|
1102
|
-
clearTimeout(timeout);
|
|
1103
|
-
signal.removeEventListener("abort", onAbort);
|
|
1104
|
-
if (receivedCode) {
|
|
1105
|
-
resolve(receivedCode);
|
|
1106
|
-
} else {
|
|
1107
|
-
reject(new Error("Server closed without receiving code"));
|
|
1108
|
-
}
|
|
1109
|
-
});
|
|
1110
|
-
});
|
|
1111
|
-
}
|
|
1112
|
-
async function exchangeOpenAICode(code, verifier) {
|
|
1113
|
-
const response = await fetch(TOKEN_URL, {
|
|
1114
|
-
method: "POST",
|
|
1115
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1116
|
-
body: new URLSearchParams({
|
|
1117
|
-
grant_type: "authorization_code",
|
|
1118
|
-
client_id: CLIENT_ID2,
|
|
1119
|
-
code,
|
|
1120
|
-
redirect_uri: REDIRECT_URI,
|
|
1121
|
-
code_verifier: verifier
|
|
1122
|
-
}),
|
|
1123
|
-
signal: oauthRequestSignal()
|
|
1124
|
-
});
|
|
1125
|
-
if (!response.ok) {
|
|
1126
|
-
const text = await response.text();
|
|
1127
|
-
throw new Error(`OpenAI token exchange failed (${response.status}): ${text}`);
|
|
1128
|
-
}
|
|
1129
|
-
const data = await response.json();
|
|
1130
|
-
return {
|
|
1131
|
-
accessToken: data.access_token,
|
|
1132
|
-
refreshToken: data.refresh_token,
|
|
1133
|
-
expiresAt: Date.now() + data.expires_in * 1e3
|
|
1134
|
-
};
|
|
808
|
+
var HOST_ID_PATTERN = /^urn:uuid:[0-9a-f-]{36}$/i;
|
|
809
|
+
async function getChatGPTHostId(file = defaultHostIdFile()) {
|
|
810
|
+
const existing = await import_promises3.default.readFile(file, "utf-8").catch(() => "");
|
|
811
|
+
if (HOST_ID_PATTERN.test(existing.trim())) return existing.trim();
|
|
812
|
+
await writePrivateFile(file, `urn:uuid:${import_node_crypto2.default.randomUUID()}`);
|
|
813
|
+
return (await import_promises3.default.readFile(file, "utf-8")).trim();
|
|
1135
814
|
}
|
|
1136
|
-
async function
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
client_id: CLIENT_ID2
|
|
1144
|
-
}),
|
|
1145
|
-
signal: oauthRequestSignal()
|
|
1146
|
-
});
|
|
1147
|
-
if (!response.ok) {
|
|
1148
|
-
const text = await response.text();
|
|
1149
|
-
throw new Error(`OpenAI token refresh failed (${response.status}): ${text}`);
|
|
1150
|
-
}
|
|
1151
|
-
const data = await response.json();
|
|
1152
|
-
const creds = {
|
|
1153
|
-
accessToken: data.access_token,
|
|
1154
|
-
refreshToken: data.refresh_token,
|
|
1155
|
-
expiresAt: Date.now() + data.expires_in * 1e3
|
|
1156
|
-
};
|
|
1157
|
-
const accountId = getAccountId(creds.accessToken);
|
|
1158
|
-
if (accountId) {
|
|
1159
|
-
creds.accountId = accountId;
|
|
815
|
+
async function readRegistration(file) {
|
|
816
|
+
try {
|
|
817
|
+
const value = JSON.parse(await import_promises3.default.readFile(file, "utf-8"));
|
|
818
|
+
if (typeof value.clientId === "string" && typeof value.subject === "string") {
|
|
819
|
+
return { clientId: value.clientId, subject: value.subject };
|
|
820
|
+
}
|
|
821
|
+
} catch {
|
|
1160
822
|
}
|
|
1161
|
-
return
|
|
823
|
+
return void 0;
|
|
1162
824
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
var CLIENT_ID_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_ID";
|
|
1168
|
-
var CLIENT_SECRET_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_SECRET";
|
|
1169
|
-
var DEFAULT_CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
|
|
1170
|
-
var DEFAULT_CLIENT_SECRET = "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";
|
|
1171
|
-
var AUTHORIZE_URL2 = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
1172
|
-
var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
1173
|
-
var CODE_ASSIST_BASE_URL = "https://cloudcode-pa.googleapis.com";
|
|
1174
|
-
var CODE_ASSIST_API_VERSION = "v1internal";
|
|
1175
|
-
var CODE_ASSIST_POST_RETRIES = 3;
|
|
1176
|
-
var CODE_ASSIST_POST_RETRY_DELAY_MS = 100;
|
|
1177
|
-
var SCOPE2 = [
|
|
1178
|
-
"https://www.googleapis.com/auth/cloud-platform",
|
|
1179
|
-
"https://www.googleapis.com/auth/userinfo.email",
|
|
1180
|
-
"https://www.googleapis.com/auth/userinfo.profile"
|
|
1181
|
-
].join(" ");
|
|
1182
|
-
var USER_TIER_FREE = "free-tier";
|
|
1183
|
-
var USER_TIER_LEGACY = "legacy-tier";
|
|
1184
|
-
var USER_TIER_STANDARD = "standard-tier";
|
|
1185
|
-
var VALIDATION_REQUIRED_REASON = "VALIDATION_REQUIRED";
|
|
1186
|
-
var VPC_SC_REASON = "SECURITY_POLICY_VIOLATED";
|
|
1187
|
-
var CodeAssistHttpError = class extends Error {
|
|
1188
|
-
status;
|
|
1189
|
-
body;
|
|
1190
|
-
constructor(label, status, body) {
|
|
1191
|
-
super(`Gemini Code Assist ${label} failed (${status}): ${body}`);
|
|
1192
|
-
this.name = "CodeAssistHttpError";
|
|
1193
|
-
this.status = status;
|
|
1194
|
-
this.body = body;
|
|
1195
|
-
}
|
|
1196
|
-
};
|
|
1197
|
-
async function loginGemini(callbacks) {
|
|
1198
|
-
const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
|
|
825
|
+
async function loginChatGPT(callbacks, options = {}) {
|
|
826
|
+
const hostId = await getChatGPTHostId(options.hostIdFile);
|
|
827
|
+
const registrationFile = options.registrationFile ?? defaultRegistrationFile();
|
|
828
|
+
const registration = await readRegistration(registrationFile);
|
|
1199
829
|
const { verifier, challenge } = await generatePKCE();
|
|
1200
|
-
const state =
|
|
1201
|
-
const
|
|
1202
|
-
const
|
|
1203
|
-
url.searchParams.set("response_type", "code");
|
|
1204
|
-
url.searchParams.set("client_id", clientId2);
|
|
1205
|
-
url.searchParams.set("redirect_uri", redirectUri);
|
|
1206
|
-
url.searchParams.set("scope", SCOPE2);
|
|
1207
|
-
url.searchParams.set("access_type", "offline");
|
|
1208
|
-
url.searchParams.set("prompt", "consent");
|
|
1209
|
-
url.searchParams.set("code_challenge", challenge);
|
|
1210
|
-
url.searchParams.set("code_challenge_method", "S256");
|
|
1211
|
-
url.searchParams.set("state", state);
|
|
1212
|
-
let code;
|
|
830
|
+
const state = import_node_crypto2.default.randomBytes(16).toString("hex");
|
|
831
|
+
const nonce = import_node_crypto2.default.randomBytes(16).toString("hex");
|
|
832
|
+
const listener = await startCallbackListener(state, Boolean(registration));
|
|
1213
833
|
try {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
)
|
|
1220
|
-
|
|
1221
|
-
if (!parsed.code) {
|
|
1222
|
-
throw new Error("No authorization code found in input.");
|
|
834
|
+
const redirectUri = `http://127.0.0.1:${listener.port}${CALLBACK_PATH}`;
|
|
835
|
+
const url = new URL(AUTHORIZE_URL);
|
|
836
|
+
url.searchParams.set("client_id", registration?.clientId ?? REGISTRATION_CLIENT_ID);
|
|
837
|
+
if (!registration) url.searchParams.set("agent_name_hint", AGENT_NAME);
|
|
838
|
+
url.searchParams.set("ext_agent_host_id", hostId);
|
|
839
|
+
if (registration && options.previous?.idToken) {
|
|
840
|
+
url.searchParams.set("id_token_hint", options.previous.idToken);
|
|
1223
841
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
842
|
+
url.searchParams.set("response_type", "code");
|
|
843
|
+
url.searchParams.set("redirect_uri", redirectUri);
|
|
844
|
+
url.searchParams.set("scope", SCOPES);
|
|
845
|
+
url.searchParams.set("resource", RESOURCE);
|
|
846
|
+
url.searchParams.set("state", state);
|
|
847
|
+
url.searchParams.set("nonce", nonce);
|
|
848
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
849
|
+
url.searchParams.set("code_challenge", challenge);
|
|
850
|
+
const callback = await acquireCallback(url.toString(), state, listener, callbacks);
|
|
851
|
+
if (callback.get("error")) {
|
|
852
|
+
if (callback.get("error") === "access_denied") throw new ChatGPTPlanNotEnabledError();
|
|
853
|
+
throw new Error(
|
|
854
|
+
`ChatGPT sign-in failed: ${callback.get("error_description") ?? callback.get("error")}`
|
|
855
|
+
);
|
|
1226
856
|
}
|
|
1227
|
-
code =
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
return {
|
|
1246
|
-
accessToken: data.access_token,
|
|
1247
|
-
refreshToken: data.refresh_token ?? refreshToken,
|
|
1248
|
-
expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3
|
|
1249
|
-
};
|
|
1250
|
-
}
|
|
1251
|
-
function getGeminiOAuthClientCredentials() {
|
|
1252
|
-
const clientId2 = process.env[CLIENT_ID_ENV]?.trim() || DEFAULT_CLIENT_ID;
|
|
1253
|
-
const clientSecret = process.env[CLIENT_SECRET_ENV]?.trim() || DEFAULT_CLIENT_SECRET;
|
|
1254
|
-
return { clientId: clientId2, clientSecret };
|
|
1255
|
-
}
|
|
1256
|
-
async function getLoopbackRedirectUri() {
|
|
1257
|
-
return new Promise((resolve, reject) => {
|
|
1258
|
-
const server = import_node_http2.default.createServer();
|
|
1259
|
-
server.listen(0, "127.0.0.1", () => {
|
|
1260
|
-
const addr = server.address();
|
|
1261
|
-
server.close(() => {
|
|
1262
|
-
if (addr && typeof addr === "object") {
|
|
1263
|
-
resolve(`http://127.0.0.1:${addr.port}/oauth2callback`);
|
|
1264
|
-
} else {
|
|
1265
|
-
reject(new Error("Failed to allocate OAuth callback port."));
|
|
1266
|
-
}
|
|
857
|
+
const code = callback.get("code");
|
|
858
|
+
if (!code) throw new Error("ChatGPT sign-in returned no authorization code.");
|
|
859
|
+
const issued = callback.get("client_id") ?? void 0;
|
|
860
|
+
if (registration && issued && issued !== registration.clientId) {
|
|
861
|
+
throw new Error("ChatGPT returned a different app registration. Start the sign-in again.");
|
|
862
|
+
}
|
|
863
|
+
const clientId = registration?.clientId ?? issued;
|
|
864
|
+
if (!clientId || clientId === REGISTRATION_CLIENT_ID) {
|
|
865
|
+
throw new Error(
|
|
866
|
+
"ChatGPT didn't finish registering GG Coder. Start the sign-in again, and paste the full callback URL if you're on another machine."
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
const tokens = await exchangeCode({ code, verifier, clientId, redirectUri });
|
|
870
|
+
const claims = await verifyIdToken(tokens.idToken, { clientId, nonce });
|
|
871
|
+
const subject = String(claims.sub ?? "");
|
|
872
|
+
if (!subject) throw new Error("ChatGPT ID token has no subject.");
|
|
873
|
+
if (registration && registration.subject !== subject) {
|
|
874
|
+
await import_promises3.default.unlink(registrationFile).catch(() => {
|
|
1267
875
|
});
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
};
|
|
1281
|
-
} catch {
|
|
1282
|
-
}
|
|
1283
|
-
if (value.includes("code=")) {
|
|
1284
|
-
const params = new URLSearchParams(value);
|
|
876
|
+
await revokeChatGPTSignIn({ refreshToken: tokens.refreshToken, clientId });
|
|
877
|
+
throw new Error(
|
|
878
|
+
"That was a different ChatGPT account. Continue with ChatGPT again to connect it."
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
if (!tokens.scopes.includes(CHATGPT_PLAN_SCOPE)) {
|
|
882
|
+
await revokeChatGPTSignIn({ refreshToken: tokens.refreshToken, clientId });
|
|
883
|
+
throw new ChatGPTPlanNotEnabledError();
|
|
884
|
+
}
|
|
885
|
+
if (!registration) {
|
|
886
|
+
await writePrivateFile(registrationFile, JSON.stringify({ clientId, subject }));
|
|
887
|
+
}
|
|
1285
888
|
return {
|
|
1286
|
-
|
|
1287
|
-
|
|
889
|
+
accessToken: tokens.accessToken,
|
|
890
|
+
refreshToken: tokens.refreshToken,
|
|
891
|
+
expiresAt: Date.now() + tokens.expiresIn * 1e3,
|
|
892
|
+
expiresIn: tokens.expiresIn,
|
|
893
|
+
accountId: subject,
|
|
894
|
+
clientId,
|
|
895
|
+
idToken: tokens.idToken,
|
|
896
|
+
scopes: tokens.scopes,
|
|
897
|
+
baseUrl: CHATGPT_PLAN_BASE_URL
|
|
1288
898
|
};
|
|
899
|
+
} finally {
|
|
900
|
+
listener.close();
|
|
1289
901
|
}
|
|
1290
|
-
return { code: value };
|
|
1291
902
|
}
|
|
1292
|
-
async function
|
|
1293
|
-
const
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
let receivedCode = null;
|
|
1297
|
-
const server = import_node_http2.default.createServer((req, res) => {
|
|
1298
|
-
const url = new URL(req.url || "", redirect.origin);
|
|
1299
|
-
if (url.pathname !== redirect.pathname) {
|
|
1300
|
-
res.statusCode = 404;
|
|
1301
|
-
res.end("Not found");
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
if (url.searchParams.get("state") !== expectedState) {
|
|
1305
|
-
res.statusCode = 400;
|
|
1306
|
-
res.end("State mismatch");
|
|
1307
|
-
return;
|
|
1308
|
-
}
|
|
1309
|
-
receivedCode = url.searchParams.get("code");
|
|
1310
|
-
res.writeHead(200, { "Content-Type": "text/html", Connection: "close" });
|
|
1311
|
-
res.end("<html><body><h1>Login successful!</h1><p>You can close this tab.</p></body></html>");
|
|
1312
|
-
server.close();
|
|
1313
|
-
});
|
|
1314
|
-
server.on("error", (err) => reject(err));
|
|
1315
|
-
server.listen(port, "127.0.0.1", () => {
|
|
1316
|
-
callbacks.onOpenUrl(authUrl);
|
|
1317
|
-
callbacks.onStatus("Waiting for browser callback...");
|
|
1318
|
-
});
|
|
1319
|
-
const timeout = setTimeout(() => {
|
|
1320
|
-
if (!receivedCode) server.close();
|
|
1321
|
-
}, 12e4);
|
|
1322
|
-
timeout.unref();
|
|
1323
|
-
server.on("close", () => {
|
|
1324
|
-
clearTimeout(timeout);
|
|
1325
|
-
if (receivedCode) {
|
|
1326
|
-
resolve(receivedCode);
|
|
1327
|
-
} else {
|
|
1328
|
-
reject(new Error("Server closed without receiving code."));
|
|
1329
|
-
}
|
|
1330
|
-
});
|
|
903
|
+
async function fetchChatGPTPlanModels(accessToken, fetchFn = fetch) {
|
|
904
|
+
const response = await fetchFn(`${CHATGPT_PLAN_BASE_URL}/models`, {
|
|
905
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
906
|
+
signal: oauthRequestSignal()
|
|
1331
907
|
});
|
|
908
|
+
if (!response.ok) throw new Error(`ChatGPT model list failed (${response.status})`);
|
|
909
|
+
const data = await response.json();
|
|
910
|
+
return (data.models ?? []).filter((m) => typeof m.slug === "string" && (m.visibility ?? "list") === "list").map((m) => m.slug);
|
|
1332
911
|
}
|
|
1333
|
-
async function
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
client_secret: clientSecret,
|
|
1338
|
-
code,
|
|
1339
|
-
redirect_uri: redirectUri,
|
|
1340
|
-
code_verifier: verifier
|
|
912
|
+
async function startCallbackListener(expectedState, anyPort) {
|
|
913
|
+
let settle;
|
|
914
|
+
const result = new Promise((resolve, reject) => {
|
|
915
|
+
settle = { resolve, reject };
|
|
1341
916
|
});
|
|
1342
|
-
|
|
1343
|
-
throw new Error("Gemini OAuth did not return a refresh token. Please try login again.");
|
|
1344
|
-
}
|
|
1345
|
-
return {
|
|
1346
|
-
accessToken: data.access_token,
|
|
1347
|
-
refreshToken: data.refresh_token,
|
|
1348
|
-
expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3
|
|
1349
|
-
};
|
|
1350
|
-
}
|
|
1351
|
-
async function postTokenRequest(body) {
|
|
1352
|
-
const response = await fetch(TOKEN_URL2, {
|
|
1353
|
-
method: "POST",
|
|
1354
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1355
|
-
body: new URLSearchParams(body),
|
|
1356
|
-
signal: oauthRequestSignal()
|
|
917
|
+
result.catch(() => {
|
|
1357
918
|
});
|
|
1358
|
-
|
|
1359
|
-
const
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
};
|
|
1374
|
-
const projectMetadata = {
|
|
1375
|
-
...coreMetadata,
|
|
1376
|
-
...envProject ? { duetProject: envProject } : {}
|
|
1377
|
-
};
|
|
1378
|
-
let loadRes;
|
|
1379
|
-
while (true) {
|
|
1380
|
-
loadRes = await loadCodeAssist(accessToken, envProject, projectMetadata);
|
|
1381
|
-
const validation = getValidationRequiredTier(loadRes);
|
|
1382
|
-
if (!validation) break;
|
|
1383
|
-
callbacks.onStatus(
|
|
1384
|
-
`Gemini Code Assist requires account validation${validation.reasonMessage ? `: ${validation.reasonMessage}` : ""}`
|
|
1385
|
-
);
|
|
1386
|
-
callbacks.onOpenUrl(validation.validationUrl);
|
|
1387
|
-
const answer = await callbacks.onPromptCode(
|
|
1388
|
-
"Complete validation in the browser, then press Enter to retry (or type cancel):"
|
|
919
|
+
const server = import_node_http.default.createServer((req, res) => {
|
|
920
|
+
const url = new URL(req.url || "", "http://127.0.0.1");
|
|
921
|
+
if (url.pathname !== CALLBACK_PATH) {
|
|
922
|
+
res.statusCode = 404;
|
|
923
|
+
res.end("Not found");
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
if (url.searchParams.get("state") !== expectedState) {
|
|
927
|
+
res.statusCode = 400;
|
|
928
|
+
res.end("State mismatch");
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
932
|
+
res.end(
|
|
933
|
+
"<html><body><h1>You're signed in to GG Coder.</h1><p>You can close this tab.</p></body></html>"
|
|
1389
934
|
);
|
|
1390
|
-
|
|
1391
|
-
|
|
935
|
+
settle?.resolve(url.searchParams);
|
|
936
|
+
server.close();
|
|
937
|
+
});
|
|
938
|
+
const listen = (port2) => new Promise((resolve, reject) => {
|
|
939
|
+
const onError = (err) => reject(err);
|
|
940
|
+
server.once("error", onError);
|
|
941
|
+
server.listen(port2, "127.0.0.1", () => {
|
|
942
|
+
server.off("error", onError);
|
|
943
|
+
resolve(server.address().port);
|
|
944
|
+
});
|
|
945
|
+
});
|
|
946
|
+
let port;
|
|
947
|
+
for (const candidate of anyPort ? [PREFERRED_PORT, 0] : [PREFERRED_PORT]) {
|
|
948
|
+
try {
|
|
949
|
+
port = await listen(candidate);
|
|
950
|
+
break;
|
|
951
|
+
} catch {
|
|
1392
952
|
}
|
|
1393
953
|
}
|
|
1394
|
-
if (
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
}
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
cloudaicompanionProject: envProject,
|
|
1407
|
-
metadata: projectMetadata
|
|
1408
|
-
};
|
|
1409
|
-
let operation = await codeAssistPost(
|
|
1410
|
-
accessToken,
|
|
1411
|
-
"onboardUser",
|
|
1412
|
-
onboardReq
|
|
1413
|
-
);
|
|
1414
|
-
while (!operation.done && operation.name) {
|
|
1415
|
-
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
1416
|
-
operation = await codeAssistGet(accessToken, operation.name);
|
|
1417
|
-
}
|
|
1418
|
-
const project = operation.response?.cloudaicompanionProject?.id ?? envProject;
|
|
1419
|
-
if (!project) throwProjectError(loadRes);
|
|
1420
|
-
return project;
|
|
954
|
+
if (port === void 0) {
|
|
955
|
+
settle?.reject(new Error("Could not start the local sign-in listener."));
|
|
956
|
+
return { port: PREFERRED_PORT, result, close: () => {
|
|
957
|
+
} };
|
|
958
|
+
}
|
|
959
|
+
const timeout = setTimeout(() => server.close(), CALLBACK_TIMEOUT_MS);
|
|
960
|
+
timeout.unref();
|
|
961
|
+
server.on("close", () => {
|
|
962
|
+
clearTimeout(timeout);
|
|
963
|
+
settle?.reject(new Error("Sign-in listener closed without a callback."));
|
|
964
|
+
});
|
|
965
|
+
return { port, result, close: () => server.close() };
|
|
1421
966
|
}
|
|
1422
|
-
async function
|
|
967
|
+
async function acquireCallback(authUrl, expectedState, listener, callbacks) {
|
|
968
|
+
callbacks.onOpenUrl(authUrl);
|
|
969
|
+
callbacks.onStatus("Waiting for ChatGPT sign-in in your browser...");
|
|
970
|
+
const done = new AbortController();
|
|
1423
971
|
try {
|
|
1424
|
-
return await
|
|
1425
|
-
|
|
1426
|
-
|
|
972
|
+
return await new Promise((resolve, reject) => {
|
|
973
|
+
let listenerFailed = false;
|
|
974
|
+
let pasteError;
|
|
975
|
+
const failIfExhausted = () => {
|
|
976
|
+
if (!listenerFailed || pasteError === void 0) return;
|
|
977
|
+
reject(
|
|
978
|
+
pasteError instanceof Error ? pasteError : new Error("Could not complete the sign-in.")
|
|
979
|
+
);
|
|
980
|
+
};
|
|
981
|
+
listener.result.then(resolve, () => {
|
|
982
|
+
listenerFailed = true;
|
|
983
|
+
failIfExhausted();
|
|
984
|
+
});
|
|
985
|
+
promptForCallback(expectedState, callbacks, done.signal).then(resolve, (err) => {
|
|
986
|
+
if (err instanceof FatalLoginError) {
|
|
987
|
+
reject(err);
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
pasteError = err;
|
|
991
|
+
failIfExhausted();
|
|
992
|
+
});
|
|
1427
993
|
});
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
}
|
|
1432
|
-
if (err instanceof CodeAssistHttpError && err.status === 403 && envProject === "cloudshell-gca") {
|
|
1433
|
-
throw new Error(
|
|
1434
|
-
"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=...",
|
|
1435
|
-
{ cause: err }
|
|
1436
|
-
);
|
|
1437
|
-
}
|
|
1438
|
-
throw err;
|
|
994
|
+
} finally {
|
|
995
|
+
done.abort();
|
|
996
|
+
listener.close();
|
|
1439
997
|
}
|
|
1440
998
|
}
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
);
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
}
|
|
1450
|
-
function throwProjectError(response) {
|
|
1451
|
-
const reasons = response.ineligibleTiers?.map((tier) => tier.reasonMessage ?? tier.tierName).filter((reason) => Boolean(reason));
|
|
1452
|
-
if (reasons && reasons.length > 0) {
|
|
1453
|
-
throw new Error(`Gemini Code Assist setup failed: ${reasons.join(", ")}`);
|
|
999
|
+
var FatalLoginError = class extends Error {
|
|
1000
|
+
};
|
|
1001
|
+
function parseCallbackInput(input) {
|
|
1002
|
+
const value = input.trim();
|
|
1003
|
+
if (!value) return void 0;
|
|
1004
|
+
try {
|
|
1005
|
+
return new URL(value).searchParams;
|
|
1006
|
+
} catch {
|
|
1454
1007
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1008
|
+
if (/[?&]?(code|error)=/.test(value)) {
|
|
1009
|
+
return new URLSearchParams(value.replace(/^.*\?/, ""));
|
|
1010
|
+
}
|
|
1011
|
+
return void 0;
|
|
1458
1012
|
}
|
|
1459
|
-
async function
|
|
1460
|
-
let
|
|
1461
|
-
for (let attempt = 0; attempt
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
throw err;
|
|
1013
|
+
async function promptForCallback(expectedState, callbacks, signal) {
|
|
1014
|
+
let message = "If the browser is on another machine (SSH/headless), paste the full callback URL here:";
|
|
1015
|
+
for (let attempt = 0; attempt < MAX_PASTE_ATTEMPTS; attempt++) {
|
|
1016
|
+
if (signal.aborted) throw new Error("Callback already received.");
|
|
1017
|
+
const raw = await callbacks.onPromptCode(message, signal);
|
|
1018
|
+
if (signal.aborted) throw new Error("Callback already received.");
|
|
1019
|
+
const params = parseCallbackInput(raw);
|
|
1020
|
+
if (params && (params.get("code") || params.get("error"))) {
|
|
1021
|
+
if (params.get("state") !== expectedState) {
|
|
1022
|
+
throw new FatalLoginError("Sign-in state mismatch \u2014 start the sign-in again.");
|
|
1470
1023
|
}
|
|
1471
|
-
|
|
1024
|
+
return params;
|
|
1472
1025
|
}
|
|
1473
|
-
|
|
1026
|
+
message = "That wasn't a callback URL. Paste the full URL from the browser's address bar:";
|
|
1474
1027
|
}
|
|
1475
|
-
throw
|
|
1028
|
+
throw new Error("No callback URL found in input.");
|
|
1476
1029
|
}
|
|
1477
|
-
async function
|
|
1478
|
-
return
|
|
1479
|
-
method: "
|
|
1030
|
+
async function postForm(url, form) {
|
|
1031
|
+
return fetch(url, {
|
|
1032
|
+
method: "POST",
|
|
1033
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1034
|
+
body: new URLSearchParams(form),
|
|
1035
|
+
signal: oauthRequestSignal()
|
|
1480
1036
|
});
|
|
1481
1037
|
}
|
|
1482
|
-
async function
|
|
1483
|
-
const response = await
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1038
|
+
async function exchangeCode(input) {
|
|
1039
|
+
const response = await postForm(TOKEN_URL, {
|
|
1040
|
+
grant_type: "authorization_code",
|
|
1041
|
+
client_id: input.clientId,
|
|
1042
|
+
code: input.code,
|
|
1043
|
+
code_verifier: input.verifier,
|
|
1044
|
+
redirect_uri: input.redirectUri,
|
|
1045
|
+
resource: RESOURCE
|
|
1487
1046
|
});
|
|
1488
1047
|
if (!response.ok) {
|
|
1489
|
-
const text = await response.text();
|
|
1490
|
-
throw new
|
|
1048
|
+
const text = await response.text().catch(() => "");
|
|
1049
|
+
throw new Error(`ChatGPT token exchange failed (${response.status}): ${text.slice(0, 300)}`);
|
|
1491
1050
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
const endpoint = process.env.CODE_ASSIST_ENDPOINT ?? CODE_ASSIST_BASE_URL;
|
|
1496
|
-
const version = process.env.CODE_ASSIST_API_VERSION || CODE_ASSIST_API_VERSION;
|
|
1497
|
-
return `${endpoint}/${version}`;
|
|
1498
|
-
}
|
|
1499
|
-
function getCodeAssistMethodUrl(method) {
|
|
1500
|
-
return `${getCodeAssistBaseUrl()}:${method}`;
|
|
1501
|
-
}
|
|
1502
|
-
function getCodeAssistOperationUrl(operationName) {
|
|
1503
|
-
return `${getCodeAssistBaseUrl()}/${operationName}`;
|
|
1504
|
-
}
|
|
1505
|
-
function shouldRetryCodeAssistStatus(status) {
|
|
1506
|
-
return status === 429 || status === 499 || status >= 500 && status <= 599;
|
|
1507
|
-
}
|
|
1508
|
-
function isVpcScAffectedError(error) {
|
|
1509
|
-
try {
|
|
1510
|
-
const parsed = JSON.parse(error.body);
|
|
1511
|
-
if (!parsed || typeof parsed !== "object" || !("error" in parsed)) return false;
|
|
1512
|
-
const details = parsed.error?.details;
|
|
1513
|
-
return Array.isArray(details) ? details.some(
|
|
1514
|
-
(detail) => detail != null && typeof detail === "object" && "reason" in detail && detail.reason === VPC_SC_REASON
|
|
1515
|
-
) : false;
|
|
1516
|
-
} catch {
|
|
1517
|
-
return false;
|
|
1051
|
+
const data = await response.json();
|
|
1052
|
+
if (!data.access_token || !data.refresh_token || !data.id_token) {
|
|
1053
|
+
throw new Error("ChatGPT token response is missing tokens.");
|
|
1518
1054
|
}
|
|
1519
|
-
}
|
|
1520
|
-
function codeAssistHeaders(accessToken) {
|
|
1521
|
-
return {
|
|
1522
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1523
|
-
"Content-Type": "application/json",
|
|
1524
|
-
"User-Agent": "google-gemini-cli",
|
|
1525
|
-
"X-Goog-Api-Client": "gemini-cli/0.0.0"
|
|
1526
|
-
};
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
// src/oauth/xai.ts
|
|
1530
|
-
var DEFAULT_CLIENT_ID2 = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
1531
|
-
var DEFAULT_ISSUER = "https://auth.x.ai";
|
|
1532
|
-
var DEFAULT_CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
|
|
1533
|
-
var OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:access";
|
|
1534
|
-
var DEFAULT_GROK_CLI_VERSION = "1.0.67";
|
|
1535
|
-
var DEVICE_TIMEOUT_FALLBACK_MS = 10 * 60 * 1e3;
|
|
1536
|
-
var DEFAULT_TOKEN_LIFETIME_SECONDS = 3600;
|
|
1537
|
-
function issuer() {
|
|
1538
|
-
return (process.env.XAI_OAUTH_ISSUER ?? DEFAULT_ISSUER).replace(/\/+$/, "");
|
|
1539
|
-
}
|
|
1540
|
-
function clientId() {
|
|
1541
|
-
return process.env.XAI_OAUTH_CLIENT_ID ?? DEFAULT_CLIENT_ID2;
|
|
1542
|
-
}
|
|
1543
|
-
function grokCliBaseUrl() {
|
|
1544
|
-
return (process.env.XAI_CLI_BASE_URL ?? DEFAULT_CLI_BASE_URL).replace(/\/+$/, "");
|
|
1545
|
-
}
|
|
1546
|
-
function grokCliVersion() {
|
|
1547
|
-
const raw = process.env.GROK_CLI_VERSION ?? DEFAULT_GROK_CLI_VERSION;
|
|
1548
|
-
const cleaned = raw.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
1549
|
-
return cleaned.length > 0 ? cleaned : DEFAULT_GROK_CLI_VERSION;
|
|
1550
|
-
}
|
|
1551
|
-
function grokCliHeaders(modelId) {
|
|
1552
1055
|
return {
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1056
|
+
accessToken: data.access_token,
|
|
1057
|
+
refreshToken: data.refresh_token,
|
|
1058
|
+
idToken: data.id_token,
|
|
1059
|
+
expiresIn: data.expires_in ?? 3600,
|
|
1060
|
+
scopes: (data.scope ?? "").split(/\s+/).filter(Boolean)
|
|
1557
1061
|
};
|
|
1558
1062
|
}
|
|
1559
|
-
function
|
|
1560
|
-
if (
|
|
1561
|
-
|
|
1562
|
-
return normalized === grokCliBaseUrl() || /(^|\.)grok\.com/i.test(normalized);
|
|
1563
|
-
}
|
|
1564
|
-
async function postForm2(endpoint, params) {
|
|
1565
|
-
const response = await fetch(`${issuer()}${endpoint}`, {
|
|
1566
|
-
method: "POST",
|
|
1567
|
-
headers: {
|
|
1568
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
1569
|
-
Accept: "application/json",
|
|
1570
|
-
"User-Agent": `grok-cli/${grokCliVersion()}`
|
|
1571
|
-
},
|
|
1572
|
-
body: new URLSearchParams(params).toString(),
|
|
1573
|
-
signal: oauthRequestSignal()
|
|
1574
|
-
});
|
|
1575
|
-
let data = {};
|
|
1576
|
-
try {
|
|
1577
|
-
const parsed = await response.json();
|
|
1578
|
-
if (parsed && typeof parsed === "object") data = parsed;
|
|
1579
|
-
} catch {
|
|
1580
|
-
}
|
|
1581
|
-
return { status: response.status, data };
|
|
1582
|
-
}
|
|
1583
|
-
function errorDetail2(data) {
|
|
1584
|
-
const desc = data.error_description ?? data.message ?? data.error;
|
|
1585
|
-
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
1586
|
-
}
|
|
1587
|
-
function jwtExpirySeconds(token) {
|
|
1588
|
-
const payload = token.split(".")[1];
|
|
1589
|
-
if (!payload) return void 0;
|
|
1590
|
-
try {
|
|
1591
|
-
const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString(
|
|
1592
|
-
"utf-8"
|
|
1593
|
-
);
|
|
1594
|
-
const claims = JSON.parse(json);
|
|
1595
|
-
if (!claims || typeof claims !== "object") return void 0;
|
|
1596
|
-
const exp = claims.exp;
|
|
1597
|
-
return typeof exp === "number" && Number.isFinite(exp) && exp > 0 ? exp : void 0;
|
|
1598
|
-
} catch {
|
|
1599
|
-
return void 0;
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
function credsFromTokenResponse2(data, opts) {
|
|
1603
|
-
const accessToken = data.access_token;
|
|
1604
|
-
const responseRefreshToken = data.refresh_token;
|
|
1605
|
-
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
1606
|
-
throw new Error("Grok OAuth response missing access_token.");
|
|
1063
|
+
async function refreshChatGPTToken(creds) {
|
|
1064
|
+
if (!creds.clientId) {
|
|
1065
|
+
throw new Error("ChatGPT token refresh failed (401): no issued client ID saved");
|
|
1607
1066
|
}
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1067
|
+
const response = await postForm(TOKEN_URL, {
|
|
1068
|
+
grant_type: "refresh_token",
|
|
1069
|
+
client_id: creds.clientId,
|
|
1070
|
+
refresh_token: creds.refreshToken,
|
|
1071
|
+
resource: RESOURCE
|
|
1072
|
+
});
|
|
1073
|
+
if (!response.ok) {
|
|
1074
|
+
const text = await response.text().catch(() => "");
|
|
1075
|
+
throw new Error(`ChatGPT token refresh failed (${response.status}): ${text.slice(0, 300)}`);
|
|
1613
1076
|
}
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
expiresIn = responseExpiresIn;
|
|
1618
|
-
} else {
|
|
1619
|
-
const exp = jwtExpirySeconds(accessToken);
|
|
1620
|
-
const fromJwt = exp !== void 0 ? exp - Math.floor(Date.now() / 1e3) : 0;
|
|
1621
|
-
expiresIn = fromJwt > 0 ? fromJwt : DEFAULT_TOKEN_LIFETIME_SECONDS;
|
|
1077
|
+
const data = await response.json();
|
|
1078
|
+
if (!data.access_token || !data.refresh_token) {
|
|
1079
|
+
throw new Error("ChatGPT token refresh returned no tokens.");
|
|
1622
1080
|
}
|
|
1081
|
+
const expiresIn = data.expires_in ?? 3600;
|
|
1623
1082
|
return {
|
|
1624
|
-
accessToken,
|
|
1625
|
-
refreshToken,
|
|
1083
|
+
accessToken: data.access_token,
|
|
1084
|
+
refreshToken: data.refresh_token,
|
|
1626
1085
|
expiresAt: Date.now() + expiresIn * 1e3,
|
|
1627
1086
|
expiresIn,
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
}
|
|
1631
|
-
async function requestDeviceAuthorization2() {
|
|
1632
|
-
const { status, data } = await postForm2("/oauth2/device/code", {
|
|
1633
|
-
client_id: clientId(),
|
|
1634
|
-
scope: OAUTH_SCOPE
|
|
1635
|
-
});
|
|
1636
|
-
if (status !== 200) {
|
|
1637
|
-
throw new Error(`Grok device authorization failed (${status}): ${errorDetail2(data)}`);
|
|
1638
|
-
}
|
|
1639
|
-
const userCode = data.user_code;
|
|
1640
|
-
const deviceCode = data.device_code;
|
|
1641
|
-
const verificationUriComplete = data.verification_uri_complete;
|
|
1642
|
-
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
1643
|
-
throw new Error("Grok device authorization response missing user_code/device_code.");
|
|
1644
|
-
}
|
|
1645
|
-
return {
|
|
1646
|
-
userCode,
|
|
1647
|
-
deviceCode,
|
|
1648
|
-
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
1649
|
-
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
1650
|
-
interval: Number(data.interval ?? 5) || 5,
|
|
1651
|
-
// RFC 8628 §3.2: the server states how long the device code lives. Honor it
|
|
1652
|
-
// rather than imposing our own budget — polling a code we know is dead only
|
|
1653
|
-
// burns requests, and a longer local window would keep a user waiting past
|
|
1654
|
-
// the point the code can ever succeed.
|
|
1655
|
-
expiresInMs: (Number(data.expires_in) || 0) > 0 ? Number(data.expires_in) * 1e3 : DEVICE_TIMEOUT_FALLBACK_MS
|
|
1087
|
+
...data.id_token ? { idToken: data.id_token } : {},
|
|
1088
|
+
...data.scope ? { scopes: data.scope.split(/\s+/).filter(Boolean) } : {}
|
|
1656
1089
|
};
|
|
1657
1090
|
}
|
|
1658
|
-
async function
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
switch (errorCode) {
|
|
1672
|
-
case "authorization_pending":
|
|
1673
|
-
return { kind: "pending" };
|
|
1674
|
-
case "slow_down":
|
|
1675
|
-
return { kind: "slow_down" };
|
|
1676
|
-
case "expired_token":
|
|
1677
|
-
return { kind: "expired" };
|
|
1678
|
-
case "access_denied":
|
|
1679
|
-
return { kind: "denied" };
|
|
1680
|
-
default:
|
|
1681
|
-
throw new Error(`Grok token polling failed (${status}): ${errorDetail2(data)}`);
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
function sleep2(ms) {
|
|
1685
|
-
return new Promise((resolve) => {
|
|
1686
|
-
setTimeout(resolve, ms);
|
|
1687
|
-
});
|
|
1688
|
-
}
|
|
1689
|
-
async function loginXai(callbacks) {
|
|
1690
|
-
const auth = await requestDeviceAuthorization2();
|
|
1691
|
-
callbacks.onStatus(
|
|
1692
|
-
`Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
|
|
1693
|
-
);
|
|
1694
|
-
callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
|
|
1695
|
-
callbacks.onStatus("Waiting for you to authorize in the browser...");
|
|
1696
|
-
const deadline = Date.now() + auth.expiresInMs;
|
|
1697
|
-
let interval = Math.max(auth.interval, 1);
|
|
1698
|
-
while (Date.now() < deadline) {
|
|
1699
|
-
const remaining = deadline - Date.now();
|
|
1700
|
-
await sleep2(Math.min(interval * 1e3, remaining));
|
|
1701
|
-
const result = await pollDeviceToken2(auth.deviceCode);
|
|
1702
|
-
if (result.kind === "success") return result.creds;
|
|
1703
|
-
if (result.kind === "denied") {
|
|
1704
|
-
throw new Error("Grok authorization was denied.");
|
|
1705
|
-
}
|
|
1706
|
-
if (result.kind === "expired") {
|
|
1707
|
-
throw new Error("Grok device code expired. Please run login again.");
|
|
1091
|
+
async function revokeChatGPTSignIn(creds, options = {}) {
|
|
1092
|
+
if (!creds.clientId || !creds.refreshToken) return false;
|
|
1093
|
+
const attempts = options.attempts ?? 3;
|
|
1094
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
1095
|
+
try {
|
|
1096
|
+
const response = await postForm(REVOKE_URL, {
|
|
1097
|
+
token: creds.refreshToken,
|
|
1098
|
+
token_type_hint: "refresh_token",
|
|
1099
|
+
client_id: creds.clientId
|
|
1100
|
+
});
|
|
1101
|
+
if (response.ok) return true;
|
|
1102
|
+
if (response.status < 500) return false;
|
|
1103
|
+
} catch {
|
|
1708
1104
|
}
|
|
1709
|
-
if (
|
|
1710
|
-
|
|
1105
|
+
if (attempt < attempts - 1) {
|
|
1106
|
+
await new Promise((r) => setTimeout(r, (options.backoffMs ?? 500) * 2 ** attempt));
|
|
1711
1107
|
}
|
|
1712
1108
|
}
|
|
1713
|
-
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1109
|
+
return false;
|
|
1110
|
+
}
|
|
1111
|
+
function decodeSegment(segment) {
|
|
1112
|
+
return JSON.parse(Buffer.from(segment, "base64url").toString("utf-8"));
|
|
1113
|
+
}
|
|
1114
|
+
async function verifyIdToken(idToken, expected) {
|
|
1115
|
+
const [headerSeg, payloadSeg, signatureSeg] = idToken.split(".");
|
|
1116
|
+
if (!headerSeg || !payloadSeg || !signatureSeg) throw new Error("Malformed ChatGPT ID token.");
|
|
1117
|
+
const header = decodeSegment(headerSeg);
|
|
1118
|
+
if (header.alg !== "RS256") throw new Error("Unexpected ChatGPT ID token algorithm.");
|
|
1119
|
+
const response = await fetch(JWKS_URL, { signal: oauthRequestSignal() });
|
|
1120
|
+
if (!response.ok) throw new Error(`Could not load OpenAI signing keys (${response.status}).`);
|
|
1121
|
+
const { keys = [] } = await response.json();
|
|
1122
|
+
const jwk = keys.find((k) => k.kid === header.kid);
|
|
1123
|
+
if (!jwk) throw new Error("ChatGPT ID token is signed with an unknown key.");
|
|
1124
|
+
const valid = import_node_crypto2.default.verify(
|
|
1125
|
+
"RSA-SHA256",
|
|
1126
|
+
Buffer.from(`${headerSeg}.${payloadSeg}`),
|
|
1127
|
+
import_node_crypto2.default.createPublicKey({ key: jwk, format: "jwk" }),
|
|
1128
|
+
Buffer.from(signatureSeg, "base64url")
|
|
1129
|
+
);
|
|
1130
|
+
if (!valid) throw new Error("ChatGPT ID token signature is invalid.");
|
|
1131
|
+
const claims = decodeSegment(payloadSeg);
|
|
1132
|
+
const now = Math.floor((expected.now ?? Date.now()) / 1e3);
|
|
1133
|
+
const aud = claims.aud;
|
|
1134
|
+
const audiences = Array.isArray(aud) ? aud : [aud];
|
|
1135
|
+
if (claims.iss !== ISSUER) throw new Error("ChatGPT ID token has the wrong issuer.");
|
|
1136
|
+
if (!audiences.includes(expected.clientId)) {
|
|
1137
|
+
throw new Error("ChatGPT ID token was issued to another app.");
|
|
1138
|
+
}
|
|
1139
|
+
if (typeof claims.exp !== "number" || claims.exp + ID_TOKEN_CLOCK_SKEW_S < now) {
|
|
1140
|
+
throw new Error("ChatGPT ID token has expired.");
|
|
1723
1141
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1142
|
+
if (claims.nonce !== expected.nonce) throw new Error("ChatGPT ID token nonce mismatch.");
|
|
1143
|
+
return claims;
|
|
1726
1144
|
}
|
|
1727
1145
|
|
|
1728
1146
|
// src/file-lock.ts
|
|
1729
|
-
var
|
|
1730
|
-
var
|
|
1147
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
1148
|
+
var import_promises5 = require("timers/promises");
|
|
1731
1149
|
var STALE_TIMEOUT_MS = 1e4;
|
|
1732
1150
|
var RETRY_INTERVAL_MS = 50;
|
|
1733
1151
|
var MAX_WAIT_MS = 5e3;
|
|
@@ -1747,7 +1165,7 @@ async function acquireLock(lockPath) {
|
|
|
1747
1165
|
while (true) {
|
|
1748
1166
|
try {
|
|
1749
1167
|
const info = { pid: process.pid, timestamp: Date.now() };
|
|
1750
|
-
await
|
|
1168
|
+
await import_promises4.default.writeFile(lockPath, JSON.stringify(info), { flag: "wx" });
|
|
1751
1169
|
return;
|
|
1752
1170
|
} catch (err) {
|
|
1753
1171
|
const code = err.code;
|
|
@@ -1756,47 +1174,47 @@ async function acquireLock(lockPath) {
|
|
|
1756
1174
|
const lockPresent = await lockFileExists(lockPath);
|
|
1757
1175
|
if (!lockPresent && ++refusedWithoutLock > 3) throw err;
|
|
1758
1176
|
if (Date.now() - startTime > MAX_WAIT_MS) throw err;
|
|
1759
|
-
await (0,
|
|
1177
|
+
await (0, import_promises5.setTimeout)(RETRY_INTERVAL_MS);
|
|
1760
1178
|
continue;
|
|
1761
1179
|
}
|
|
1762
1180
|
try {
|
|
1763
|
-
const content = await
|
|
1181
|
+
const content = await import_promises4.default.readFile(lockPath, "utf-8");
|
|
1764
1182
|
const info = JSON.parse(content);
|
|
1765
1183
|
const isProcessAlive = isAlive(info.pid);
|
|
1766
1184
|
const isStale = Date.now() - info.timestamp > STALE_TIMEOUT_MS;
|
|
1767
1185
|
if (!isProcessAlive || isStale) {
|
|
1768
|
-
await
|
|
1186
|
+
await import_promises4.default.unlink(lockPath).catch(() => {
|
|
1769
1187
|
});
|
|
1770
1188
|
continue;
|
|
1771
1189
|
}
|
|
1772
1190
|
} catch {
|
|
1773
|
-
const stat = await
|
|
1191
|
+
const stat = await import_promises4.default.stat(lockPath).catch(() => null);
|
|
1774
1192
|
if (!stat) continue;
|
|
1775
1193
|
if (Date.now() - stat.mtimeMs > STALE_TIMEOUT_MS) {
|
|
1776
|
-
await
|
|
1194
|
+
await import_promises4.default.unlink(lockPath).catch(() => {
|
|
1777
1195
|
});
|
|
1778
1196
|
continue;
|
|
1779
1197
|
}
|
|
1780
1198
|
}
|
|
1781
1199
|
if (Date.now() - startTime > MAX_WAIT_MS) {
|
|
1782
|
-
await
|
|
1200
|
+
await import_promises4.default.unlink(lockPath).catch(() => {
|
|
1783
1201
|
});
|
|
1784
1202
|
continue;
|
|
1785
1203
|
}
|
|
1786
|
-
await (0,
|
|
1204
|
+
await (0, import_promises5.setTimeout)(RETRY_INTERVAL_MS);
|
|
1787
1205
|
}
|
|
1788
1206
|
}
|
|
1789
1207
|
}
|
|
1790
1208
|
async function lockFileExists(lockPath) {
|
|
1791
1209
|
try {
|
|
1792
|
-
await
|
|
1210
|
+
await import_promises4.default.stat(lockPath);
|
|
1793
1211
|
return true;
|
|
1794
1212
|
} catch (err) {
|
|
1795
1213
|
return err.code !== "ENOENT";
|
|
1796
1214
|
}
|
|
1797
1215
|
}
|
|
1798
1216
|
async function releaseLock(lockPath) {
|
|
1799
|
-
await
|
|
1217
|
+
await import_promises4.default.unlink(lockPath).catch(() => {
|
|
1800
1218
|
});
|
|
1801
1219
|
}
|
|
1802
1220
|
function isAlive(pid) {
|
|
@@ -1810,10 +1228,10 @@ function isAlive(pid) {
|
|
|
1810
1228
|
}
|
|
1811
1229
|
|
|
1812
1230
|
// src/logger.ts
|
|
1813
|
-
var
|
|
1231
|
+
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
1814
1232
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
1815
|
-
var
|
|
1816
|
-
var
|
|
1233
|
+
var import_node_crypto3 = require("crypto");
|
|
1234
|
+
var import_gg_ai2 = require("@kenkaiiii/gg-ai");
|
|
1817
1235
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
1818
1236
|
var MIN_HEADROOM_BYTES = 64 * 1024;
|
|
1819
1237
|
var fd = null;
|
|
@@ -1828,14 +1246,14 @@ var cleanups = [];
|
|
|
1828
1246
|
var exactSecrets = [];
|
|
1829
1247
|
function rotateIfNeeded(filePath, force = false) {
|
|
1830
1248
|
try {
|
|
1831
|
-
const st =
|
|
1249
|
+
const st = import_node_fs3.default.statSync(filePath);
|
|
1832
1250
|
if (!force && st.size <= MAX_BYTES - MIN_HEADROOM_BYTES) return;
|
|
1833
1251
|
const rotated = `${filePath}.1`;
|
|
1834
1252
|
try {
|
|
1835
|
-
|
|
1253
|
+
import_node_fs3.default.unlinkSync(rotated);
|
|
1836
1254
|
} catch {
|
|
1837
1255
|
}
|
|
1838
|
-
|
|
1256
|
+
import_node_fs3.default.renameSync(filePath, rotated);
|
|
1839
1257
|
} catch {
|
|
1840
1258
|
}
|
|
1841
1259
|
}
|
|
@@ -1843,18 +1261,18 @@ function rotateOpenLog() {
|
|
|
1843
1261
|
if (fd === null || logPath === null) return false;
|
|
1844
1262
|
const sizeBefore = fileBytes;
|
|
1845
1263
|
try {
|
|
1846
|
-
|
|
1264
|
+
import_node_fs3.default.closeSync(fd);
|
|
1847
1265
|
} catch {
|
|
1848
1266
|
}
|
|
1849
1267
|
fd = null;
|
|
1850
1268
|
rotateIfNeeded(logPath, true);
|
|
1851
1269
|
try {
|
|
1852
|
-
fd =
|
|
1853
|
-
fileBytes =
|
|
1270
|
+
fd = import_node_fs3.default.openSync(logPath, "a");
|
|
1271
|
+
fileBytes = import_node_fs3.default.fstatSync(fd).size;
|
|
1854
1272
|
} catch {
|
|
1855
1273
|
if (fd !== null) {
|
|
1856
1274
|
try {
|
|
1857
|
-
|
|
1275
|
+
import_node_fs3.default.closeSync(fd);
|
|
1858
1276
|
} catch {
|
|
1859
1277
|
}
|
|
1860
1278
|
}
|
|
@@ -1868,24 +1286,24 @@ function rotateOpenLog() {
|
|
|
1868
1286
|
function openLog(filePath, name) {
|
|
1869
1287
|
if (fd !== null || capped) return false;
|
|
1870
1288
|
appName = name;
|
|
1871
|
-
exactSecrets = (0,
|
|
1289
|
+
exactSecrets = (0, import_gg_ai2.environmentSecrets)(process.env);
|
|
1872
1290
|
try {
|
|
1873
|
-
|
|
1291
|
+
import_node_fs3.default.mkdirSync(import_node_path4.default.dirname(filePath), { recursive: true, mode: 448 });
|
|
1874
1292
|
} catch {
|
|
1875
1293
|
}
|
|
1876
1294
|
rotateIfNeeded(filePath);
|
|
1877
1295
|
try {
|
|
1878
|
-
fd =
|
|
1296
|
+
fd = import_node_fs3.default.openSync(filePath, "a");
|
|
1879
1297
|
} catch {
|
|
1880
1298
|
fd = null;
|
|
1881
1299
|
fileBytes = 0;
|
|
1882
1300
|
return false;
|
|
1883
1301
|
}
|
|
1884
1302
|
try {
|
|
1885
|
-
fileBytes =
|
|
1303
|
+
fileBytes = import_node_fs3.default.fstatSync(fd).size;
|
|
1886
1304
|
} catch {
|
|
1887
1305
|
try {
|
|
1888
|
-
|
|
1306
|
+
import_node_fs3.default.closeSync(fd);
|
|
1889
1307
|
} catch {
|
|
1890
1308
|
}
|
|
1891
1309
|
fd = null;
|
|
@@ -1895,10 +1313,10 @@ function openLog(filePath, name) {
|
|
|
1895
1313
|
logPath = filePath;
|
|
1896
1314
|
processBytes = 0;
|
|
1897
1315
|
rotationFailed = false;
|
|
1898
|
-
sessionId = (0,
|
|
1316
|
+
sessionId = (0, import_node_crypto3.randomBytes)(4).toString("hex");
|
|
1899
1317
|
try {
|
|
1900
1318
|
if (fileBytes < MAX_BYTES) {
|
|
1901
|
-
const written =
|
|
1319
|
+
const written = import_node_fs3.default.writeSync(fd, "\n");
|
|
1902
1320
|
fileBytes += written;
|
|
1903
1321
|
processBytes += written;
|
|
1904
1322
|
}
|
|
@@ -1915,10 +1333,10 @@ function isLoggerOpen() {
|
|
|
1915
1333
|
function log(level, category, message, data) {
|
|
1916
1334
|
if (fd === null) return;
|
|
1917
1335
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1918
|
-
const safeMessage = (0,
|
|
1336
|
+
const safeMessage = (0, import_gg_ai2.redactText)(message, { secrets: exactSecrets });
|
|
1919
1337
|
let line = `[${ts}] [sid=${sessionId}] [${level}] [${category}] ${safeMessage}`;
|
|
1920
1338
|
if (data) {
|
|
1921
|
-
const safeData = (0,
|
|
1339
|
+
const safeData = (0, import_gg_ai2.redactValue)(data, { secrets: exactSecrets });
|
|
1922
1340
|
const pairs = Object.entries(safeData).map(([k, v]) => {
|
|
1923
1341
|
if (typeof v === "string") return `${k}=${v}`;
|
|
1924
1342
|
if (typeof v === "bigint") return `${k}=${String(v)}`;
|
|
@@ -1933,9 +1351,9 @@ function log(level, category, message, data) {
|
|
|
1933
1351
|
`;
|
|
1934
1352
|
try {
|
|
1935
1353
|
if (fileBytes + Buffer.byteLength(capLine) <= MAX_BYTES) {
|
|
1936
|
-
fileBytes +=
|
|
1354
|
+
fileBytes += import_node_fs3.default.writeSync(fd, capLine);
|
|
1937
1355
|
}
|
|
1938
|
-
|
|
1356
|
+
import_node_fs3.default.closeSync(fd);
|
|
1939
1357
|
} catch {
|
|
1940
1358
|
}
|
|
1941
1359
|
fd = null;
|
|
@@ -1945,7 +1363,7 @@ function log(level, category, message, data) {
|
|
|
1945
1363
|
if (fileBytes + lineBytes > MAX_BYTES && !rotationFailed && !rotateOpenLog()) return;
|
|
1946
1364
|
if (fd === null) return;
|
|
1947
1365
|
try {
|
|
1948
|
-
const written =
|
|
1366
|
+
const written = import_node_fs3.default.writeSync(fd, line);
|
|
1949
1367
|
fileBytes += written;
|
|
1950
1368
|
processBytes += written;
|
|
1951
1369
|
} catch {
|
|
@@ -1958,7 +1376,7 @@ function closeLogger(opts) {
|
|
|
1958
1376
|
if (fd !== null) {
|
|
1959
1377
|
if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
|
|
1960
1378
|
try {
|
|
1961
|
-
if (fd !== null)
|
|
1379
|
+
if (fd !== null) import_node_fs3.default.closeSync(fd);
|
|
1962
1380
|
} catch {
|
|
1963
1381
|
}
|
|
1964
1382
|
}
|
|
@@ -1974,13 +1392,13 @@ function closeLogger(opts) {
|
|
|
1974
1392
|
}
|
|
1975
1393
|
|
|
1976
1394
|
// src/hidden-providers.ts
|
|
1977
|
-
var HIDDEN_PROVIDERS = [
|
|
1395
|
+
var HIDDEN_PROVIDERS = [];
|
|
1978
1396
|
function isHiddenProvider(provider) {
|
|
1979
1397
|
return HIDDEN_PROVIDERS.includes(provider);
|
|
1980
1398
|
}
|
|
1981
1399
|
|
|
1982
1400
|
// src/auth-storage.ts
|
|
1983
|
-
var
|
|
1401
|
+
var import_gg_ai3 = require("@kenkaiiii/gg-ai");
|
|
1984
1402
|
|
|
1985
1403
|
// src/minimax-region.ts
|
|
1986
1404
|
var MINIMAX_GLOBAL_BASE_URL = "https://api.minimax.io/anthropic";
|
|
@@ -2065,22 +1483,76 @@ async function detectXiaomiTokenPlanBaseUrl(apiKey, options = {}) {
|
|
|
2065
1483
|
}
|
|
2066
1484
|
|
|
2067
1485
|
// src/auth-storage.ts
|
|
2068
|
-
var
|
|
2069
|
-
var
|
|
2070
|
-
var
|
|
1486
|
+
var OPENAI_CHATGPT_KEY = "openai-chatgpt";
|
|
1487
|
+
var KIMI_CODE_KEY = "kimi-code";
|
|
1488
|
+
var RETIRED_SIGN_INS = [
|
|
1489
|
+
{
|
|
1490
|
+
provider: "anthropic",
|
|
1491
|
+
storageKey: "anthropic",
|
|
1492
|
+
message: "Your old Claude sign-in no longer works. Sign in with Claude again to keep using your subscription."
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
provider: "openai",
|
|
1496
|
+
storageKey: "openai",
|
|
1497
|
+
message: "Your old ChatGPT sign-in no longer works. Continue with ChatGPT again, or add an OpenAI API key."
|
|
1498
|
+
},
|
|
2071
1499
|
{
|
|
2072
1500
|
provider: "moonshot",
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
apiKeyLabel: "Moonshot API key",
|
|
2076
|
-
restoreHint: 'Run "ggcoder login" and choose Kimi OAuth to restore OAuth auth.'
|
|
1501
|
+
storageKey: "moonshot-oauth",
|
|
1502
|
+
message: "Kimi sign-in was removed. Paste a Kimi Code plan key from the Kimi Code Console, or a Moonshot API key."
|
|
2077
1503
|
},
|
|
2078
1504
|
{
|
|
2079
1505
|
provider: "xai",
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
1506
|
+
storageKey: "xai-oauth",
|
|
1507
|
+
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."
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
provider: "gemini",
|
|
1511
|
+
storageKey: "gemini",
|
|
1512
|
+
message: "Google sign-in was removed. Add a Gemini API key from Google AI Studio instead."
|
|
1513
|
+
}
|
|
1514
|
+
];
|
|
1515
|
+
function isRetiredEntry(retired, value) {
|
|
1516
|
+
if (!value) return false;
|
|
1517
|
+
switch (retired.storageKey) {
|
|
1518
|
+
case "anthropic":
|
|
1519
|
+
return isLegacyClaudeOAuth(value);
|
|
1520
|
+
// These slots now hold API keys; only a refreshable sign-in token is retired.
|
|
1521
|
+
case "openai":
|
|
1522
|
+
case "gemini":
|
|
1523
|
+
return Boolean(value.refreshToken) || Boolean(value.accountId) || Boolean(value.projectId);
|
|
1524
|
+
default:
|
|
1525
|
+
return true;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
function isRetiredStorageKey(data, storageKey) {
|
|
1529
|
+
return RETIRED_SIGN_INS.some(
|
|
1530
|
+
(r) => r.storageKey === storageKey && isRetiredEntry(r, data[storageKey])
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
function usableEntry(data, storageKey) {
|
|
1534
|
+
return isRetiredStorageKey(data, storageKey) ? void 0 : data[storageKey];
|
|
1535
|
+
}
|
|
1536
|
+
function storageKeyProvider(storageKey) {
|
|
1537
|
+
if (storageKey === OPENAI_CHATGPT_KEY) return "openai";
|
|
1538
|
+
if (storageKey === KIMI_CODE_KEY) return "moonshot";
|
|
1539
|
+
if (storageKey === XIAOMI_CREDITS_KEY) return "xiaomi";
|
|
1540
|
+
if (storageKey === GEMINI_API_KEY) return "gemini";
|
|
1541
|
+
return storageKey;
|
|
1542
|
+
}
|
|
1543
|
+
function apiKeyStorageKeys(provider) {
|
|
1544
|
+
if (provider === "moonshot") return [KIMI_CODE_KEY, "moonshot"];
|
|
1545
|
+
if (provider === "xiaomi") return ["xiaomi", XIAOMI_CREDITS_KEY];
|
|
1546
|
+
if (provider === "gemini") return [GEMINI_API_KEY];
|
|
1547
|
+
return [provider];
|
|
1548
|
+
}
|
|
1549
|
+
var DUAL_AUTH_PROVIDERS = [
|
|
1550
|
+
{
|
|
1551
|
+
provider: "openai",
|
|
1552
|
+
oauthKey: OPENAI_CHATGPT_KEY,
|
|
1553
|
+
oauthLabel: "ChatGPT plan",
|
|
1554
|
+
apiKeyLabel: "OpenAI API key",
|
|
1555
|
+
restoreHint: 'Run "ggcoder login" and choose Continue with ChatGPT to use your ChatGPT plan again.'
|
|
2084
1556
|
}
|
|
2085
1557
|
];
|
|
2086
1558
|
function dualAuthProvider(provider) {
|
|
@@ -2105,14 +1577,20 @@ function activeBaseUrlEntry(data, provider) {
|
|
|
2105
1577
|
const dual = dualAuthProvider(provider);
|
|
2106
1578
|
if (dual) {
|
|
2107
1579
|
const oauth = data[dual.oauthKey];
|
|
1580
|
+
const apiKey = usableEntry(data, dual.provider);
|
|
2108
1581
|
if (oauth) {
|
|
2109
1582
|
const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
|
|
2110
|
-
if (Date.now() < exhaustedUntil &&
|
|
1583
|
+
if (Date.now() < exhaustedUntil && apiKey) return apiKey;
|
|
2111
1584
|
return oauth;
|
|
2112
1585
|
}
|
|
2113
|
-
return
|
|
1586
|
+
return apiKey;
|
|
1587
|
+
}
|
|
1588
|
+
const key = apiKeyStorageKeys(provider).find((k) => usableEntry(data, k));
|
|
1589
|
+
const entry = key ? data[key] : void 0;
|
|
1590
|
+
if (key === KIMI_CODE_KEY && entry && !entry.baseUrl) {
|
|
1591
|
+
return { ...entry, baseUrl: KIMI_CODE_DEFAULT_BASE_URL };
|
|
2114
1592
|
}
|
|
2115
|
-
return
|
|
1593
|
+
return entry;
|
|
2116
1594
|
}
|
|
2117
1595
|
var MINIMAX_REGION_RETRY_MS = 5 * 60 * 1e3;
|
|
2118
1596
|
var miniMaxRegionProbes = /* @__PURE__ */ new Map();
|
|
@@ -2123,9 +1601,17 @@ function cachedMiniMaxRegion(apiKey) {
|
|
|
2123
1601
|
miniMaxRegionProbes.set(apiKey, { at: Date.now(), result });
|
|
2124
1602
|
return result;
|
|
2125
1603
|
}
|
|
1604
|
+
var kimiRegionProbes = /* @__PURE__ */ new Map();
|
|
1605
|
+
function cachedKimiRegion(apiKey) {
|
|
1606
|
+
const cached = kimiRegionProbes.get(apiKey);
|
|
1607
|
+
if (cached && Date.now() - cached.at < MINIMAX_REGION_RETRY_MS) return cached.result;
|
|
1608
|
+
const result = detectKimiCodeBaseUrl(apiKey).catch(() => void 0);
|
|
1609
|
+
kimiRegionProbes.set(apiKey, { at: Date.now(), result });
|
|
1610
|
+
return result;
|
|
1611
|
+
}
|
|
2126
1612
|
function readStoredBaseUrlSync(authFile, provider) {
|
|
2127
1613
|
try {
|
|
2128
|
-
const data = JSON.parse((0,
|
|
1614
|
+
const data = JSON.parse((0, import_node_fs4.readFileSync)(authFile, "utf-8"));
|
|
2129
1615
|
return activeBaseUrlEntry(data, provider)?.baseUrl;
|
|
2130
1616
|
} catch {
|
|
2131
1617
|
return void 0;
|
|
@@ -2138,21 +1624,7 @@ function refreshThresholdMs(creds) {
|
|
|
2138
1624
|
return Math.max(MIN_REFRESH_THRESHOLD_MS, lifetimeMs * REFRESH_THRESHOLD_RATIO);
|
|
2139
1625
|
}
|
|
2140
1626
|
var USAGE_EXHAUSTED_DEFAULT_MS = 15 * 60 * 1e3;
|
|
2141
|
-
var
|
|
2142
|
-
// Anthropic is API-key only; Claude subscriptions run through Claude Code.
|
|
2143
|
-
"anthropic",
|
|
2144
|
-
"glm",
|
|
2145
|
-
"moonshot",
|
|
2146
|
-
"xiaomi",
|
|
2147
|
-
"minimax",
|
|
2148
|
-
"deepseek",
|
|
2149
|
-
"openrouter",
|
|
2150
|
-
"huggingface",
|
|
2151
|
-
"sakana",
|
|
2152
|
-
"xai",
|
|
2153
|
-
// Local endpoints: a fixed (usually placeholder) key, never refreshable.
|
|
2154
|
-
"local"
|
|
2155
|
-
]);
|
|
1627
|
+
var REFRESHABLE_STORAGE_KEYS = /* @__PURE__ */ new Set([OPENAI_CHATGPT_KEY]);
|
|
2156
1628
|
var AuthStorage = class {
|
|
2157
1629
|
data = {};
|
|
2158
1630
|
filePath;
|
|
@@ -2173,10 +1645,12 @@ var AuthStorage = class {
|
|
|
2173
1645
|
refreshLocks = /* @__PURE__ */ new Map();
|
|
2174
1646
|
detectMiniMaxRegion;
|
|
2175
1647
|
detectXiaomiCluster;
|
|
1648
|
+
detectKimiRegion;
|
|
2176
1649
|
constructor(filePath, options) {
|
|
2177
1650
|
this.filePath = filePath ?? getAppPaths().authFile;
|
|
2178
1651
|
this.detectMiniMaxRegion = options?.detectMiniMaxBaseUrl ?? cachedMiniMaxRegion;
|
|
2179
1652
|
this.detectXiaomiCluster = options?.detectXiaomiTokenPlanBaseUrl ?? detectXiaomiTokenPlanBaseUrl;
|
|
1653
|
+
this.detectKimiRegion = options?.detectKimiCodeBaseUrl ?? cachedKimiRegion;
|
|
2180
1654
|
}
|
|
2181
1655
|
/** Path to the on-disk auth file. Useful for status output. */
|
|
2182
1656
|
get path() {
|
|
@@ -2204,22 +1678,15 @@ var AuthStorage = class {
|
|
|
2204
1678
|
return keys.find((key) => Boolean(this.data[key]));
|
|
2205
1679
|
}
|
|
2206
1680
|
/**
|
|
2207
|
-
* True if the user has any usable auth for the logical provider
|
|
2208
|
-
*
|
|
2209
|
-
* satisfies it.
|
|
1681
|
+
* True if the user has any usable auth for the logical provider: the plan
|
|
1682
|
+
* sign-in or any of its API keys. A retired sign-in doesn't count.
|
|
2210
1683
|
*/
|
|
2211
1684
|
async hasProviderAuth(provider) {
|
|
2212
1685
|
if (isHiddenProvider(provider)) return false;
|
|
2213
1686
|
await this.ensureFresh();
|
|
2214
1687
|
const dual = dualAuthProvider(provider);
|
|
2215
1688
|
if (dual) {
|
|
2216
|
-
return Boolean(this.data[dual.oauthKey] || this.data
|
|
2217
|
-
}
|
|
2218
|
-
if (provider === "xiaomi") {
|
|
2219
|
-
return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
|
|
2220
|
-
}
|
|
2221
|
-
if (provider === "gemini") {
|
|
2222
|
-
return Boolean(this.data["gemini"] || this.data[GEMINI_API_KEY]);
|
|
1689
|
+
return Boolean(this.data[dual.oauthKey] || usableEntry(this.data, dual.provider));
|
|
2223
1690
|
}
|
|
2224
1691
|
if (provider === "anthropic") {
|
|
2225
1692
|
return Boolean(this.data.anthropic) && !isLegacyClaudeOAuth(this.data.anthropic) || isClaudeCodeAuthMarker(this.data[CLAUDE_CODE_AUTH_KEY]);
|
|
@@ -2227,7 +1694,25 @@ var AuthStorage = class {
|
|
|
2227
1694
|
if (provider === "local") {
|
|
2228
1695
|
return Object.keys(this.data).some((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX));
|
|
2229
1696
|
}
|
|
2230
|
-
return Boolean(this.data
|
|
1697
|
+
return apiKeyStorageKeys(provider).some((key) => Boolean(usableEntry(this.data, key)));
|
|
1698
|
+
}
|
|
1699
|
+
/** True if any of `provider`'s API keys is stored. A retired sign-in in a key slot doesn't count. */
|
|
1700
|
+
async hasApiKey(provider) {
|
|
1701
|
+
await this.ensureFresh();
|
|
1702
|
+
if (provider === "anthropic" && isLegacyClaudeOAuth(this.data.anthropic)) return false;
|
|
1703
|
+
return apiKeyStorageKeys(provider).some((key) => Boolean(usableEntry(this.data, key)));
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* `provider`'s first stored API key, never its plan sign-in. For calls that
|
|
1707
|
+
* only an API key may make (OpenAI's Images API isn't on the ChatGPT plan).
|
|
1708
|
+
*/
|
|
1709
|
+
async resolveApiKey(provider) {
|
|
1710
|
+
await this.ensureFresh();
|
|
1711
|
+
for (const key of apiKeyStorageKeys(provider)) {
|
|
1712
|
+
const creds = usableEntry(this.data, key);
|
|
1713
|
+
if (creds && !(provider === "anthropic" && isLegacyClaudeOAuth(creds))) return creds;
|
|
1714
|
+
}
|
|
1715
|
+
throw new NotLoggedInError(provider);
|
|
2231
1716
|
}
|
|
2232
1717
|
/** True when the user signed in with Claude through Claude Code (marker only, no token). */
|
|
2233
1718
|
async hasClaudeCodeLogin() {
|
|
@@ -2241,17 +1726,24 @@ var AuthStorage = class {
|
|
|
2241
1726
|
if (isLegacyClaudeOAuth(latest.anthropic)) delete latest.anthropic;
|
|
2242
1727
|
});
|
|
2243
1728
|
}
|
|
1729
|
+
/** Retired sign-ins still on disk, so the UI can ask the user to reconnect. */
|
|
1730
|
+
async legacySignIns() {
|
|
1731
|
+
await this.ensureFresh();
|
|
1732
|
+
return RETIRED_SIGN_INS.filter((r) => isRetiredEntry(r, this.data[r.storageKey]));
|
|
1733
|
+
}
|
|
2244
1734
|
/**
|
|
2245
|
-
* Remove
|
|
2246
|
-
*
|
|
1735
|
+
* Remove `provider`'s retired sign-in, if any. True when one was removed, so
|
|
1736
|
+
* the caller can tell the user once to reconnect.
|
|
2247
1737
|
*/
|
|
2248
|
-
async
|
|
1738
|
+
async takeLegacySignIn(provider) {
|
|
1739
|
+
const retired = RETIRED_SIGN_INS.find((r) => r.provider === provider);
|
|
1740
|
+
if (!retired) return false;
|
|
2249
1741
|
await this.ensureFresh();
|
|
2250
|
-
if (!
|
|
1742
|
+
if (!isRetiredEntry(retired, this.data[retired.storageKey])) return false;
|
|
2251
1743
|
let removed = false;
|
|
2252
1744
|
await this.mutateLatest((latest) => {
|
|
2253
|
-
if (!
|
|
2254
|
-
delete latest.
|
|
1745
|
+
if (!isRetiredEntry(retired, latest[retired.storageKey])) return;
|
|
1746
|
+
delete latest[retired.storageKey];
|
|
2255
1747
|
removed = true;
|
|
2256
1748
|
});
|
|
2257
1749
|
return removed;
|
|
@@ -2271,7 +1763,7 @@ var AuthStorage = class {
|
|
|
2271
1763
|
*/
|
|
2272
1764
|
async setLocalEndpoint(endpointId, baseUrl, apiKey) {
|
|
2273
1765
|
await this.setCredentials(`${LOCAL_AUTH_KEY_PREFIX}${endpointId}`, {
|
|
2274
|
-
accessToken: apiKey && apiKey.length > 0 ? apiKey :
|
|
1766
|
+
accessToken: apiKey && apiKey.length > 0 ? apiKey : import_gg_ai3.LOCAL_NO_API_KEY,
|
|
2275
1767
|
refreshToken: "",
|
|
2276
1768
|
expiresAt: Date.now() + LOCAL_CREDENTIAL_LIFETIME_MS,
|
|
2277
1769
|
baseUrl
|
|
@@ -2292,18 +1784,18 @@ var AuthStorage = class {
|
|
|
2292
1784
|
const oauthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
2293
1785
|
if (dual && oauthCreds) {
|
|
2294
1786
|
const exhaustedUntil = oauthCreds.usageExhaustedUntil ?? 0;
|
|
2295
|
-
const apiKeyActive = Date.now() < exhaustedUntil && Boolean(this.data
|
|
1787
|
+
const apiKeyActive = Date.now() < exhaustedUntil && Boolean(usableEntry(this.data, dual.provider));
|
|
2296
1788
|
if (!apiKeyActive) return false;
|
|
2297
1789
|
}
|
|
2298
|
-
return
|
|
1790
|
+
return !REFRESHABLE_STORAGE_KEYS.has(provider);
|
|
2299
1791
|
}
|
|
2300
1792
|
/**
|
|
2301
1793
|
* The base URL on the credential that is active right now, if any.
|
|
2302
1794
|
* Synchronous — call only after load()/resolveCredentials() populated the
|
|
2303
|
-
* snapshot. For a dual-auth provider this is the
|
|
2304
|
-
*
|
|
2305
|
-
*
|
|
2306
|
-
*
|
|
1795
|
+
* snapshot. For a dual-auth provider this is the plan endpoint whenever the
|
|
1796
|
+
* sign-in is the one resolveCredentials would serve (i.e. not currently
|
|
1797
|
+
* usage-exhausted with an API key configured); for a plan/pay-per-token key
|
|
1798
|
+
* split it is the plan key's endpoint when one is stored.
|
|
2307
1799
|
*/
|
|
2308
1800
|
getStoredBaseUrl(provider) {
|
|
2309
1801
|
return activeBaseUrlEntry(this.data, provider)?.baseUrl;
|
|
@@ -2312,7 +1804,7 @@ var AuthStorage = class {
|
|
|
2312
1804
|
const first = !this.loaded;
|
|
2313
1805
|
await withFileLock(this.filePath, async () => {
|
|
2314
1806
|
try {
|
|
2315
|
-
const content = await
|
|
1807
|
+
const content = await import_promises6.default.readFile(this.filePath, "utf-8");
|
|
2316
1808
|
this.data = JSON.parse(content);
|
|
2317
1809
|
if (first) {
|
|
2318
1810
|
log("INFO", "auth", `Loaded credentials from ${this.filePath}`, {
|
|
@@ -2358,7 +1850,7 @@ var AuthStorage = class {
|
|
|
2358
1850
|
}
|
|
2359
1851
|
let changed;
|
|
2360
1852
|
try {
|
|
2361
|
-
const stat = await
|
|
1853
|
+
const stat = await import_promises6.default.stat(this.filePath);
|
|
2362
1854
|
changed = stat.ino !== this.snapshotIno || stat.mtimeMs !== this.snapshotMtimeMs || stat.size !== this.snapshotSize;
|
|
2363
1855
|
} catch {
|
|
2364
1856
|
changed = this.snapshotSize !== -1;
|
|
@@ -2371,7 +1863,7 @@ var AuthStorage = class {
|
|
|
2371
1863
|
*/
|
|
2372
1864
|
async rememberSnapshot() {
|
|
2373
1865
|
try {
|
|
2374
|
-
const stat = await
|
|
1866
|
+
const stat = await import_promises6.default.stat(this.filePath);
|
|
2375
1867
|
this.snapshotMtimeMs = stat.mtimeMs;
|
|
2376
1868
|
this.snapshotSize = stat.size;
|
|
2377
1869
|
this.snapshotIno = stat.ino;
|
|
@@ -2426,9 +1918,34 @@ var AuthStorage = class {
|
|
|
2426
1918
|
});
|
|
2427
1919
|
return { ...creds, baseUrl };
|
|
2428
1920
|
}
|
|
2429
|
-
|
|
1921
|
+
/**
|
|
1922
|
+
* A Kimi Code key saved without an endpoint gets the host that issued it
|
|
1923
|
+
* (`api.kimi.com` or `api.kimi.ai`), saved so the check runs once.
|
|
1924
|
+
*/
|
|
1925
|
+
async withKimiRegion(creds) {
|
|
1926
|
+
const baseUrl = await this.detectKimiRegion(creds.accessToken);
|
|
1927
|
+
if (!baseUrl) return { ...creds, baseUrl: KIMI_CODE_DEFAULT_BASE_URL };
|
|
1928
|
+
log("INFO", "auth", `Kimi Code key belongs to ${baseUrl} \u2014 saved for future requests`);
|
|
1929
|
+
await this.mutateLatest((latest) => {
|
|
1930
|
+
const current = latest[KIMI_CODE_KEY];
|
|
1931
|
+
if (current?.accessToken === creds.accessToken && !current.baseUrl) {
|
|
1932
|
+
current.baseUrl = baseUrl;
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
1935
|
+
return { ...creds, baseUrl };
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Store `creds` under `storageKey`. Connecting a provider again also removes
|
|
1939
|
+
* its retired sign-in, which is what the reconnect notice asked for.
|
|
1940
|
+
*/
|
|
1941
|
+
async setCredentials(storageKey, creds) {
|
|
1942
|
+
const provider = storageKeyProvider(storageKey);
|
|
2430
1943
|
await this.mutateLatest((latest) => {
|
|
2431
|
-
|
|
1944
|
+
for (const retired of RETIRED_SIGN_INS) {
|
|
1945
|
+
if (retired.provider !== provider || retired.storageKey === storageKey) continue;
|
|
1946
|
+
if (isRetiredEntry(retired, latest[retired.storageKey])) delete latest[retired.storageKey];
|
|
1947
|
+
}
|
|
1948
|
+
latest[storageKey] = creds;
|
|
2432
1949
|
});
|
|
2433
1950
|
}
|
|
2434
1951
|
/**
|
|
@@ -2465,9 +1982,9 @@ var AuthStorage = class {
|
|
|
2465
1982
|
* Mark the credential stored under `storageKey` as usage-exhausted until
|
|
2466
1983
|
* `resetsAt` (unix SECONDS, from the provider's rate-limit response) or a
|
|
2467
1984
|
* 15-minute default when no reset time is known. While the mark is in the
|
|
2468
|
-
* future, `resolveCredentials("
|
|
2469
|
-
*
|
|
2470
|
-
*
|
|
1985
|
+
* future, `resolveCredentials("openai")` serves the OpenAI API key instead of
|
|
1986
|
+
* the ChatGPT plan sign-in (when both are configured) — the plan stays the
|
|
1987
|
+
* preferred credential and is retried automatically once the mark
|
|
2471
1988
|
* lapses. Persisted to auth.json so a restart (or another gg-app window)
|
|
2472
1989
|
* doesn't burn a request rediscovering the same exhausted window. No-op if
|
|
2473
1990
|
* nothing is stored under `storageKey`.
|
|
@@ -2512,12 +2029,12 @@ var AuthStorage = class {
|
|
|
2512
2029
|
await this.ensureFresh();
|
|
2513
2030
|
}
|
|
2514
2031
|
const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : providerStorageKeys(provider);
|
|
2515
|
-
if (!directStorageKeys.some((key) => Boolean(this.data
|
|
2032
|
+
if (!directStorageKeys.some((key) => Boolean(usableEntry(this.data, key))) && !(provider === "anthropic" && this.data[CLAUDE_CODE_AUTH_KEY])) {
|
|
2516
2033
|
await this.reloadLatest();
|
|
2517
2034
|
}
|
|
2518
2035
|
if (opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider)) {
|
|
2519
2036
|
for (const key of opts.storageKeys) {
|
|
2520
|
-
const creds2 = this.data
|
|
2037
|
+
const creds2 = usableEntry(this.data, key);
|
|
2521
2038
|
if (!creds2) continue;
|
|
2522
2039
|
if (key !== provider && dualAuthProviderByOAuthKey(key)) {
|
|
2523
2040
|
return await this.resolveCredentials(key, {
|
|
@@ -2531,6 +2048,7 @@ var AuthStorage = class {
|
|
|
2531
2048
|
...opts.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
|
|
2532
2049
|
});
|
|
2533
2050
|
}
|
|
2051
|
+
if (key === KIMI_CODE_KEY && !creds2.baseUrl) return await this.withKimiRegion(creds2);
|
|
2534
2052
|
return creds2;
|
|
2535
2053
|
}
|
|
2536
2054
|
throw new NotLoggedInError(provider);
|
|
@@ -2543,15 +2061,16 @@ var AuthStorage = class {
|
|
|
2543
2061
|
}
|
|
2544
2062
|
const dual = dualAuthProvider(provider);
|
|
2545
2063
|
const dualOAuthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
2064
|
+
const dualApiKey = dual ? usableEntry(this.data, dual.provider) : void 0;
|
|
2546
2065
|
if (dual && dualOAuthCreds) {
|
|
2547
2066
|
const exhaustedUntil = dualOAuthCreds.usageExhaustedUntil ?? 0;
|
|
2548
|
-
if (Date.now() < exhaustedUntil &&
|
|
2067
|
+
if (Date.now() < exhaustedUntil && dualApiKey) {
|
|
2549
2068
|
log(
|
|
2550
2069
|
"WARN",
|
|
2551
2070
|
"auth",
|
|
2552
2071
|
`${dual.oauthLabel} usage window is exhausted \u2014 using the ${dual.apiKeyLabel} until ${new Date(exhaustedUntil).toISOString()} (OAuth resumes automatically).`
|
|
2553
2072
|
);
|
|
2554
|
-
return
|
|
2073
|
+
return dualApiKey;
|
|
2555
2074
|
}
|
|
2556
2075
|
try {
|
|
2557
2076
|
return await this.resolveCredentials(dual.oauthKey, {
|
|
@@ -2559,23 +2078,24 @@ var AuthStorage = class {
|
|
|
2559
2078
|
...opts?.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
|
|
2560
2079
|
});
|
|
2561
2080
|
} catch (err) {
|
|
2562
|
-
if (err instanceof NotLoggedInError &&
|
|
2081
|
+
if (err instanceof NotLoggedInError && dualApiKey) {
|
|
2563
2082
|
log(
|
|
2564
2083
|
"WARN",
|
|
2565
2084
|
"auth",
|
|
2566
2085
|
`${dual.oauthLabel} credential is no longer valid \u2014 falling back to the ${dual.apiKeyLabel}. ${dual.restoreHint}`
|
|
2567
2086
|
);
|
|
2568
|
-
return
|
|
2087
|
+
return dualApiKey;
|
|
2569
2088
|
}
|
|
2570
2089
|
throw err;
|
|
2571
2090
|
}
|
|
2572
2091
|
}
|
|
2573
|
-
const creds = this.data
|
|
2092
|
+
const creds = usableEntry(this.data, provider);
|
|
2574
2093
|
if (!creds) {
|
|
2575
2094
|
throw new NotLoggedInError(provider);
|
|
2576
2095
|
}
|
|
2577
|
-
if (
|
|
2096
|
+
if (!REFRESHABLE_STORAGE_KEYS.has(provider)) {
|
|
2578
2097
|
if (provider === "minimax" && !creds.baseUrl) return await this.withMiniMaxRegion(creds);
|
|
2098
|
+
if (provider === KIMI_CODE_KEY && !creds.baseUrl) return await this.withKimiRegion(creds);
|
|
2579
2099
|
return creds;
|
|
2580
2100
|
}
|
|
2581
2101
|
if (!opts?.forceRefresh && Date.now() < creds.expiresAt - refreshThresholdMs(creds)) {
|
|
@@ -2603,13 +2123,14 @@ var AuthStorage = class {
|
|
|
2603
2123
|
this.data = latest;
|
|
2604
2124
|
return latestCreds;
|
|
2605
2125
|
}
|
|
2606
|
-
const refreshFn = provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : provider === XAI_OAUTH_KEY ? refreshXaiToken : refreshOpenAIToken;
|
|
2607
2126
|
let refreshed;
|
|
2608
2127
|
try {
|
|
2609
|
-
refreshed = await
|
|
2128
|
+
refreshed = await refreshChatGPTToken(latestCreds);
|
|
2610
2129
|
} catch (err) {
|
|
2611
2130
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2612
|
-
const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh
|
|
2131
|
+
const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh|token_expired|refresh_token_(?:expired|invalidated|reused)/i.test(
|
|
2132
|
+
msg
|
|
2133
|
+
) || /unauthorized/i.test(msg);
|
|
2613
2134
|
if (isAuthFailure) {
|
|
2614
2135
|
const current = await readAuthData(this.filePath);
|
|
2615
2136
|
const currentCreds = current[provider];
|
|
@@ -2629,15 +2150,10 @@ var AuthStorage = class {
|
|
|
2629
2150
|
}
|
|
2630
2151
|
throw err;
|
|
2631
2152
|
}
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
refreshed.projectId = latestCreds.projectId;
|
|
2637
|
-
}
|
|
2638
|
-
if (!refreshed.baseUrl && latestCreds.baseUrl) {
|
|
2639
|
-
refreshed.baseUrl = latestCreds.baseUrl;
|
|
2640
|
-
}
|
|
2153
|
+
refreshed = {
|
|
2154
|
+
...pick(latestCreds, ["accountId", "baseUrl", "clientId", "idToken", "scopes"]),
|
|
2155
|
+
...refreshed
|
|
2156
|
+
};
|
|
2641
2157
|
latest[provider] = refreshed;
|
|
2642
2158
|
this.data = latest;
|
|
2643
2159
|
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
@@ -2660,9 +2176,14 @@ var AuthStorage = class {
|
|
|
2660
2176
|
return creds.accessToken;
|
|
2661
2177
|
}
|
|
2662
2178
|
};
|
|
2179
|
+
function pick(value, keys) {
|
|
2180
|
+
const out = {};
|
|
2181
|
+
for (const key of keys) if (value[key] !== void 0) out[key] = value[key];
|
|
2182
|
+
return out;
|
|
2183
|
+
}
|
|
2663
2184
|
async function readAuthData(filePath) {
|
|
2664
2185
|
try {
|
|
2665
|
-
const content = await
|
|
2186
|
+
const content = await import_promises6.default.readFile(filePath, "utf-8");
|
|
2666
2187
|
return JSON.parse(content);
|
|
2667
2188
|
} catch (error) {
|
|
2668
2189
|
if (error.code === "ENOENT") return {};
|
|
@@ -2670,12 +2191,12 @@ async function readAuthData(filePath) {
|
|
|
2670
2191
|
}
|
|
2671
2192
|
}
|
|
2672
2193
|
async function atomicWriteFile(filePath, content) {
|
|
2673
|
-
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${
|
|
2194
|
+
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${import_node_crypto4.default.randomUUID().slice(0, 8)}.tmp`;
|
|
2674
2195
|
try {
|
|
2675
|
-
await
|
|
2676
|
-
await
|
|
2196
|
+
await import_promises6.default.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
|
|
2197
|
+
await import_promises6.default.rename(tmpPath, filePath);
|
|
2677
2198
|
} catch (err) {
|
|
2678
|
-
await
|
|
2199
|
+
await import_promises6.default.unlink(tmpPath).catch(() => {
|
|
2679
2200
|
});
|
|
2680
2201
|
throw err;
|
|
2681
2202
|
}
|
|
@@ -2707,7 +2228,8 @@ var CODEX_FAST_BILLING = {
|
|
|
2707
2228
|
detail: "Codex Fast mode uses your ChatGPT plan limits at 2.5x the standard rate (purchased credits at 2x).",
|
|
2708
2229
|
paid: false
|
|
2709
2230
|
};
|
|
2710
|
-
var GEMINI_AUTH_KEYS = [GEMINI_API_KEY
|
|
2231
|
+
var GEMINI_AUTH_KEYS = [GEMINI_API_KEY];
|
|
2232
|
+
var KIMI_AUTH_KEYS = [KIMI_CODE_KEY, "moonshot"];
|
|
2711
2233
|
var MODELS = [
|
|
2712
2234
|
// ── Anthropic ──────────────────────────────────────────
|
|
2713
2235
|
// NOTE: Claude Mythos 5.1 (`claude-mythos-5-1`, 2026-09-01) is kept commented
|
|
@@ -2837,14 +2359,13 @@ var MODELS = [
|
|
|
2837
2359
|
{
|
|
2838
2360
|
// GPT-6 Astra — "Our most capable model for complex, demanding work."
|
|
2839
2361
|
// (Codex catalog priority 2, listed for every ChatGPT plan, requires a
|
|
2840
|
-
// Codex client >= 0.153.0
|
|
2841
|
-
// 1.05M on the public Responses API, 272K on the ChatGPT
|
|
2362
|
+
// Codex client >= 0.153.0). Same split as 5.6:
|
|
2363
|
+
// 1.05M on the public Responses API, 272K on the ChatGPT plan route
|
|
2842
2364
|
// (openai/codex models.json, `gpt-6-astra`). Reasoning ladder low → medium
|
|
2843
2365
|
// → high → xhigh → max → ultra; `ultra` is the Codex orchestration preset
|
|
2844
2366
|
// (multi_agent v2) and is Codex-only — the public API tops out at `max`.
|
|
2845
|
-
//
|
|
2846
|
-
//
|
|
2847
|
-
// Codex route is the supported way to use it as an agent.
|
|
2367
|
+
// OpenAI requires the Responses API for tool calling on Astra, so both
|
|
2368
|
+
// the ChatGPT plan and API keys ride gg-ai's Responses transport.
|
|
2848
2369
|
id: "gpt-6-astra",
|
|
2849
2370
|
name: "GPT-6 Astra",
|
|
2850
2371
|
provider: "openai",
|
|
@@ -2887,9 +2408,7 @@ var MODELS = [
|
|
|
2887
2408
|
// next start.
|
|
2888
2409
|
{
|
|
2889
2410
|
// GPT-6.1 Sol — "Latest workhorse model for coding and everyday work."
|
|
2890
|
-
// (Codex priority 1, default low).
|
|
2891
|
-
// but the ChatGPT backend only serves it from 0.159.0 (see
|
|
2892
|
-
// CODEX_CLIENT_VERSION). $2/$10 MTok, cached input $0.10. Ladder low →
|
|
2411
|
+
// (Codex priority 1, default low). $2/$10 MTok, cached input $0.10. Ladder low →
|
|
2893
2412
|
// medium → high → xhigh → max → ultra; ultra is the Codex orchestration
|
|
2894
2413
|
// preset (xhigh effort on the wire + proactive local subagent delegation).
|
|
2895
2414
|
id: "gpt-6.1-sol",
|
|
@@ -3076,8 +2595,8 @@ var MODELS = [
|
|
|
3076
2595
|
maxThinkingLevel: "high",
|
|
3077
2596
|
authStorageKeys: GEMINI_AUTH_KEYS
|
|
3078
2597
|
},
|
|
3079
|
-
// Keep 3.1 Flash Lite first for the
|
|
3080
|
-
//
|
|
2598
|
+
// Keep 3.1 Flash Lite first for the default and fast-model routing.
|
|
2599
|
+
// Gemini 3.7
|
|
3081
2600
|
// Flash is retired: Google deprecated it on 2026-10-08 and routes every
|
|
3082
2601
|
// `gemini-3.7-flash` request to 3.8 Flash (ai.google.dev/gemini-api/docs/
|
|
3083
2602
|
// changelog), so a saved session on it falls back to the provider default.
|
|
@@ -3110,9 +2629,7 @@ var MODELS = [
|
|
|
3110
2629
|
authStorageKeys: GEMINI_AUTH_KEYS
|
|
3111
2630
|
},
|
|
3112
2631
|
{
|
|
3113
|
-
// Wire name `gemini-3-flash
|
|
3114
|
-
// display string `gemini-3.5-flash` with a 404, so gemini-cli keeps this
|
|
3115
|
-
// alternative name (SECONDARY_GEMINI_3_5_FLASH_MODEL) for that endpoint.
|
|
2632
|
+
// Wire name `gemini-3-flash`, the id Google serves 3.5 Flash under.
|
|
3116
2633
|
id: "gemini-3-flash",
|
|
3117
2634
|
name: "Gemini 3.5 Flash",
|
|
3118
2635
|
provider: "gemini",
|
|
@@ -3127,9 +2644,7 @@ var MODELS = [
|
|
|
3127
2644
|
authStorageKeys: GEMINI_AUTH_KEYS
|
|
3128
2645
|
},
|
|
3129
2646
|
{
|
|
3130
|
-
// Gemini 3.1 Pro is public preview
|
|
3131
|
-
// enablement, so free/personal OAuth accounts 404 on it (see
|
|
3132
|
-
// ACCOUNT_GATED_MODELS in gg-ai's gemini provider).
|
|
2647
|
+
// Gemini 3.1 Pro is public preview.
|
|
3133
2648
|
id: "gemini-3.1-pro-preview",
|
|
3134
2649
|
name: "Gemini 3.1 Pro (Preview)",
|
|
3135
2650
|
provider: "gemini",
|
|
@@ -3146,8 +2661,8 @@ var MODELS = [
|
|
|
3146
2661
|
// ── Moonshot (Kimi) ────────────────────────────────────
|
|
3147
2662
|
// K3 is Kimi's 2.8T-parameter flagship for long-horizon coding, knowledge
|
|
3148
2663
|
// work, and deep reasoning. Its effort ladder is server-declared as
|
|
3149
|
-
// low/high/max on both the public API (default max) and the Kimi
|
|
3150
|
-
//
|
|
2664
|
+
// low/high/max on both the public API (default max) and the Kimi Code plan
|
|
2665
|
+
// endpoint (default high); thinking can also be fully disabled.
|
|
3151
2666
|
{
|
|
3152
2667
|
id: "kimi-k3",
|
|
3153
2668
|
name: "Kimi K3",
|
|
@@ -3161,13 +2676,14 @@ var MODELS = [
|
|
|
3161
2676
|
supportsVideo: true,
|
|
3162
2677
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
3163
2678
|
costTier: "high",
|
|
3164
|
-
maxThinkingLevel: "max"
|
|
2679
|
+
maxThinkingLevel: "max",
|
|
2680
|
+
authStorageKeys: KIMI_AUTH_KEYS
|
|
3165
2681
|
},
|
|
3166
|
-
// K2.8 Preview (2026-09-11) is served only on the Kimi
|
|
3167
|
-
//
|
|
2682
|
+
// K2.8 Preview (2026-09-11) is served only on the Kimi Code plan endpoint,
|
|
2683
|
+
// under its rolling `kimi-for-coding` id (live /models, 2026-09-28:
|
|
3168
2684
|
// display_name "K2.8 Preview", 1M context, image + video input, efforts
|
|
3169
|
-
// low/high/max default max). The public API
|
|
3170
|
-
//
|
|
2685
|
+
// low/high/max default max). The public Moonshot API does not serve it, so
|
|
2686
|
+
// it resolves from a Kimi Code plan key only.
|
|
3171
2687
|
{
|
|
3172
2688
|
id: "kimi-for-coding",
|
|
3173
2689
|
name: "Kimi K2.8 Preview",
|
|
@@ -3180,7 +2696,7 @@ var MODELS = [
|
|
|
3180
2696
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
3181
2697
|
costTier: "medium",
|
|
3182
2698
|
maxThinkingLevel: "max",
|
|
3183
|
-
authStorageKeys: [
|
|
2699
|
+
authStorageKeys: [KIMI_CODE_KEY]
|
|
3184
2700
|
},
|
|
3185
2701
|
// K2.7 Code is requested by its pinned id (not the `kimi-for-coding` alias
|
|
3186
2702
|
// that moved to K2.8), so it stays the real K2.7 on both endpoints.
|
|
@@ -3195,7 +2711,8 @@ var MODELS = [
|
|
|
3195
2711
|
supportsVideo: true,
|
|
3196
2712
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
3197
2713
|
costTier: "medium",
|
|
3198
|
-
maxThinkingLevel: "high"
|
|
2714
|
+
maxThinkingLevel: "high",
|
|
2715
|
+
authStorageKeys: KIMI_AUTH_KEYS
|
|
3199
2716
|
},
|
|
3200
2717
|
// K2.7 Code HighSpeed — the same model as K2.7 Code served at ~180 tok/s
|
|
3201
2718
|
// (up to ~260 on short contexts), 256K context (platform.kimi.ai model list,
|
|
@@ -3214,7 +2731,8 @@ var MODELS = [
|
|
|
3214
2731
|
supportsVideo: true,
|
|
3215
2732
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
3216
2733
|
costTier: "medium",
|
|
3217
|
-
maxThinkingLevel: "high"
|
|
2734
|
+
maxThinkingLevel: "high",
|
|
2735
|
+
authStorageKeys: KIMI_AUTH_KEYS
|
|
3218
2736
|
},
|
|
3219
2737
|
// ── Z.AI (GLM) ─────────────────────────────────────────
|
|
3220
2738
|
// Two GLM entries, both live on the coding endpoint (verified against its
|
|
@@ -3816,12 +3334,12 @@ function clampThinkingForPlanMode(level) {
|
|
|
3816
3334
|
}
|
|
3817
3335
|
|
|
3818
3336
|
// src/local-models.ts
|
|
3819
|
-
var
|
|
3337
|
+
var import_gg_ai4 = require("@kenkaiiii/gg-ai");
|
|
3820
3338
|
var DEFAULT_LOCAL_ENDPOINTS = [
|
|
3821
3339
|
{ id: "ollama", label: "Ollama", baseUrl: "http://127.0.0.1:11434/v1", kind: "ollama" }
|
|
3822
3340
|
];
|
|
3823
3341
|
var FALLBACK_CONTEXT_WINDOW = 4096;
|
|
3824
|
-
var LOCAL_API_KEY_PLACEHOLDER =
|
|
3342
|
+
var LOCAL_API_KEY_PLACEHOLDER = import_gg_ai4.LOCAL_NO_API_KEY;
|
|
3825
3343
|
var DEFAULT_PROBE_TIMEOUT_MS = 1200;
|
|
3826
3344
|
var ENRICH_CONCURRENCY = 6;
|
|
3827
3345
|
var CACHE_TTL_MS = 3e4;
|
|
@@ -3850,7 +3368,7 @@ function endpointRoot(baseUrl) {
|
|
|
3850
3368
|
return baseUrl.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
3851
3369
|
}
|
|
3852
3370
|
function authHeaders(endpoint) {
|
|
3853
|
-
const key = (0,
|
|
3371
|
+
const key = (0, import_gg_ai4.localBearerKey)(endpoint.apiKey);
|
|
3854
3372
|
return {
|
|
3855
3373
|
...key ? { Authorization: `Bearer ${key}` } : {},
|
|
3856
3374
|
Accept: "application/json"
|
|
@@ -4141,7 +3659,7 @@ function findProbedModel(probes, modelId) {
|
|
|
4141
3659
|
}
|
|
4142
3660
|
|
|
4143
3661
|
// src/claude-code/usage.ts
|
|
4144
|
-
var
|
|
3662
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
4145
3663
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
4146
3664
|
var USAGE_FILE = "usage.json";
|
|
4147
3665
|
var WINDOWS = {
|
|
@@ -4177,7 +3695,7 @@ function usagePath(opts) {
|
|
|
4177
3695
|
async function readClaudeCodeUsage(opts = {}) {
|
|
4178
3696
|
let parsed;
|
|
4179
3697
|
try {
|
|
4180
|
-
parsed = JSON.parse(await
|
|
3698
|
+
parsed = JSON.parse(await import_promises7.default.readFile(usagePath(opts), "utf8"));
|
|
4181
3699
|
} catch {
|
|
4182
3700
|
return null;
|
|
4183
3701
|
}
|
|
@@ -4195,8 +3713,8 @@ function recordClaudeCodeRateLimit(info, opts = {}) {
|
|
|
4195
3713
|
const next = mergeClaudeCodeRateLimit(info, await readClaudeCodeUsage({ ...opts, now }), now);
|
|
4196
3714
|
if (next.windows.length === 0) return;
|
|
4197
3715
|
const file = usagePath(opts);
|
|
4198
|
-
await
|
|
4199
|
-
await
|
|
3716
|
+
await import_promises7.default.mkdir(import_node_path5.default.dirname(file), { recursive: true });
|
|
3717
|
+
await import_promises7.default.writeFile(file, JSON.stringify(next), "utf8");
|
|
4200
3718
|
};
|
|
4201
3719
|
writeQueue = writeQueue.then(write).catch(() => {
|
|
4202
3720
|
});
|
|
@@ -4204,7 +3722,7 @@ function recordClaudeCodeRateLimit(info, opts = {}) {
|
|
|
4204
3722
|
}
|
|
4205
3723
|
async function clearClaudeCodeUsage(opts = {}) {
|
|
4206
3724
|
await writeQueue;
|
|
4207
|
-
await
|
|
3725
|
+
await import_promises7.default.rm(usagePath(opts), { force: true }).catch(() => {
|
|
4208
3726
|
});
|
|
4209
3727
|
}
|
|
4210
3728
|
|
|
@@ -4245,6 +3763,9 @@ async function signOutOfClaudeCode(auth, opts = {}) {
|
|
|
4245
3763
|
}
|
|
4246
3764
|
|
|
4247
3765
|
// src/auth-providers.ts
|
|
3766
|
+
function apiKeyDetails(label, billing, when = "") {
|
|
3767
|
+
return { apikey: { method: "apikey", label, billing, when } };
|
|
3768
|
+
}
|
|
4248
3769
|
var ALL_AUTH_PROVIDERS = [
|
|
4249
3770
|
{
|
|
4250
3771
|
value: "anthropic",
|
|
@@ -4272,89 +3793,103 @@ var ALL_AUTH_PROVIDERS = [
|
|
|
4272
3793
|
value: "openai",
|
|
4273
3794
|
label: "OpenAI",
|
|
4274
3795
|
description: "GPT-6 Astra, GPT-6 Astra Fast, GPT-6.1 Sol, GPT-6.1 Sol Fast, GPT-6.1 Terra, GPT-6.1 Terra Fast, GPT-6 Luna, GPT-6 Luna Fast",
|
|
4275
|
-
methods: ["oauth"]
|
|
3796
|
+
methods: ["oauth", "apikey"],
|
|
3797
|
+
apiKeyLabel: "OpenAI",
|
|
3798
|
+
methodDetails: {
|
|
3799
|
+
oauth: {
|
|
3800
|
+
method: "oauth",
|
|
3801
|
+
label: "Continue with ChatGPT",
|
|
3802
|
+
billing: "Uses your ChatGPT plan.",
|
|
3803
|
+
when: "Signs in through OpenAI. Manage what GG Coder may use at chatgpt.com/settings/usage.",
|
|
3804
|
+
requires: "A ChatGPT plan."
|
|
3805
|
+
},
|
|
3806
|
+
apikey: {
|
|
3807
|
+
method: "apikey",
|
|
3808
|
+
label: "OpenAI API key",
|
|
3809
|
+
billing: "Pay-per-token from an OpenAI Platform key.",
|
|
3810
|
+
when: "No subscription needed. Covers you while your ChatGPT plan's usage is out."
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
4276
3813
|
},
|
|
4277
3814
|
{
|
|
4278
3815
|
value: "gemini",
|
|
4279
3816
|
label: "Gemini",
|
|
4280
3817
|
description: "Gemini 3.8 Flash, 3.5 Flash Lite, 3.1 Flash Lite, 3.5 Flash, 3.1 Pro (Preview)",
|
|
4281
|
-
methods: ["
|
|
3818
|
+
methods: ["apikey"],
|
|
4282
3819
|
apiKeyLabel: "Gemini",
|
|
4283
3820
|
apiKeyBaseUrl: GEMINI_API_KEY_BASE_URL,
|
|
4284
|
-
// Stored under its own key
|
|
3821
|
+
// Stored under its own key: `gemini` held the retired Google sign-in.
|
|
4285
3822
|
apiKeyVariants: [
|
|
4286
|
-
{
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
method: "oauth",
|
|
4291
|
-
label: "Sign in with Google",
|
|
4292
|
-
billing: "Gemini Code Assist Standard or Enterprise licence.",
|
|
4293
|
-
when: "Personal Google accounts are no longer served here (Google ended it on June 18, 2026)."
|
|
4294
|
-
},
|
|
4295
|
-
apikey: {
|
|
4296
|
-
method: "apikey",
|
|
4297
|
-
label: "Gemini API key",
|
|
3823
|
+
{
|
|
3824
|
+
key: GEMINI_API_KEY,
|
|
3825
|
+
label: "Google AI Studio",
|
|
3826
|
+
kind: "payg",
|
|
4298
3827
|
billing: "Pay-per-token from a Google AI Studio key.",
|
|
4299
|
-
|
|
3828
|
+
baseUrl: GEMINI_API_KEY_BASE_URL
|
|
4300
3829
|
}
|
|
4301
|
-
|
|
3830
|
+
],
|
|
3831
|
+
methodDetails: apiKeyDetails("Gemini API key", "Pay-per-token from a Google AI Studio key.")
|
|
4302
3832
|
},
|
|
4303
3833
|
{
|
|
4304
3834
|
value: "xai",
|
|
4305
3835
|
label: "xAI (Grok)",
|
|
4306
|
-
description: "Grok 4.7
|
|
4307
|
-
methods: ["
|
|
3836
|
+
description: "Grok 4.7",
|
|
3837
|
+
methods: ["apikey"],
|
|
4308
3838
|
apiKeyLabel: "xAI",
|
|
4309
|
-
methodDetails:
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
when: ""
|
|
4315
|
-
},
|
|
4316
|
-
apikey: {
|
|
4317
|
-
method: "apikey",
|
|
4318
|
-
label: "xAI API key",
|
|
4319
|
-
billing: "Pay-per-token on console.x.ai credits.",
|
|
4320
|
-
when: ""
|
|
4321
|
-
}
|
|
4322
|
-
}
|
|
3839
|
+
methodDetails: apiKeyDetails(
|
|
3840
|
+
"xAI API key",
|
|
3841
|
+
"Pay-per-token from console.x.ai credits.",
|
|
3842
|
+
"SuperGrok and X Premium usage is only offered in apps xAI partners with."
|
|
3843
|
+
)
|
|
4323
3844
|
},
|
|
4324
3845
|
{
|
|
4325
3846
|
value: "moonshot",
|
|
4326
|
-
label: "Moonshot",
|
|
4327
|
-
description: "Kimi K3, K2.8 Preview (Kimi
|
|
4328
|
-
methods: ["
|
|
4329
|
-
apiKeyLabel: "
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
label: "
|
|
4334
|
-
|
|
4335
|
-
|
|
3847
|
+
label: "Kimi (Moonshot)",
|
|
3848
|
+
description: "Kimi K3, K2.8 Preview (Kimi Code plan), K2.7 Code, K2.7 Code HighSpeed",
|
|
3849
|
+
methods: ["apikey"],
|
|
3850
|
+
apiKeyLabel: "Kimi",
|
|
3851
|
+
apiKeyVariants: [
|
|
3852
|
+
{
|
|
3853
|
+
key: KIMI_CODE_KEY,
|
|
3854
|
+
label: "Kimi Code plan",
|
|
3855
|
+
kind: "plan",
|
|
3856
|
+
billing: "Uses your Kimi Code plan. Create the key in the Kimi Code Console."
|
|
4336
3857
|
},
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
label: "Moonshot API
|
|
4340
|
-
|
|
4341
|
-
|
|
3858
|
+
{
|
|
3859
|
+
key: "moonshot",
|
|
3860
|
+
label: "Moonshot API",
|
|
3861
|
+
kind: "payg",
|
|
3862
|
+
billing: "Pay-per-token from platform.moonshot.ai."
|
|
4342
3863
|
}
|
|
4343
|
-
|
|
3864
|
+
],
|
|
3865
|
+
methodDetails: apiKeyDetails(
|
|
3866
|
+
"Kimi API key",
|
|
3867
|
+
"A Kimi Code plan key uses your plan; a Moonshot key is pay-per-token."
|
|
3868
|
+
)
|
|
4344
3869
|
},
|
|
4345
3870
|
{
|
|
4346
3871
|
value: "glm",
|
|
4347
3872
|
label: "Z.AI (GLM)",
|
|
4348
3873
|
description: "GLM-5.3, GLM-5.3-Flash",
|
|
4349
3874
|
methods: ["apikey"],
|
|
4350
|
-
apiKeyLabel: "Z.AI"
|
|
3875
|
+
apiKeyLabel: "Z.AI",
|
|
3876
|
+
methodDetails: apiKeyDetails(
|
|
3877
|
+
"Z.AI API key",
|
|
3878
|
+
"Pay-per-token from your Z.AI account balance.",
|
|
3879
|
+
"GLM Coding Plan quota can't be used here: Z.AI limits the plan to the tools it lists."
|
|
3880
|
+
)
|
|
4351
3881
|
},
|
|
4352
3882
|
{
|
|
4353
3883
|
value: "minimax",
|
|
4354
3884
|
label: "MiniMax",
|
|
4355
3885
|
description: "MiniMax M3.1 Flash, M3",
|
|
4356
3886
|
methods: ["apikey"],
|
|
4357
|
-
apiKeyLabel: "MiniMax"
|
|
3887
|
+
apiKeyLabel: "MiniMax",
|
|
3888
|
+
methodDetails: apiKeyDetails(
|
|
3889
|
+
"MiniMax API key",
|
|
3890
|
+
"An M Plan key uses your plan; a pay-as-you-go key is pay-per-token.",
|
|
3891
|
+
"The key you paste decides which one is billed."
|
|
3892
|
+
)
|
|
4358
3893
|
},
|
|
4359
3894
|
{
|
|
4360
3895
|
value: "xiaomi",
|
|
@@ -4367,44 +3902,66 @@ var ALL_AUTH_PROVIDERS = [
|
|
|
4367
3902
|
{
|
|
4368
3903
|
key: "xiaomi",
|
|
4369
3904
|
label: "Token Plan",
|
|
3905
|
+
kind: "plan",
|
|
3906
|
+
billing: "Uses your MiMo Token Plan.",
|
|
4370
3907
|
baseUrl: "https://token-plan-sgp.xiaomimimo.com/v1"
|
|
4371
3908
|
},
|
|
4372
3909
|
{
|
|
4373
3910
|
key: XIAOMI_CREDITS_KEY,
|
|
4374
|
-
label: "API Credits
|
|
3911
|
+
label: "API Credits",
|
|
3912
|
+
kind: "payg",
|
|
3913
|
+
billing: "Pay-per-token from MiMo API Credits. Required for UltraSpeed.",
|
|
4375
3914
|
baseUrl: "https://api.xiaomimimo.com/v1"
|
|
4376
3915
|
}
|
|
4377
|
-
]
|
|
3916
|
+
],
|
|
3917
|
+
methodDetails: apiKeyDetails(
|
|
3918
|
+
"Xiaomi MiMo API key",
|
|
3919
|
+
"A Token Plan key uses your plan; an API Credits key is pay-per-token."
|
|
3920
|
+
)
|
|
4378
3921
|
},
|
|
4379
3922
|
{
|
|
4380
3923
|
value: "deepseek",
|
|
4381
3924
|
label: "DeepSeek",
|
|
4382
3925
|
description: "DeepSeek V4 Pro, V4.1 Flash",
|
|
4383
3926
|
methods: ["apikey"],
|
|
4384
|
-
apiKeyLabel: "DeepSeek"
|
|
3927
|
+
apiKeyLabel: "DeepSeek",
|
|
3928
|
+
methodDetails: apiKeyDetails("DeepSeek API key", "Pay-per-token from platform.deepseek.com.")
|
|
4385
3929
|
},
|
|
4386
3930
|
{
|
|
4387
3931
|
value: "sakana",
|
|
4388
3932
|
label: "Sakana (Fugu)",
|
|
4389
3933
|
description: "Fugu, Fugu Max, Fugu Ultra",
|
|
4390
3934
|
methods: ["apikey"],
|
|
4391
|
-
apiKeyLabel: "Sakana"
|
|
3935
|
+
apiKeyLabel: "Sakana",
|
|
3936
|
+
methodDetails: apiKeyDetails("Sakana API key", "Pay-per-token from your Sakana API account.")
|
|
4392
3937
|
},
|
|
4393
3938
|
{
|
|
4394
3939
|
value: "openrouter",
|
|
4395
3940
|
label: "OpenRouter",
|
|
4396
3941
|
description: "Qwen3.8 Max \xB7 multi-provider gateway",
|
|
4397
3942
|
methods: ["apikey"],
|
|
4398
|
-
apiKeyLabel: "OpenRouter"
|
|
3943
|
+
apiKeyLabel: "OpenRouter",
|
|
3944
|
+
methodDetails: apiKeyDetails("OpenRouter API key", "Pay-per-token from OpenRouter credits.")
|
|
4399
3945
|
},
|
|
4400
3946
|
{
|
|
4401
3947
|
value: "huggingface",
|
|
4402
3948
|
label: "Hugging Face",
|
|
4403
3949
|
description: "Kimi K2.7 Code, DeepSeek V4.1 Flash, GPT-OSS 120B",
|
|
4404
3950
|
methods: ["apikey"],
|
|
4405
|
-
apiKeyLabel: "Hugging Face"
|
|
3951
|
+
apiKeyLabel: "Hugging Face",
|
|
3952
|
+
methodDetails: apiKeyDetails(
|
|
3953
|
+
"Hugging Face token",
|
|
3954
|
+
"Pay-per-token from Hugging Face Inference Providers credits."
|
|
3955
|
+
)
|
|
4406
3956
|
}
|
|
4407
3957
|
];
|
|
3958
|
+
function requiredKeyLabel(provider, storageKeys) {
|
|
3959
|
+
const variants = ALL_AUTH_PROVIDERS.find((p) => p.value === provider)?.apiKeyVariants ?? [];
|
|
3960
|
+
const needed = variants.filter((v) => storageKeys.includes(v.key));
|
|
3961
|
+
if (needed.length === 0 || needed.length === variants.length) return void 0;
|
|
3962
|
+
const names = needed.map((v) => v.label).join(" or ");
|
|
3963
|
+
return `Needs ${/^[aeiou]/i.test(names) ? "an" : "a"} ${names} key`;
|
|
3964
|
+
}
|
|
4408
3965
|
var AUTH_PROVIDERS = ALL_AUTH_PROVIDERS.filter(
|
|
4409
3966
|
(p) => !isHiddenProvider(p.value)
|
|
4410
3967
|
);
|
|
@@ -4431,18 +3988,24 @@ function describeAuthMethods(provider) {
|
|
|
4431
3988
|
});
|
|
4432
3989
|
}
|
|
4433
3990
|
function authPriorityNote(provider) {
|
|
4434
|
-
if (provider === "gemini") {
|
|
4435
|
-
return "Uses the Gemini API key first when one is set; otherwise the Google sign-in.";
|
|
4436
|
-
}
|
|
4437
3991
|
if (provider === "anthropic") {
|
|
4438
3992
|
return "Uses Sign in with Claude while you are signed in; otherwise the Anthropic API key.";
|
|
4439
3993
|
}
|
|
4440
3994
|
const dual = dualAuthProvider(provider);
|
|
4441
|
-
if (
|
|
4442
|
-
|
|
3995
|
+
if (dual) {
|
|
3996
|
+
return `Uses your ${dual.oauthLabel} first; the ${dual.apiKeyLabel} takes over while its usage is out, then switches back.`;
|
|
3997
|
+
}
|
|
3998
|
+
const variants = getAuthProvider(provider)?.apiKeyVariants;
|
|
3999
|
+
const plan = variants?.find((v) => v.kind === "plan");
|
|
4000
|
+
const payg = variants?.find((v) => v.kind === "payg");
|
|
4001
|
+
if (plan && payg) {
|
|
4002
|
+
return `Uses the ${plan.label} key first when both are set; the ${payg.label} key covers models the plan doesn't include.`;
|
|
4003
|
+
}
|
|
4004
|
+
return void 0;
|
|
4443
4005
|
}
|
|
4444
4006
|
|
|
4445
4007
|
// src/provider-usage.ts
|
|
4008
|
+
var import_gg_ai5 = require("@kenkaiiii/gg-ai");
|
|
4446
4009
|
var SubscriptionUsageError = class extends Error {
|
|
4447
4010
|
constructor(message, status, retryAfterMs2) {
|
|
4448
4011
|
super(message);
|
|
@@ -4451,51 +4014,14 @@ var SubscriptionUsageError = class extends Error {
|
|
|
4451
4014
|
this.name = "SubscriptionUsageError";
|
|
4452
4015
|
}
|
|
4453
4016
|
};
|
|
4454
|
-
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
4455
4017
|
function finiteNumber(value) {
|
|
4456
4018
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4457
4019
|
}
|
|
4458
|
-
function clampPercent(value) {
|
|
4459
|
-
const number = finiteNumber(value);
|
|
4460
|
-
return number === void 0 ? void 0 : Math.min(100, Math.max(0, number));
|
|
4461
|
-
}
|
|
4462
4020
|
function isoTimestamp(value) {
|
|
4463
4021
|
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
4464
4022
|
const timestamp = Date.parse(value);
|
|
4465
4023
|
return Number.isFinite(timestamp) ? timestamp : void 0;
|
|
4466
4024
|
}
|
|
4467
|
-
function unixTimestamp(value) {
|
|
4468
|
-
const seconds = finiteNumber(value);
|
|
4469
|
-
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
4470
|
-
}
|
|
4471
|
-
function codexResetAt(window, now) {
|
|
4472
|
-
const absolute = unixTimestamp(window.reset_at);
|
|
4473
|
-
if (absolute !== void 0) return absolute;
|
|
4474
|
-
const afterSeconds = finiteNumber(window.reset_after_seconds);
|
|
4475
|
-
return afterSeconds === void 0 ? void 0 : now + afterSeconds * 1e3;
|
|
4476
|
-
}
|
|
4477
|
-
function currentWindowLabel(seconds, fallbackHours) {
|
|
4478
|
-
const duration = finiteNumber(seconds);
|
|
4479
|
-
const hours = Math.max(1, Math.round((duration ?? fallbackHours * 3600) / 3600));
|
|
4480
|
-
return `${hours}-hour`;
|
|
4481
|
-
}
|
|
4482
|
-
function codexWindowKind(window, fallback) {
|
|
4483
|
-
const duration = finiteNumber(window.limit_window_seconds);
|
|
4484
|
-
if (duration === void 0) return fallback;
|
|
4485
|
-
return duration >= 6 * 24 * 60 * 60 ? "weekly" : "current";
|
|
4486
|
-
}
|
|
4487
|
-
function normalizedCodexWindow(window, fallbackKind, now) {
|
|
4488
|
-
if (!window) return null;
|
|
4489
|
-
const usedPercent = clampPercent(window.used_percent);
|
|
4490
|
-
if (usedPercent === void 0) return null;
|
|
4491
|
-
const kind = codexWindowKind(window, fallbackKind);
|
|
4492
|
-
return {
|
|
4493
|
-
kind,
|
|
4494
|
-
label: kind === "weekly" ? "Weekly" : currentWindowLabel(window.limit_window_seconds, 5),
|
|
4495
|
-
usedPercent,
|
|
4496
|
-
resetsAt: codexResetAt(window, now)
|
|
4497
|
-
};
|
|
4498
|
-
}
|
|
4499
4025
|
function retryAfterMs(response, now) {
|
|
4500
4026
|
const value = response.headers.get("retry-after")?.trim();
|
|
4501
4027
|
if (!value) return void 0;
|
|
@@ -4518,31 +4044,6 @@ async function readUsageResponse(response, now) {
|
|
|
4518
4044
|
throw new SubscriptionUsageError("Subscription usage response was not valid JSON");
|
|
4519
4045
|
}
|
|
4520
4046
|
}
|
|
4521
|
-
async function fetchCodexUsage(credentials, fetchFn, signal, now) {
|
|
4522
|
-
const headers = {
|
|
4523
|
-
Authorization: `Bearer ${credentials.accessToken}`,
|
|
4524
|
-
Accept: "application/json",
|
|
4525
|
-
originator: "ggcoder",
|
|
4526
|
-
"User-Agent": "ggcoder"
|
|
4527
|
-
};
|
|
4528
|
-
if (credentials.accountId) headers["ChatGPT-Account-Id"] = credentials.accountId;
|
|
4529
|
-
const response = await fetchFn(CODEX_USAGE_URL, { method: "GET", signal, headers });
|
|
4530
|
-
const data = await readUsageResponse(response, now());
|
|
4531
|
-
const windows = [
|
|
4532
|
-
normalizedCodexWindow(data.rate_limit?.primary_window, "current", now()),
|
|
4533
|
-
normalizedCodexWindow(data.rate_limit?.secondary_window, "weekly", now())
|
|
4534
|
-
].filter((window) => window !== null);
|
|
4535
|
-
windows.sort((left, right) => {
|
|
4536
|
-
if (left.kind === right.kind) return 0;
|
|
4537
|
-
return left.kind === "current" ? -1 : 1;
|
|
4538
|
-
});
|
|
4539
|
-
return {
|
|
4540
|
-
provider: "openai",
|
|
4541
|
-
displayName: "Codex",
|
|
4542
|
-
windows,
|
|
4543
|
-
fetchedAt: now()
|
|
4544
|
-
};
|
|
4545
|
-
}
|
|
4546
4047
|
function kimiNumber(value) {
|
|
4547
4048
|
if (typeof value === "string" && value.trim()) {
|
|
4548
4049
|
const parsed = Number(value);
|
|
@@ -4573,15 +4074,14 @@ function kimiWindowMinutes(window) {
|
|
|
4573
4074
|
}
|
|
4574
4075
|
}
|
|
4575
4076
|
async function fetchKimiUsage(credentials, fetchFn, signal, now) {
|
|
4576
|
-
const
|
|
4077
|
+
const baseUrl = (credentials.baseUrl ?? KIMI_CODE_DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
4078
|
+
const response = await fetchFn(`${baseUrl}/usages`, {
|
|
4577
4079
|
method: "GET",
|
|
4578
4080
|
signal,
|
|
4579
4081
|
headers: {
|
|
4580
4082
|
Authorization: "Bearer " + credentials.accessToken,
|
|
4581
4083
|
Accept: "application/json",
|
|
4582
|
-
|
|
4583
|
-
// same as model requests.
|
|
4584
|
-
...kimiCodingHeaders()
|
|
4084
|
+
"User-Agent": import_gg_ai5.GG_CODER_USER_AGENT
|
|
4585
4085
|
}
|
|
4586
4086
|
});
|
|
4587
4087
|
const data = await readUsageResponse(response, now());
|
|
@@ -4633,7 +4133,7 @@ async function fetchSubscriptionUsage(provider, credentials, options = {}) {
|
|
|
4633
4133
|
const now = options.now ?? Date.now;
|
|
4634
4134
|
const signal = AbortSignal.timeout(options.timeoutMs ?? 8e3);
|
|
4635
4135
|
try {
|
|
4636
|
-
return
|
|
4136
|
+
return await fetchKimiUsage(credentials, fetchFn, signal, now);
|
|
4637
4137
|
} catch (error) {
|
|
4638
4138
|
if (error instanceof SubscriptionUsageError) throw error;
|
|
4639
4139
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4698,7 +4198,7 @@ var TelegramBot = class {
|
|
|
4698
4198
|
} catch (err) {
|
|
4699
4199
|
if (!this.running) break;
|
|
4700
4200
|
console.error(`[telegram] Poll error: ${err instanceof Error ? err.message : err}`);
|
|
4701
|
-
await
|
|
4201
|
+
await sleep(3e3);
|
|
4702
4202
|
}
|
|
4703
4203
|
}
|
|
4704
4204
|
}
|
|
@@ -4884,7 +4384,7 @@ function splitMessage(text) {
|
|
|
4884
4384
|
}
|
|
4885
4385
|
return chunks;
|
|
4886
4386
|
}
|
|
4887
|
-
function
|
|
4387
|
+
function sleep(ms) {
|
|
4888
4388
|
return new Promise((r) => setTimeout(r, ms));
|
|
4889
4389
|
}
|
|
4890
4390
|
|
|
@@ -4969,8 +4469,8 @@ async function transcribeVoice(fileUrl) {
|
|
|
4969
4469
|
}
|
|
4970
4470
|
|
|
4971
4471
|
// src/auto-update.ts
|
|
4972
|
-
var
|
|
4973
|
-
var
|
|
4472
|
+
var import_node_child_process2 = require("child_process");
|
|
4473
|
+
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
4974
4474
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
4975
4475
|
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
4976
4476
|
var FETCH_TIMEOUT_MS = 1e4;
|
|
@@ -4986,7 +4486,7 @@ function compareVersions(a, b) {
|
|
|
4986
4486
|
function performUpdateInBackground(command) {
|
|
4987
4487
|
try {
|
|
4988
4488
|
const parts = command.split(" ");
|
|
4989
|
-
const child = (0,
|
|
4489
|
+
const child = (0, import_node_child_process2.spawn)(parts[0], parts.slice(1), {
|
|
4990
4490
|
detached: true,
|
|
4991
4491
|
stdio: "ignore",
|
|
4992
4492
|
env: { ...process.env, npm_config_loglevel: "silent" }
|
|
@@ -5004,7 +4504,7 @@ function createAutoUpdater(config) {
|
|
|
5004
4504
|
}
|
|
5005
4505
|
function readState() {
|
|
5006
4506
|
try {
|
|
5007
|
-
const raw =
|
|
4507
|
+
const raw = import_node_fs5.default.readFileSync(stateFilePath(), "utf-8");
|
|
5008
4508
|
return JSON.parse(raw);
|
|
5009
4509
|
} catch {
|
|
5010
4510
|
return null;
|
|
@@ -5013,8 +4513,8 @@ function createAutoUpdater(config) {
|
|
|
5013
4513
|
function writeState(state) {
|
|
5014
4514
|
try {
|
|
5015
4515
|
const filePath = stateFilePath();
|
|
5016
|
-
|
|
5017
|
-
|
|
4516
|
+
import_node_fs5.default.mkdirSync(import_node_path6.default.dirname(filePath), { recursive: true, mode: 448 });
|
|
4517
|
+
import_node_fs5.default.writeFileSync(filePath, JSON.stringify(state));
|
|
5018
4518
|
} catch {
|
|
5019
4519
|
}
|
|
5020
4520
|
}
|
|
@@ -5140,29 +4640,38 @@ function createAutoUpdater(config) {
|
|
|
5140
4640
|
0 && (module.exports = {
|
|
5141
4641
|
AUTH_PROVIDERS,
|
|
5142
4642
|
AuthStorage,
|
|
4643
|
+
CHATGPT_PLAN_BASE_URL,
|
|
4644
|
+
CHATGPT_PLAN_NOT_ENABLED_MESSAGE,
|
|
4645
|
+
CHATGPT_PLAN_SCOPE,
|
|
4646
|
+
CHATGPT_USAGE_URL,
|
|
5143
4647
|
CLAUDE_CODE_AUTH_KEY,
|
|
5144
4648
|
CLAUDE_CODE_AUTH_MARKER,
|
|
5145
4649
|
CLAUDE_CODE_MANIFEST,
|
|
4650
|
+
ChatGPTPlanNotEnabledError,
|
|
5146
4651
|
DEFAULT_LOCAL_ENDPOINTS,
|
|
5147
4652
|
DEFAULT_MAX_VIDEO_BYTES,
|
|
5148
4653
|
FALLBACK_CONTEXT_WINDOW,
|
|
5149
4654
|
GEMINI_API_KEY,
|
|
5150
4655
|
GEMINI_API_KEY_BASE_URL,
|
|
5151
4656
|
HIDDEN_PROVIDERS,
|
|
4657
|
+
KIMI_CODE_DEFAULT_BASE_URL,
|
|
4658
|
+
KIMI_CODE_KEY,
|
|
4659
|
+
KIMI_CODE_OVERSEAS_BASE_URL,
|
|
5152
4660
|
LOCAL_API_KEY_PLACEHOLDER,
|
|
5153
4661
|
LOCAL_AUTH_KEY_PREFIX,
|
|
5154
4662
|
MINIMAX_CN_BASE_URL,
|
|
5155
4663
|
MINIMAX_GLOBAL_BASE_URL,
|
|
5156
4664
|
MODELS,
|
|
5157
|
-
MOONSHOT_OAUTH_KEY,
|
|
5158
4665
|
NotLoggedInError,
|
|
4666
|
+
OPENAI_CHATGPT_KEY,
|
|
5159
4667
|
OPENROUTER_DEFAULT_BASE_URL,
|
|
5160
4668
|
PLAN_MODE_THINKING_CAP,
|
|
4669
|
+
RETIRED_SIGN_INS,
|
|
5161
4670
|
SubscriptionUsageError,
|
|
5162
4671
|
TelegramBot,
|
|
5163
|
-
XAI_OAUTH_KEY,
|
|
5164
4672
|
XIAOMI_CREDITS_KEY,
|
|
5165
4673
|
XIAOMI_TOKEN_PLAN_BASE_URLS,
|
|
4674
|
+
apiKeyStorageKeys,
|
|
5166
4675
|
authPriorityNote,
|
|
5167
4676
|
clampThinkingForPlanMode,
|
|
5168
4677
|
claudeCodeAuthStatus,
|
|
@@ -5179,6 +4688,7 @@ function createAutoUpdater(config) {
|
|
|
5179
4688
|
createAutoUpdater,
|
|
5180
4689
|
decodeOggOpus,
|
|
5181
4690
|
describeAuthMethods,
|
|
4691
|
+
detectKimiCodeBaseUrl,
|
|
5182
4692
|
detectMiniMaxBaseUrl,
|
|
5183
4693
|
detectXiaomiTokenPlanBaseUrl,
|
|
5184
4694
|
discoverLocalModels,
|
|
@@ -5187,6 +4697,7 @@ function createAutoUpdater(config) {
|
|
|
5187
4697
|
dualAuthProviderByOAuthKey,
|
|
5188
4698
|
endpointRoot,
|
|
5189
4699
|
ensureClaudeCodeBinary,
|
|
4700
|
+
fetchChatGPTPlanModels,
|
|
5190
4701
|
fetchSubscriptionUsage,
|
|
5191
4702
|
findProbedModel,
|
|
5192
4703
|
formatClaudeCodeInstallProgress,
|
|
@@ -5197,6 +4708,7 @@ function createAutoUpdater(config) {
|
|
|
5197
4708
|
getAuthProvider,
|
|
5198
4709
|
getAuthStorageKey,
|
|
5199
4710
|
getAuthStorageKeys,
|
|
4711
|
+
getChatGPTHostId,
|
|
5200
4712
|
getContextWindow,
|
|
5201
4713
|
getDefaultModel,
|
|
5202
4714
|
getDefaultThinkingLevel,
|
|
@@ -5212,10 +4724,7 @@ function createAutoUpdater(config) {
|
|
|
5212
4724
|
getSupportedThinkingLevels,
|
|
5213
4725
|
getToolResultCharLimit,
|
|
5214
4726
|
getVideoByteLimit,
|
|
5215
|
-
grokCliBaseUrl,
|
|
5216
|
-
grokCliHeaders,
|
|
5217
4727
|
isClaudeCodeAuthMarker,
|
|
5218
|
-
isGrokCliEndpoint,
|
|
5219
4728
|
isHiddenProvider,
|
|
5220
4729
|
isKimiCodingEndpoint,
|
|
5221
4730
|
isLegacyClaudeOAuth,
|
|
@@ -5224,21 +4733,17 @@ function createAutoUpdater(config) {
|
|
|
5224
4733
|
isModelLoaded,
|
|
5225
4734
|
isPaidModel,
|
|
5226
4735
|
isThinkingLevelSupported,
|
|
5227
|
-
kimiCodeBaseUrl,
|
|
5228
|
-
kimiCodingHeaders,
|
|
5229
4736
|
localAuthStorageKey,
|
|
5230
4737
|
localModelShortName,
|
|
5231
4738
|
log,
|
|
4739
|
+
loginChatGPT,
|
|
5232
4740
|
loginClaudeCode,
|
|
5233
|
-
loginGemini,
|
|
5234
|
-
loginKimi,
|
|
5235
|
-
loginOpenAI,
|
|
5236
|
-
loginXai,
|
|
5237
4741
|
logoutClaudeCode,
|
|
5238
4742
|
mergeClaudeCodeRateLimit,
|
|
5239
4743
|
oauthStorageKey,
|
|
5240
4744
|
ollamaServedContext,
|
|
5241
4745
|
openLog,
|
|
4746
|
+
parseCallbackInput,
|
|
5242
4747
|
parseLocalModelId,
|
|
5243
4748
|
parseOpenRouterCatalog,
|
|
5244
4749
|
probeEndpoint,
|
|
@@ -5246,21 +4751,22 @@ function createAutoUpdater(config) {
|
|
|
5246
4751
|
readClaudeCodeUsage,
|
|
5247
4752
|
readStoredBaseUrlSync,
|
|
5248
4753
|
recordClaudeCodeRateLimit,
|
|
5249
|
-
|
|
5250
|
-
refreshKimiToken,
|
|
5251
|
-
refreshOpenAIToken,
|
|
5252
|
-
refreshXaiToken,
|
|
4754
|
+
refreshChatGPTToken,
|
|
5253
4755
|
registerLogCleanup,
|
|
5254
4756
|
registerRuntimeModels,
|
|
4757
|
+
requiredKeyLabel,
|
|
5255
4758
|
resample,
|
|
5256
4759
|
resolveClaudeCodeBinary,
|
|
4760
|
+
revokeChatGPTSignIn,
|
|
5257
4761
|
setProgressCallback,
|
|
5258
4762
|
setProviderCatalog,
|
|
5259
4763
|
signInWithClaudeCode,
|
|
5260
4764
|
signOutOfClaudeCode,
|
|
4765
|
+
storageKeyProvider,
|
|
5261
4766
|
toModelInfo,
|
|
5262
4767
|
transcribeVoice,
|
|
5263
4768
|
usesOpenAICodexTransport,
|
|
4769
|
+
verifyIdToken,
|
|
5264
4770
|
withFileLock,
|
|
5265
4771
|
withoutPaidTier
|
|
5266
4772
|
});
|