@kenkaiiii/gg-core 5.93.0 → 5.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/dist/{chunk-2X5L6U4Z.js → chunk-GXOFTHM5.js} +4 -2
- package/dist/chunk-GXOFTHM5.js.map +1 -0
- package/dist/{chunk-SDUIKPGO.js → chunk-J7W5HS6X.js} +1191 -1189
- package/dist/chunk-J7W5HS6X.js.map +1 -0
- package/dist/index.cjs +1472 -1368
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +338 -187
- package/dist/index.d.ts +338 -187
- package/dist/index.js +319 -213
- package/dist/index.js.map +1 -1
- package/dist/model-registry.cjs +96 -77
- package/dist/model-registry.cjs.map +1 -1
- package/dist/model-registry.d.cts +11 -15
- package/dist/model-registry.d.ts +11 -15
- package/dist/model-registry.js +2 -2
- package/dist/paths.cjs +3 -1
- package/dist/paths.cjs.map +1 -1
- package/dist/paths.d.cts +4 -0
- package/dist/paths.d.ts +4 -0
- package/dist/paths.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-2X5L6U4Z.js.map +0 -1
- package/dist/chunk-SDUIKPGO.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -32,34 +32,55 @@ 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,
|
|
39
|
+
CLAUDE_CODE_AUTH_KEY: () => CLAUDE_CODE_AUTH_KEY,
|
|
40
|
+
CLAUDE_CODE_AUTH_MARKER: () => CLAUDE_CODE_AUTH_MARKER,
|
|
41
|
+
CLAUDE_CODE_MANIFEST: () => CLAUDE_CODE_MANIFEST,
|
|
42
|
+
ChatGPTPlanNotEnabledError: () => ChatGPTPlanNotEnabledError,
|
|
35
43
|
DEFAULT_LOCAL_ENDPOINTS: () => DEFAULT_LOCAL_ENDPOINTS,
|
|
36
44
|
DEFAULT_MAX_VIDEO_BYTES: () => DEFAULT_MAX_VIDEO_BYTES,
|
|
37
45
|
FALLBACK_CONTEXT_WINDOW: () => FALLBACK_CONTEXT_WINDOW,
|
|
38
46
|
GEMINI_API_KEY: () => GEMINI_API_KEY,
|
|
39
47
|
GEMINI_API_KEY_BASE_URL: () => GEMINI_API_KEY_BASE_URL,
|
|
40
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,
|
|
41
52
|
LOCAL_API_KEY_PLACEHOLDER: () => LOCAL_API_KEY_PLACEHOLDER,
|
|
42
53
|
LOCAL_AUTH_KEY_PREFIX: () => LOCAL_AUTH_KEY_PREFIX,
|
|
43
54
|
MINIMAX_CN_BASE_URL: () => MINIMAX_CN_BASE_URL,
|
|
44
55
|
MINIMAX_GLOBAL_BASE_URL: () => MINIMAX_GLOBAL_BASE_URL,
|
|
45
56
|
MODELS: () => MODELS,
|
|
46
|
-
MOONSHOT_OAUTH_KEY: () => MOONSHOT_OAUTH_KEY,
|
|
47
57
|
NotLoggedInError: () => NotLoggedInError,
|
|
58
|
+
OPENAI_CHATGPT_KEY: () => OPENAI_CHATGPT_KEY,
|
|
48
59
|
OPENROUTER_DEFAULT_BASE_URL: () => OPENROUTER_DEFAULT_BASE_URL,
|
|
49
60
|
PLAN_MODE_THINKING_CAP: () => PLAN_MODE_THINKING_CAP,
|
|
61
|
+
RETIRED_SIGN_INS: () => RETIRED_SIGN_INS,
|
|
50
62
|
SubscriptionUsageError: () => SubscriptionUsageError,
|
|
51
63
|
TelegramBot: () => TelegramBot,
|
|
52
|
-
XAI_OAUTH_KEY: () => XAI_OAUTH_KEY,
|
|
53
64
|
XIAOMI_CREDITS_KEY: () => XIAOMI_CREDITS_KEY,
|
|
54
65
|
XIAOMI_TOKEN_PLAN_BASE_URLS: () => XIAOMI_TOKEN_PLAN_BASE_URLS,
|
|
66
|
+
apiKeyStorageKeys: () => apiKeyStorageKeys,
|
|
55
67
|
authPriorityNote: () => authPriorityNote,
|
|
56
68
|
clampThinkingForPlanMode: () => clampThinkingForPlanMode,
|
|
69
|
+
claudeCodeAuthStatus: () => claudeCodeAuthStatus,
|
|
70
|
+
claudeCodeBinaryPath: () => claudeCodeBinaryPath,
|
|
71
|
+
claudeCodeCallbackUrl: () => claudeCodeCallbackUrl,
|
|
72
|
+
claudeCodeConfigDir: () => claudeCodeConfigDir,
|
|
73
|
+
claudeCodeEnv: () => claudeCodeEnv,
|
|
74
|
+
claudeCodeHome: () => claudeCodeHome,
|
|
75
|
+
claudeCodePlatformKey: () => claudeCodePlatformKey,
|
|
76
|
+
clearClaudeCodeUsage: () => clearClaudeCodeUsage,
|
|
57
77
|
clearLocalDiscoveryCache: () => clearLocalDiscoveryCache,
|
|
58
78
|
clearRuntimeModels: () => clearRuntimeModels,
|
|
59
79
|
closeLogger: () => closeLogger,
|
|
60
80
|
createAutoUpdater: () => createAutoUpdater,
|
|
61
81
|
decodeOggOpus: () => decodeOggOpus,
|
|
62
82
|
describeAuthMethods: () => describeAuthMethods,
|
|
83
|
+
detectKimiCodeBaseUrl: () => detectKimiCodeBaseUrl,
|
|
63
84
|
detectMiniMaxBaseUrl: () => detectMiniMaxBaseUrl,
|
|
64
85
|
detectXiaomiTokenPlanBaseUrl: () => detectXiaomiTokenPlanBaseUrl,
|
|
65
86
|
discoverLocalModels: () => discoverLocalModels,
|
|
@@ -67,8 +88,11 @@ __export(index_exports, {
|
|
|
67
88
|
dualAuthProvider: () => dualAuthProvider,
|
|
68
89
|
dualAuthProviderByOAuthKey: () => dualAuthProviderByOAuthKey,
|
|
69
90
|
endpointRoot: () => endpointRoot,
|
|
91
|
+
ensureClaudeCodeBinary: () => ensureClaudeCodeBinary,
|
|
92
|
+
fetchChatGPTPlanModels: () => fetchChatGPTPlanModels,
|
|
70
93
|
fetchSubscriptionUsage: () => fetchSubscriptionUsage,
|
|
71
94
|
findProbedModel: () => findProbedModel,
|
|
95
|
+
formatClaudeCodeInstallProgress: () => formatClaudeCodeInstallProgress,
|
|
72
96
|
formatLocalModelId: () => formatLocalModelId,
|
|
73
97
|
generatePKCE: () => generatePKCE,
|
|
74
98
|
getAllModels: () => getAllModels,
|
|
@@ -76,8 +100,7 @@ __export(index_exports, {
|
|
|
76
100
|
getAuthProvider: () => getAuthProvider,
|
|
77
101
|
getAuthStorageKey: () => getAuthStorageKey,
|
|
78
102
|
getAuthStorageKeys: () => getAuthStorageKeys,
|
|
79
|
-
|
|
80
|
-
getClaudeCodeVersion: () => import_gg_ai.getClaudeCodeVersion,
|
|
103
|
+
getChatGPTHostId: () => getChatGPTHostId,
|
|
81
104
|
getContextWindow: () => getContextWindow,
|
|
82
105
|
getDefaultModel: () => getDefaultModel,
|
|
83
106
|
getDefaultThinkingLevel: () => getDefaultThinkingLevel,
|
|
@@ -93,59 +116,90 @@ __export(index_exports, {
|
|
|
93
116
|
getSupportedThinkingLevels: () => getSupportedThinkingLevels,
|
|
94
117
|
getToolResultCharLimit: () => getToolResultCharLimit,
|
|
95
118
|
getVideoByteLimit: () => getVideoByteLimit,
|
|
96
|
-
|
|
97
|
-
grokCliHeaders: () => grokCliHeaders,
|
|
98
|
-
isGrokCliEndpoint: () => isGrokCliEndpoint,
|
|
119
|
+
isClaudeCodeAuthMarker: () => isClaudeCodeAuthMarker,
|
|
99
120
|
isHiddenProvider: () => isHiddenProvider,
|
|
100
121
|
isKimiCodingEndpoint: () => isKimiCodingEndpoint,
|
|
122
|
+
isLegacyClaudeOAuth: () => isLegacyClaudeOAuth,
|
|
101
123
|
isLocalModelId: () => isLocalModelId,
|
|
102
124
|
isLoggerOpen: () => isLoggerOpen,
|
|
103
125
|
isModelLoaded: () => isModelLoaded,
|
|
104
126
|
isPaidModel: () => isPaidModel,
|
|
105
127
|
isThinkingLevelSupported: () => isThinkingLevelSupported,
|
|
106
|
-
kimiCodeBaseUrl: () => kimiCodeBaseUrl,
|
|
107
|
-
kimiCodingHeaders: () => kimiCodingHeaders,
|
|
108
128
|
localAuthStorageKey: () => localAuthStorageKey,
|
|
109
129
|
localModelShortName: () => localModelShortName,
|
|
110
130
|
log: () => log,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
loginXai: () => loginXai,
|
|
131
|
+
loginChatGPT: () => loginChatGPT,
|
|
132
|
+
loginClaudeCode: () => loginClaudeCode,
|
|
133
|
+
logoutClaudeCode: () => logoutClaudeCode,
|
|
134
|
+
mergeClaudeCodeRateLimit: () => mergeClaudeCodeRateLimit,
|
|
116
135
|
oauthStorageKey: () => oauthStorageKey,
|
|
117
136
|
ollamaServedContext: () => ollamaServedContext,
|
|
118
137
|
openLog: () => openLog,
|
|
138
|
+
parseCallbackInput: () => parseCallbackInput,
|
|
119
139
|
parseLocalModelId: () => parseLocalModelId,
|
|
120
140
|
parseOpenRouterCatalog: () => parseOpenRouterCatalog,
|
|
121
|
-
pendingClaudeCodeVersionRefresh: () => import_gg_ai.pendingClaudeCodeVersionRefresh,
|
|
122
141
|
probeEndpoint: () => probeEndpoint,
|
|
123
142
|
providerStorageKeys: () => providerStorageKeys,
|
|
143
|
+
readClaudeCodeUsage: () => readClaudeCodeUsage,
|
|
124
144
|
readStoredBaseUrlSync: () => readStoredBaseUrlSync,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
refreshKimiToken: () => refreshKimiToken,
|
|
128
|
-
refreshOpenAIToken: () => refreshOpenAIToken,
|
|
129
|
-
refreshXaiToken: () => refreshXaiToken,
|
|
145
|
+
recordClaudeCodeRateLimit: () => recordClaudeCodeRateLimit,
|
|
146
|
+
refreshChatGPTToken: () => refreshChatGPTToken,
|
|
130
147
|
registerLogCleanup: () => registerLogCleanup,
|
|
131
148
|
registerRuntimeModels: () => registerRuntimeModels,
|
|
149
|
+
requiredKeyLabel: () => requiredKeyLabel,
|
|
132
150
|
resample: () => resample,
|
|
151
|
+
resolveClaudeCodeBinary: () => resolveClaudeCodeBinary,
|
|
152
|
+
revokeChatGPTSignIn: () => revokeChatGPTSignIn,
|
|
133
153
|
setProgressCallback: () => setProgressCallback,
|
|
134
154
|
setProviderCatalog: () => setProviderCatalog,
|
|
155
|
+
signInWithClaudeCode: () => signInWithClaudeCode,
|
|
156
|
+
signOutOfClaudeCode: () => signOutOfClaudeCode,
|
|
157
|
+
storageKeyProvider: () => storageKeyProvider,
|
|
135
158
|
toModelInfo: () => toModelInfo,
|
|
136
159
|
transcribeVoice: () => transcribeVoice,
|
|
137
160
|
usesOpenAICodexTransport: () => usesOpenAICodexTransport,
|
|
161
|
+
verifyIdToken: () => verifyIdToken,
|
|
138
162
|
withFileLock: () => withFileLock,
|
|
139
163
|
withoutPaidTier: () => withoutPaidTier
|
|
140
164
|
});
|
|
141
165
|
module.exports = __toCommonJS(index_exports);
|
|
142
166
|
|
|
143
|
-
// src/
|
|
144
|
-
var
|
|
145
|
-
var
|
|
146
|
-
var
|
|
147
|
-
var
|
|
148
|
-
|
|
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);
|
|
149
203
|
|
|
150
204
|
// src/paths.ts
|
|
151
205
|
var import_node_path = __toESM(require("path"), 1);
|
|
@@ -170,237 +224,519 @@ function getAppPaths() {
|
|
|
170
224
|
agentsDir: import_node_path.default.join(agentDir, "agents"),
|
|
171
225
|
progressFile: import_node_path.default.join(agentDir, "progress.json"),
|
|
172
226
|
progressBackupFile: import_node_path.default.join(agentDir, "progress.backup.json"),
|
|
173
|
-
trophiesFile: import_node_path.default.join(agentDir, "trophies.json")
|
|
227
|
+
trophiesFile: import_node_path.default.join(agentDir, "trophies.json"),
|
|
228
|
+
claudeCodeDir: import_node_path.default.join(agentDir, "claude-code"),
|
|
229
|
+
claudeCodeConfigDir: import_node_path.default.join(agentDir, "claude-code", "config")
|
|
174
230
|
};
|
|
175
231
|
}
|
|
176
232
|
|
|
177
|
-
// src/
|
|
178
|
-
var
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
233
|
+
// src/claude-code/runtime.ts
|
|
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);
|
|
237
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
238
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
182
239
|
|
|
183
|
-
// src/
|
|
184
|
-
var
|
|
185
|
-
var
|
|
186
|
-
var
|
|
187
|
-
var
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
} catch {
|
|
213
|
-
return void 0;
|
|
240
|
+
// src/claude-code/tar.ts
|
|
241
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
242
|
+
var import_promises = __toESM(require("fs/promises"), 1);
|
|
243
|
+
var import_node_zlib = __toESM(require("zlib"), 1);
|
|
244
|
+
var BLOCK = 512;
|
|
245
|
+
function readString(buf, offset, length) {
|
|
246
|
+
const end = buf.indexOf(0, offset);
|
|
247
|
+
return buf.toString("utf8", offset, end === -1 || end > offset + length ? offset + length : end);
|
|
248
|
+
}
|
|
249
|
+
function readSize(buf, offset) {
|
|
250
|
+
if (buf[offset] & 128) {
|
|
251
|
+
let value = buf[offset] & 127;
|
|
252
|
+
for (let i = 1; i < 12; i++) value = value * 256 + buf[offset + i];
|
|
253
|
+
return value;
|
|
254
|
+
}
|
|
255
|
+
const text = readString(buf, offset, 12).trim();
|
|
256
|
+
return text ? parseInt(text, 8) : 0;
|
|
257
|
+
}
|
|
258
|
+
function paxPath(data) {
|
|
259
|
+
let pos = 0;
|
|
260
|
+
while (pos < data.length) {
|
|
261
|
+
const space = data.indexOf(32, pos);
|
|
262
|
+
if (space === -1) break;
|
|
263
|
+
const len = parseInt(data.toString("utf8", pos, space), 10);
|
|
264
|
+
if (!Number.isFinite(len) || len <= 0) break;
|
|
265
|
+
const record = data.toString("utf8", space + 1, pos + len - 1);
|
|
266
|
+
const eq = record.indexOf("=");
|
|
267
|
+
if (eq !== -1 && record.slice(0, eq) === "path") return record.slice(eq + 1);
|
|
268
|
+
pos += len;
|
|
214
269
|
}
|
|
270
|
+
return void 0;
|
|
215
271
|
}
|
|
216
|
-
function
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
272
|
+
async function extractFileFromTarGz(tgzPath, entryName, destPath) {
|
|
273
|
+
const input = import_node_fs.default.createReadStream(tgzPath).pipe(import_node_zlib.default.createGunzip());
|
|
274
|
+
let pending = Buffer.alloc(0);
|
|
275
|
+
let remaining = 0;
|
|
276
|
+
let padding = 0;
|
|
277
|
+
let kind = "skip";
|
|
278
|
+
let meta = [];
|
|
279
|
+
let overridePath;
|
|
280
|
+
let out;
|
|
281
|
+
let found = false;
|
|
282
|
+
try {
|
|
283
|
+
for await (const chunk of input) {
|
|
284
|
+
pending = pending.length ? Buffer.concat([pending, chunk]) : chunk;
|
|
285
|
+
let pos = 0;
|
|
286
|
+
while (pos < pending.length) {
|
|
287
|
+
if (remaining > 0) {
|
|
288
|
+
const take = Math.min(remaining, pending.length - pos);
|
|
289
|
+
const slice = pending.subarray(pos, pos + take);
|
|
290
|
+
if (kind === "target") await out.write(slice);
|
|
291
|
+
else if (kind === "pax" || kind === "longname") meta.push(Buffer.from(slice));
|
|
292
|
+
pos += take;
|
|
293
|
+
remaining -= take;
|
|
294
|
+
if (remaining === 0) {
|
|
295
|
+
if (kind === "target") {
|
|
296
|
+
await out.close();
|
|
297
|
+
out = void 0;
|
|
298
|
+
found = true;
|
|
299
|
+
} else if (kind === "pax") {
|
|
300
|
+
overridePath = paxPath(Buffer.concat(meta)) ?? overridePath;
|
|
301
|
+
} else if (kind === "longname") {
|
|
302
|
+
overridePath = readString(Buffer.concat(meta), 0, Number.MAX_SAFE_INTEGER);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (padding > 0) {
|
|
308
|
+
const skip = Math.min(padding, pending.length - pos);
|
|
309
|
+
pos += skip;
|
|
310
|
+
padding -= skip;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (found) break;
|
|
314
|
+
if (pending.length - pos < BLOCK) break;
|
|
315
|
+
const header = pending.subarray(pos, pos + BLOCK);
|
|
316
|
+
pos += BLOCK;
|
|
317
|
+
if (header.every((b) => b === 0)) continue;
|
|
318
|
+
const size = readSize(header, 124);
|
|
319
|
+
const type = String.fromCharCode(header[156] || 48);
|
|
320
|
+
let name = readString(header, 0, 100);
|
|
321
|
+
if (readString(header, 257, 6) === "ustar") {
|
|
322
|
+
const prefix = readString(header, 345, 155);
|
|
323
|
+
if (prefix) name = `${prefix}/${name}`;
|
|
324
|
+
}
|
|
325
|
+
remaining = size;
|
|
326
|
+
padding = (BLOCK - size % BLOCK) % BLOCK;
|
|
327
|
+
meta = [];
|
|
328
|
+
if (type === "x") kind = "pax";
|
|
329
|
+
else if (type === "L") kind = "longname";
|
|
330
|
+
else {
|
|
331
|
+
const path7 = overridePath ?? name;
|
|
332
|
+
overridePath = void 0;
|
|
333
|
+
if ((type === "0" || type === "\0" || type === "7") && path7 === entryName) {
|
|
334
|
+
kind = "target";
|
|
335
|
+
out = await import_promises.default.open(destPath, "w", 493);
|
|
336
|
+
if (size === 0) {
|
|
337
|
+
await out.close();
|
|
338
|
+
out = void 0;
|
|
339
|
+
found = true;
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
kind = "skip";
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
pending = pos < pending.length ? Buffer.from(pending.subarray(pos)) : Buffer.alloc(0);
|
|
347
|
+
if (found) break;
|
|
348
|
+
}
|
|
349
|
+
if (!found && out) throw new Error(`Archive ended inside ${entryName}`);
|
|
350
|
+
return found;
|
|
351
|
+
} finally {
|
|
352
|
+
if (out) await out.close().catch(() => {
|
|
353
|
+
});
|
|
354
|
+
input.destroy();
|
|
355
|
+
}
|
|
223
356
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
357
|
+
|
|
358
|
+
// src/claude-code/manifest.json
|
|
359
|
+
var manifest_default = {
|
|
360
|
+
sdkVersion: "0.3.318",
|
|
361
|
+
claudeCodeVersion: "2.1.318",
|
|
362
|
+
platforms: {
|
|
363
|
+
"darwin-arm64": {
|
|
364
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.318.tgz",
|
|
365
|
+
integrity: "sha512-AWoXeDXzAMD3RaA21lWuHvCrKd8xmbTTmKjovninn4j07jheoVSMzFbCFjpp4kk5TdhT36JtWPkAxvjNMbcO8w==",
|
|
366
|
+
bin: "claude"
|
|
367
|
+
},
|
|
368
|
+
"darwin-x64": {
|
|
369
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.318.tgz",
|
|
370
|
+
integrity: "sha512-6CkZ5PCdebSY/LePp16sf/HYRtM6zVuxtn8qYICFS5KUZFsjccJJ9swVsOdHXNHi4SWZcSmIFTwoUbfFCzisRw==",
|
|
371
|
+
bin: "claude"
|
|
372
|
+
},
|
|
373
|
+
"linux-arm64": {
|
|
374
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.318.tgz",
|
|
375
|
+
integrity: "sha512-mBm4su17aMitLBBCQj3bHdejyUDY6z/ormGih+HG24N689RSbdKECjGjkT87tDBIR/4jyoqmuSeHqM1DI8TJlg==",
|
|
376
|
+
bin: "claude"
|
|
377
|
+
},
|
|
378
|
+
"linux-arm64-musl": {
|
|
379
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.318.tgz",
|
|
380
|
+
integrity: "sha512-l4LSYFOrvlYCrUImGfxMTQ0wg+T3s9CBhrBvPF/0oJGKZg+QnxHHU8jUVm9e9/xYmDRTtqFIrr4id/5AfH4XGw==",
|
|
381
|
+
bin: "claude"
|
|
382
|
+
},
|
|
383
|
+
"linux-x64": {
|
|
384
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.318.tgz",
|
|
385
|
+
integrity: "sha512-+mE+ueBD3oRFdaG16rd0h+LMjdeivGxL0pr/+FZK7JeLWVu+yPSw/f/X02b9oK36/DWykiFTu2G6HnJ9/+YO6w==",
|
|
386
|
+
bin: "claude"
|
|
387
|
+
},
|
|
388
|
+
"linux-x64-musl": {
|
|
389
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.318.tgz",
|
|
390
|
+
integrity: "sha512-SAF3lj/2OzTU84p+OOA13QEKA9+5fYAw0M9E7AJjdnRmVFGqQfbV7Do1i1f9Sj0mIubB3rLTRphGRw8O4HZjgA==",
|
|
391
|
+
bin: "claude"
|
|
392
|
+
},
|
|
393
|
+
"win32-arm64": {
|
|
394
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.318.tgz",
|
|
395
|
+
integrity: "sha512-19iVSZ4379QbZU1XnGUtfFvhKS1WzS8H1sbU/DbrzqbJ5Kq1eB1drj+obB1fCxTh6m26tRPj0zMJmdeu/jk3Gw==",
|
|
396
|
+
bin: "claude.exe"
|
|
397
|
+
},
|
|
398
|
+
"win32-x64": {
|
|
399
|
+
url: "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.318.tgz",
|
|
400
|
+
integrity: "sha512-ZdKVWpyVNVt0cXrOf8sUBaxcxLEAP+oGMdQDLBWr/BqOxTWLy7TBWOj1kzw+wVg/9WIkjkgQWeJzGXPWulR1sA==",
|
|
401
|
+
bin: "claude.exe"
|
|
231
402
|
}
|
|
232
403
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// src/claude-code/runtime.ts
|
|
407
|
+
var CLAUDE_CODE_MANIFEST = manifest_default;
|
|
408
|
+
var CLAUDE_CODE_AUTH_KEY = "anthropic-claude-code";
|
|
409
|
+
var CLAUDE_CODE_AUTH_MARKER = { via: "claude-code" };
|
|
410
|
+
function isClaudeCodeAuthMarker(value) {
|
|
411
|
+
return value?.via === CLAUDE_CODE_AUTH_MARKER.via;
|
|
412
|
+
}
|
|
413
|
+
function isLegacyClaudeOAuth(value) {
|
|
414
|
+
const token = value?.accessToken;
|
|
415
|
+
return typeof token === "string" && token.startsWith("sk-ant-oat");
|
|
416
|
+
}
|
|
417
|
+
function isMusl() {
|
|
418
|
+
if (process.platform !== "linux") return false;
|
|
419
|
+
const report = typeof process.report?.getReport === "function" ? process.report.getReport() : null;
|
|
420
|
+
return report != null && report.header?.glibcVersionRuntime === void 0;
|
|
421
|
+
}
|
|
422
|
+
function claudeCodePlatformKey(info = { platform: process.platform, arch: process.arch, musl: isMusl() }) {
|
|
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" : ""}`;
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
function resolveAsset(opts) {
|
|
430
|
+
const manifest = opts.manifest ?? CLAUDE_CODE_MANIFEST;
|
|
431
|
+
const key = opts.platformKey ?? claudeCodePlatformKey();
|
|
432
|
+
const asset = key ? manifest.platforms[key] : void 0;
|
|
433
|
+
if (!asset) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
`Claude Code isn't available for ${process.platform}-${process.arch}. Use an Anthropic API key instead.`
|
|
436
|
+
);
|
|
238
437
|
}
|
|
239
|
-
return
|
|
438
|
+
return { manifest, asset };
|
|
240
439
|
}
|
|
241
|
-
function
|
|
242
|
-
return
|
|
243
|
-
"X-Msh-Platform": KIMI_PLATFORM,
|
|
244
|
-
"X-Msh-Version": kimiVersion(),
|
|
245
|
-
"X-Msh-Device-Name": asciiHeader((0, import_node_os2.hostname)()),
|
|
246
|
-
"X-Msh-Device-Model": asciiHeader(deviceModel()),
|
|
247
|
-
"X-Msh-Os-Version": asciiHeader((0, import_node_os2.release)()),
|
|
248
|
-
"X-Msh-Device-Id": deviceId()
|
|
249
|
-
};
|
|
440
|
+
function claudeCodeHome(opts = {}) {
|
|
441
|
+
return opts.home ?? getAppPaths().claudeCodeDir;
|
|
250
442
|
}
|
|
251
|
-
function
|
|
252
|
-
return
|
|
253
|
-
"User-Agent": `kimi-code-cli/${kimiVersion()}`,
|
|
254
|
-
...deviceHeaders()
|
|
255
|
-
};
|
|
443
|
+
function claudeCodeConfigDir(opts = {}) {
|
|
444
|
+
return opts.home ? import_node_path2.default.join(opts.home, "config") : getAppPaths().claudeCodeConfigDir;
|
|
256
445
|
}
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return normalized === kimiCodeBaseUrl() || /(^|\.)kimi\.com/i.test(normalized);
|
|
446
|
+
function claudeCodeBinaryPath(opts = {}) {
|
|
447
|
+
const { manifest, asset } = resolveAsset(opts);
|
|
448
|
+
return import_node_path2.default.join(claudeCodeHome(opts), manifest.claudeCodeVersion, asset.bin);
|
|
261
449
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
method: "POST",
|
|
265
|
-
headers: {
|
|
266
|
-
...deviceHeaders(),
|
|
267
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
268
|
-
Accept: "application/json"
|
|
269
|
-
},
|
|
270
|
-
body: new URLSearchParams(params).toString(),
|
|
271
|
-
signal: oauthRequestSignal()
|
|
272
|
-
});
|
|
273
|
-
let data = {};
|
|
450
|
+
function resolveClaudeCodeBinary(opts = {}) {
|
|
451
|
+
let binary;
|
|
274
452
|
try {
|
|
275
|
-
|
|
276
|
-
if (parsed && typeof parsed === "object") data = parsed;
|
|
453
|
+
binary = claudeCodeBinaryPath(opts);
|
|
277
454
|
} catch {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
return import_node_fs2.default.statSync(binary).isFile() ? binary : null;
|
|
459
|
+
} catch {
|
|
460
|
+
return null;
|
|
278
461
|
}
|
|
279
|
-
return { status: response.status, data };
|
|
280
462
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
463
|
+
var installs = /* @__PURE__ */ new Map();
|
|
464
|
+
async function ensureClaudeCodeBinary(opts = {}) {
|
|
465
|
+
const existing = resolveClaudeCodeBinary(opts);
|
|
466
|
+
if (existing) return existing;
|
|
467
|
+
const target = claudeCodeBinaryPath(opts);
|
|
468
|
+
let install = installs.get(target);
|
|
469
|
+
if (!install) {
|
|
470
|
+
install = installClaudeCode(opts).finally(() => installs.delete(target));
|
|
471
|
+
installs.set(target, install);
|
|
472
|
+
}
|
|
473
|
+
return install;
|
|
474
|
+
}
|
|
475
|
+
async function installClaudeCode(opts) {
|
|
476
|
+
const { manifest, asset } = resolveAsset(opts);
|
|
477
|
+
const home = claudeCodeHome(opts);
|
|
478
|
+
const versionDir = import_node_path2.default.join(home, manifest.claudeCodeVersion);
|
|
479
|
+
const binary = import_node_path2.default.join(versionDir, asset.bin);
|
|
480
|
+
await import_promises2.default.mkdir(home, { recursive: true });
|
|
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}`);
|
|
484
|
+
try {
|
|
485
|
+
await download(asset, tarball, opts);
|
|
486
|
+
opts.onProgress?.({ phase: "extract" });
|
|
487
|
+
await import_promises2.default.mkdir(staging, { recursive: true });
|
|
488
|
+
const found = await extractFileFromTarGz(
|
|
489
|
+
tarball,
|
|
490
|
+
`package/${asset.bin}`,
|
|
491
|
+
import_node_path2.default.join(staging, asset.bin)
|
|
492
|
+
);
|
|
493
|
+
if (!found) throw new Error(`The Claude Code download has no ${asset.bin} binary.`);
|
|
494
|
+
await import_promises2.default.chmod(import_node_path2.default.join(staging, asset.bin), 493);
|
|
495
|
+
try {
|
|
496
|
+
await import_promises2.default.rename(staging, versionDir);
|
|
497
|
+
} catch (err) {
|
|
498
|
+
if (!import_node_fs2.default.existsSync(binary)) throw err;
|
|
499
|
+
}
|
|
500
|
+
await removeOldVersions(home, manifest.claudeCodeVersion);
|
|
501
|
+
return binary;
|
|
502
|
+
} finally {
|
|
503
|
+
await import_promises2.default.rm(tarball, { force: true }).catch(() => {
|
|
504
|
+
});
|
|
505
|
+
await import_promises2.default.rm(staging, { recursive: true, force: true }).catch(() => {
|
|
506
|
+
});
|
|
507
|
+
}
|
|
284
508
|
}
|
|
285
|
-
function
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
throw new Error("Kimi OAuth response missing access_token.");
|
|
509
|
+
async function download(asset, dest, opts) {
|
|
510
|
+
const doFetch = opts.fetch ?? fetch;
|
|
511
|
+
const res = await doFetch(asset.url, { signal: opts.signal });
|
|
512
|
+
if (!res.ok || !res.body) {
|
|
513
|
+
throw new Error(`Claude Code download failed (HTTP ${res.status}).`);
|
|
291
514
|
}
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
515
|
+
const totalBytes = Number(res.headers.get("content-length")) || void 0;
|
|
516
|
+
const hash = import_node_crypto.default.createHash("sha512");
|
|
517
|
+
const out = await import_promises2.default.open(dest, "w");
|
|
518
|
+
let receivedBytes = 0;
|
|
519
|
+
let lastReported = -1;
|
|
520
|
+
try {
|
|
521
|
+
opts.onProgress?.({ phase: "download", receivedBytes, totalBytes });
|
|
522
|
+
for await (const chunk of res.body) {
|
|
523
|
+
if (opts.signal?.aborted) throw opts.signal.reason ?? new Error("Download cancelled.");
|
|
524
|
+
hash.update(chunk);
|
|
525
|
+
await out.write(chunk);
|
|
526
|
+
receivedBytes += chunk.byteLength;
|
|
527
|
+
const step = totalBytes ? Math.floor(receivedBytes / totalBytes * 100) : Math.floor(receivedBytes / 4194304);
|
|
528
|
+
if (step !== lastReported) {
|
|
529
|
+
lastReported = step;
|
|
530
|
+
opts.onProgress?.({ phase: "download", receivedBytes, totalBytes });
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
} finally {
|
|
534
|
+
await out.close();
|
|
295
535
|
}
|
|
296
|
-
|
|
297
|
-
|
|
536
|
+
opts.onProgress?.({ phase: "verify" });
|
|
537
|
+
const expected = asset.integrity.replace(/^sha512-/, "");
|
|
538
|
+
if (hash.digest("base64") !== expected) {
|
|
539
|
+
throw new Error(
|
|
540
|
+
"The Claude Code download didn't match its pinned checksum, so it was discarded."
|
|
541
|
+
);
|
|
298
542
|
}
|
|
299
|
-
return {
|
|
300
|
-
accessToken,
|
|
301
|
-
refreshToken,
|
|
302
|
-
expiresAt: Date.now() + expiresIn * 1e3,
|
|
303
|
-
expiresIn,
|
|
304
|
-
baseUrl: kimiCodeBaseUrl()
|
|
305
|
-
};
|
|
306
543
|
}
|
|
307
|
-
async function
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
544
|
+
async function removeOldVersions(home, keep) {
|
|
545
|
+
let entries;
|
|
546
|
+
try {
|
|
547
|
+
entries = await import_promises2.default.readdir(home, { withFileTypes: true });
|
|
548
|
+
} catch {
|
|
549
|
+
return;
|
|
313
550
|
}
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
551
|
+
for (const entry of entries) {
|
|
552
|
+
if (!entry.isDirectory() || entry.name === keep || entry.name === "config") continue;
|
|
553
|
+
if (!/^\d+\.\d+\.\d+/.test(entry.name)) continue;
|
|
554
|
+
await import_promises2.default.rm(import_node_path2.default.join(home, entry.name), { recursive: true, force: true }).catch(() => {
|
|
555
|
+
});
|
|
319
556
|
}
|
|
320
|
-
return {
|
|
321
|
-
userCode,
|
|
322
|
-
deviceCode,
|
|
323
|
-
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
324
|
-
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
325
|
-
interval: Number(data.interval ?? 5) || 5
|
|
326
|
-
};
|
|
327
557
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
558
|
+
function formatClaudeCodeInstallProgress(progress) {
|
|
559
|
+
if (progress.phase === "verify") return "Checking the Claude Code download\u2026";
|
|
560
|
+
if (progress.phase === "extract") return "Unpacking Claude Code\u2026";
|
|
561
|
+
const mb = (bytes) => Math.round(bytes / 1048576);
|
|
562
|
+
if (!progress.totalBytes) return `Downloading Claude Code\u2026 ${mb(progress.receivedBytes)} MB`;
|
|
563
|
+
const pct = Math.floor(progress.receivedBytes / progress.totalBytes * 100);
|
|
564
|
+
return `Downloading Claude Code\u2026 ${pct}% (${mb(progress.receivedBytes)} of ${mb(progress.totalBytes)} MB)`;
|
|
565
|
+
}
|
|
566
|
+
var STRIPPED_ENV = [
|
|
567
|
+
"ANTHROPIC_API_KEY",
|
|
568
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
569
|
+
"ANTHROPIC_BASE_URL",
|
|
570
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
571
|
+
"CLAUDE_CODE_OAUTH_REFRESH_TOKEN",
|
|
572
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
573
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
574
|
+
"CLAUDE_CODE_USE_FOUNDRY",
|
|
575
|
+
"NODE_OPTIONS"
|
|
576
|
+
];
|
|
577
|
+
function claudeCodeEnv(opts = {}, base = process.env) {
|
|
578
|
+
const env = {};
|
|
579
|
+
for (const [key, value] of Object.entries(base)) {
|
|
580
|
+
if (value !== void 0 && !STRIPPED_ENV.includes(key)) env[key] = value;
|
|
581
|
+
}
|
|
582
|
+
env.CLAUDE_CONFIG_DIR = claudeCodeConfigDir(opts);
|
|
583
|
+
env.DISABLE_AUTOUPDATER = "1";
|
|
584
|
+
return env;
|
|
585
|
+
}
|
|
586
|
+
function runCommand(opts) {
|
|
587
|
+
if (opts.binary) return { command: opts.binary, prefix: opts.binaryArgs ?? [] };
|
|
588
|
+
const binary = resolveClaudeCodeBinary(opts);
|
|
589
|
+
if (!binary) throw new Error("Claude Code isn't installed yet. Sign in with Claude first.");
|
|
590
|
+
return { command: binary, prefix: [] };
|
|
591
|
+
}
|
|
592
|
+
function runClaude(args, opts) {
|
|
593
|
+
const { command, prefix } = runCommand(opts);
|
|
594
|
+
return new Promise((resolve, reject) => {
|
|
595
|
+
const child = (0, import_node_child_process.spawn)(command, [...prefix, ...args], {
|
|
596
|
+
env: claudeCodeEnv(opts),
|
|
597
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
598
|
+
windowsHide: true,
|
|
599
|
+
signal: opts.signal
|
|
600
|
+
});
|
|
601
|
+
let stdout = "";
|
|
602
|
+
let stderr = "";
|
|
603
|
+
child.stdout.on("data", (d) => stdout += d.toString());
|
|
604
|
+
child.stderr.on("data", (d) => stderr += d.toString());
|
|
605
|
+
child.on("error", reject);
|
|
606
|
+
child.on("close", (code) => resolve({ code, stdout, stderr }));
|
|
333
607
|
});
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
case "access_denied":
|
|
349
|
-
return { kind: "denied" };
|
|
350
|
-
default:
|
|
351
|
-
throw new Error(`Kimi token polling failed (${status}): ${errorDetail(data)}`);
|
|
608
|
+
}
|
|
609
|
+
async function claudeCodeAuthStatus(opts = {}) {
|
|
610
|
+
if (!opts.binary && !resolveClaudeCodeBinary(opts)) return { loggedIn: false };
|
|
611
|
+
const { stdout } = await runClaude(["auth", "status", "--json"], opts);
|
|
612
|
+
try {
|
|
613
|
+
const parsed = JSON.parse(stdout);
|
|
614
|
+
return {
|
|
615
|
+
loggedIn: parsed.loggedIn === true,
|
|
616
|
+
...typeof parsed.authMethod === "string" ? { authMethod: parsed.authMethod } : {},
|
|
617
|
+
...typeof parsed.email === "string" ? { email: parsed.email } : {},
|
|
618
|
+
...typeof parsed.subscriptionType === "string" ? { subscriptionType: parsed.subscriptionType } : {}
|
|
619
|
+
};
|
|
620
|
+
} catch {
|
|
621
|
+
return { loggedIn: false };
|
|
352
622
|
}
|
|
353
623
|
}
|
|
354
|
-
function
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
});
|
|
624
|
+
async function logoutClaudeCode(opts = {}) {
|
|
625
|
+
if (!opts.binary && !resolveClaudeCodeBinary(opts)) return;
|
|
626
|
+
const { code, stderr } = await runClaude(["auth", "logout"], opts);
|
|
627
|
+
if (code !== 0) throw new Error(stderr.trim() || `claude auth logout exited with ${code}`);
|
|
358
628
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
interval += 5;
|
|
380
|
-
}
|
|
629
|
+
var OPEN_URL_HOOK = `const arg = String(process.argv[1] || "");
|
|
630
|
+
const file = process.env.GG_CLAUDE_CODE_URL_FILE;
|
|
631
|
+
if (file && /https?:/.test(arg)) {
|
|
632
|
+
require("node:fs").appendFileSync(file, arg + "\\n");
|
|
633
|
+
process.exit(0);
|
|
634
|
+
}
|
|
635
|
+
`;
|
|
636
|
+
var CAPTURE_WAIT_MS = 3e3;
|
|
637
|
+
function stripAnsi(text) {
|
|
638
|
+
return text.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[0-9;?]*[A-Za-z]/g, "");
|
|
639
|
+
}
|
|
640
|
+
function claudeCodeCallbackUrl(manualUrl, captured) {
|
|
641
|
+
const port = /localhost(?::|%3A)(\d{2,5})(?:\/|%2F)callback/i.exec(captured)?.[1];
|
|
642
|
+
if (!port) return null;
|
|
643
|
+
try {
|
|
644
|
+
const url = new URL(manualUrl);
|
|
645
|
+
url.searchParams.set("redirect_uri", `http://localhost:${port}/callback`);
|
|
646
|
+
return url.toString();
|
|
647
|
+
} catch {
|
|
648
|
+
return null;
|
|
381
649
|
}
|
|
382
|
-
throw new Error("Kimi login timed out. Please run login again.");
|
|
383
650
|
}
|
|
384
|
-
async function
|
|
385
|
-
const {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
|
|
651
|
+
async function loginClaudeCode(callbacks, opts = {}) {
|
|
652
|
+
const { command, prefix } = runCommand(opts);
|
|
653
|
+
const home = claudeCodeHome(opts);
|
|
654
|
+
await import_promises2.default.mkdir(claudeCodeConfigDir(opts), { recursive: true });
|
|
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`);
|
|
658
|
+
await import_promises2.default.writeFile(hookFile, OPEN_URL_HOOK);
|
|
659
|
+
const env = {
|
|
660
|
+
...claudeCodeEnv(opts),
|
|
661
|
+
BROWSER: process.execPath,
|
|
662
|
+
NODE_OPTIONS: `--require ${JSON.stringify(hookFile)}`,
|
|
663
|
+
GG_CLAUDE_CODE_URL_FILE: urlFile
|
|
664
|
+
};
|
|
665
|
+
const promptAbort = new AbortController();
|
|
666
|
+
let timer;
|
|
667
|
+
try {
|
|
668
|
+
await new Promise((resolve, reject) => {
|
|
669
|
+
const child = (0, import_node_child_process.spawn)(command, [...prefix, "auth", "login", "--claudeai"], {
|
|
670
|
+
env,
|
|
671
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
672
|
+
windowsHide: true
|
|
673
|
+
});
|
|
674
|
+
let stdout = "";
|
|
675
|
+
let stderr = "";
|
|
676
|
+
let manualUrl = null;
|
|
677
|
+
let opened = false;
|
|
678
|
+
const onAbort = () => child.kill();
|
|
679
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
680
|
+
const open = (url, needsCode) => {
|
|
681
|
+
if (opened) return;
|
|
682
|
+
opened = true;
|
|
683
|
+
clearInterval(timer);
|
|
684
|
+
callbacks.onOpenUrl(url);
|
|
685
|
+
callbacks.onStatus("Finish signing in to Claude in your browser\u2026");
|
|
686
|
+
if (!needsCode) return;
|
|
687
|
+
void callbacks.onPromptCode("Paste the code shown after signing in to Claude:", promptAbort.signal).then((code) => {
|
|
688
|
+
const trimmed = code.trim();
|
|
689
|
+
if (trimmed && !child.stdin.destroyed) child.stdin.write(`${trimmed}
|
|
690
|
+
`);
|
|
691
|
+
}).catch(() => {
|
|
692
|
+
});
|
|
693
|
+
};
|
|
694
|
+
child.stdout.on("data", (d) => {
|
|
695
|
+
stdout += d.toString();
|
|
696
|
+
if (manualUrl) return;
|
|
697
|
+
const match = /visit:\s*(https:\/\/\S+)/.exec(stripAnsi(stdout));
|
|
698
|
+
if (!match) return;
|
|
699
|
+
manualUrl = match[1];
|
|
700
|
+
const started = Date.now();
|
|
701
|
+
timer = setInterval(() => {
|
|
702
|
+
let captured = "";
|
|
703
|
+
try {
|
|
704
|
+
captured = import_node_fs2.default.readFileSync(urlFile, "utf8");
|
|
705
|
+
} catch {
|
|
706
|
+
}
|
|
707
|
+
const callbackUrl = captured ? claudeCodeCallbackUrl(manualUrl, captured) : null;
|
|
708
|
+
if (callbackUrl) open(callbackUrl, false);
|
|
709
|
+
else if (Date.now() - started > (opts.browserWaitMs ?? CAPTURE_WAIT_MS))
|
|
710
|
+
open(manualUrl, true);
|
|
711
|
+
}, 100);
|
|
712
|
+
});
|
|
713
|
+
child.stderr.on("data", (d) => stderr += d.toString());
|
|
714
|
+
child.on("error", reject);
|
|
715
|
+
child.on("close", (code) => {
|
|
716
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
717
|
+
if (opts.signal?.aborted) reject(new Error("Sign-in cancelled."));
|
|
718
|
+
else if (code === 0) resolve();
|
|
719
|
+
else {
|
|
720
|
+
const detail = stripAnsi(stderr).trim().split("\n").pop()?.trim();
|
|
721
|
+
reject(new Error(detail || `Claude sign-in failed (exit ${code}).`));
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
} finally {
|
|
726
|
+
clearInterval(timer);
|
|
727
|
+
promptAbort.abort();
|
|
728
|
+
await import_promises2.default.rm(hookFile, { force: true }).catch(() => {
|
|
729
|
+
});
|
|
730
|
+
await import_promises2.default.rm(urlFile, { force: true }).catch(() => {
|
|
731
|
+
});
|
|
392
732
|
}
|
|
393
|
-
const errorCode = typeof data.error === "string" ? data.error : "";
|
|
394
|
-
throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
|
|
395
733
|
}
|
|
396
734
|
|
|
397
|
-
// src/
|
|
398
|
-
var
|
|
399
|
-
var import_node_fs3 = require("fs");
|
|
400
|
-
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
401
|
-
|
|
402
|
-
// src/oauth/anthropic.ts
|
|
735
|
+
// src/oauth/openai.ts
|
|
736
|
+
var import_node_http = __toESM(require("http"), 1);
|
|
403
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);
|
|
404
740
|
|
|
405
741
|
// src/oauth/pkce.ts
|
|
406
742
|
function base64urlEncode(bytes) {
|
|
@@ -420,201 +756,233 @@ async function generatePKCE() {
|
|
|
420
756
|
return { verifier, challenge };
|
|
421
757
|
}
|
|
422
758
|
|
|
423
|
-
// src/
|
|
424
|
-
var
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
var CLIENT_ID2 = atob("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
|
|
428
|
-
var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
429
|
-
var TOKEN_URLS = [
|
|
430
|
-
"https://platform.claude.com/v1/oauth/token",
|
|
431
|
-
"https://console.anthropic.com/v1/oauth/token"
|
|
432
|
-
];
|
|
433
|
-
var REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
|
|
434
|
-
var SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
435
|
-
async function postTokenRequest(body, label) {
|
|
436
|
-
const encoded = JSON.stringify(body);
|
|
437
|
-
const headers = {
|
|
438
|
-
"Content-Type": "application/json",
|
|
439
|
-
"User-Agent": await (0, import_gg_ai.getClaudeCliUserAgent)(),
|
|
440
|
-
"anthropic-beta": "oauth-2025-04-20"
|
|
441
|
-
};
|
|
442
|
-
let lastError = null;
|
|
443
|
-
for (const url of TOKEN_URLS) {
|
|
444
|
-
let response;
|
|
445
|
-
try {
|
|
446
|
-
response = await fetch(url, {
|
|
447
|
-
method: "POST",
|
|
448
|
-
headers,
|
|
449
|
-
body: encoded,
|
|
450
|
-
signal: oauthRequestSignal()
|
|
451
|
-
});
|
|
452
|
-
} catch (err) {
|
|
453
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
if (response.ok) {
|
|
457
|
-
return await response.json();
|
|
458
|
-
}
|
|
459
|
-
const text = await response.text();
|
|
460
|
-
if (response.status >= 400 && response.status < 500) {
|
|
461
|
-
throw new Error(`Anthropic ${label} failed (${response.status}): ${text}`);
|
|
462
|
-
}
|
|
463
|
-
lastError = new Error(`Anthropic ${label} failed (${response.status}): ${text}`);
|
|
464
|
-
}
|
|
465
|
-
throw lastError ?? new Error(`Anthropic ${label} failed: all endpoints unreachable`);
|
|
466
|
-
}
|
|
467
|
-
function toCredentials(data) {
|
|
468
|
-
return {
|
|
469
|
-
accessToken: data.access_token,
|
|
470
|
-
refreshToken: data.refresh_token,
|
|
471
|
-
expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
async function loginAnthropic(callbacks) {
|
|
475
|
-
const { verifier, challenge } = await generatePKCE();
|
|
476
|
-
const state = import_node_crypto2.default.randomBytes(16).toString("hex");
|
|
477
|
-
const params = new URLSearchParams({
|
|
478
|
-
code: "true",
|
|
479
|
-
client_id: CLIENT_ID2,
|
|
480
|
-
response_type: "code",
|
|
481
|
-
redirect_uri: REDIRECT_URI,
|
|
482
|
-
scope: SCOPES,
|
|
483
|
-
code_challenge: challenge,
|
|
484
|
-
code_challenge_method: "S256",
|
|
485
|
-
state
|
|
486
|
-
});
|
|
487
|
-
const authUrl = `${AUTHORIZE_URL}?${params}`;
|
|
488
|
-
callbacks.onOpenUrl(authUrl);
|
|
489
|
-
const raw = await callbacks.onPromptCode("Paste the code from the browser (format: code#state):");
|
|
490
|
-
const parts = raw.trim().split("#");
|
|
491
|
-
if (parts.length !== 2 || !parts[0] || parts[1] !== state) {
|
|
492
|
-
throw new Error("Invalid code or state mismatch. Please try again.");
|
|
493
|
-
}
|
|
494
|
-
return exchangeAnthropicCode(parts[0], parts[1], verifier);
|
|
495
|
-
}
|
|
496
|
-
async function exchangeAnthropicCode(code, state, verifier) {
|
|
497
|
-
const data = await postTokenRequest(
|
|
498
|
-
{
|
|
499
|
-
grant_type: "authorization_code",
|
|
500
|
-
client_id: CLIENT_ID2,
|
|
501
|
-
code,
|
|
502
|
-
state,
|
|
503
|
-
redirect_uri: REDIRECT_URI,
|
|
504
|
-
code_verifier: verifier
|
|
505
|
-
},
|
|
506
|
-
"token exchange"
|
|
507
|
-
);
|
|
508
|
-
return toCredentials(data);
|
|
509
|
-
}
|
|
510
|
-
async function refreshAnthropicToken(refreshToken) {
|
|
511
|
-
const data = await postTokenRequest(
|
|
512
|
-
{
|
|
513
|
-
grant_type: "refresh_token",
|
|
514
|
-
client_id: CLIENT_ID2,
|
|
515
|
-
refresh_token: refreshToken
|
|
516
|
-
},
|
|
517
|
-
"token refresh"
|
|
518
|
-
);
|
|
519
|
-
return toCredentials(data);
|
|
759
|
+
// src/oauth/request-timeout.ts
|
|
760
|
+
var OAUTH_REQUEST_TIMEOUT_MS = 3e4;
|
|
761
|
+
function oauthRequestSignal() {
|
|
762
|
+
return AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
|
|
520
763
|
}
|
|
521
764
|
|
|
522
765
|
// src/oauth/openai.ts
|
|
523
|
-
var
|
|
524
|
-
var
|
|
525
|
-
var
|
|
526
|
-
var
|
|
527
|
-
var
|
|
528
|
-
var
|
|
529
|
-
var
|
|
530
|
-
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;
|
|
531
780
|
var CALLBACK_TIMEOUT_MS = 12e4;
|
|
532
781
|
var MAX_PASTE_ATTEMPTS = 3;
|
|
533
|
-
var
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
const creds = await exchangeOpenAICode(code, verifier);
|
|
551
|
-
const accountId = getAccountId(creds.accessToken);
|
|
552
|
-
if (!accountId) {
|
|
553
|
-
throw new Error("Failed to extract accountId from OpenAI token.");
|
|
554
|
-
}
|
|
555
|
-
creds.accountId = accountId;
|
|
556
|
-
return creds;
|
|
557
|
-
}
|
|
558
|
-
function parseAuthorizationInput(input) {
|
|
559
|
-
const value = input.trim();
|
|
560
|
-
if (!value) return {};
|
|
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";
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
function defaultHostIdFile() {
|
|
791
|
+
return import_node_path3.default.join(getAppPaths().agentDir, "openai-host-id");
|
|
792
|
+
}
|
|
793
|
+
function defaultRegistrationFile() {
|
|
794
|
+
return import_node_path3.default.join(getAppPaths().agentDir, "openai-chatgpt-client.json");
|
|
795
|
+
}
|
|
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`;
|
|
561
799
|
try {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
};
|
|
567
|
-
|
|
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(() => {
|
|
804
|
+
});
|
|
805
|
+
throw err;
|
|
568
806
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
807
|
+
}
|
|
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();
|
|
814
|
+
}
|
|
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 {
|
|
572
822
|
}
|
|
573
|
-
|
|
574
|
-
|
|
823
|
+
return void 0;
|
|
824
|
+
}
|
|
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);
|
|
829
|
+
const { verifier, challenge } = await generatePKCE();
|
|
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));
|
|
833
|
+
try {
|
|
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);
|
|
841
|
+
}
|
|
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
|
+
);
|
|
856
|
+
}
|
|
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(() => {
|
|
875
|
+
});
|
|
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
|
+
}
|
|
575
888
|
return {
|
|
576
|
-
|
|
577
|
-
|
|
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
|
|
578
898
|
};
|
|
899
|
+
} finally {
|
|
900
|
+
listener.close();
|
|
579
901
|
}
|
|
580
|
-
if (/\s/.test(value) || value.length < MIN_RAW_CODE_LENGTH) return {};
|
|
581
|
-
return { code: value };
|
|
582
902
|
}
|
|
583
|
-
function
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
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()
|
|
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);
|
|
592
911
|
}
|
|
593
|
-
function
|
|
594
|
-
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
912
|
+
async function startCallbackListener(expectedState, anyPort) {
|
|
913
|
+
let settle;
|
|
914
|
+
const result = new Promise((resolve, reject) => {
|
|
915
|
+
settle = { resolve, reject };
|
|
916
|
+
});
|
|
917
|
+
result.catch(() => {
|
|
918
|
+
});
|
|
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>"
|
|
934
|
+
);
|
|
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 {
|
|
952
|
+
}
|
|
953
|
+
}
|
|
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() };
|
|
598
966
|
}
|
|
599
|
-
async function
|
|
967
|
+
async function acquireCallback(authUrl, expectedState, listener, callbacks) {
|
|
600
968
|
callbacks.onOpenUrl(authUrl);
|
|
601
|
-
callbacks.onStatus("Waiting for browser
|
|
969
|
+
callbacks.onStatus("Waiting for ChatGPT sign-in in your browser...");
|
|
602
970
|
const done = new AbortController();
|
|
603
971
|
try {
|
|
604
972
|
return await new Promise((resolve, reject) => {
|
|
605
|
-
let
|
|
973
|
+
let listenerFailed = false;
|
|
606
974
|
let pasteError;
|
|
607
975
|
const failIfExhausted = () => {
|
|
608
|
-
if (!
|
|
976
|
+
if (!listenerFailed || pasteError === void 0) return;
|
|
609
977
|
reject(
|
|
610
|
-
pasteError instanceof Error ? pasteError : new Error("Could not
|
|
978
|
+
pasteError instanceof Error ? pasteError : new Error("Could not complete the sign-in.")
|
|
611
979
|
);
|
|
612
980
|
};
|
|
613
|
-
|
|
614
|
-
|
|
981
|
+
listener.result.then(resolve, () => {
|
|
982
|
+
listenerFailed = true;
|
|
615
983
|
failIfExhausted();
|
|
616
984
|
});
|
|
617
|
-
|
|
985
|
+
promptForCallback(expectedState, callbacks, done.signal).then(resolve, (err) => {
|
|
618
986
|
if (err instanceof FatalLoginError) {
|
|
619
987
|
reject(err);
|
|
620
988
|
return;
|
|
@@ -625,687 +993,159 @@ async function acquireAuthorizationCode(authUrl, expectedState, callbacks) {
|
|
|
625
993
|
});
|
|
626
994
|
} finally {
|
|
627
995
|
done.abort();
|
|
996
|
+
listener.close();
|
|
628
997
|
}
|
|
629
998
|
}
|
|
630
999
|
var FatalLoginError = class extends Error {
|
|
631
1000
|
};
|
|
632
|
-
|
|
633
|
-
let message = "If the browser is on another machine (SSH/headless), paste the callback URL or code here:";
|
|
634
|
-
for (let attempt = 0; attempt < MAX_PASTE_ATTEMPTS; attempt++) {
|
|
635
|
-
if (signal.aborted) throw new Error("Authorization code already received.");
|
|
636
|
-
const raw = await callbacks.onPromptCode(message, signal);
|
|
637
|
-
if (signal.aborted) throw new Error("Authorization code already received.");
|
|
638
|
-
const parsed = parseAuthorizationInput(raw);
|
|
639
|
-
if (parsed.code && parsed.state && parsed.state !== expectedState) {
|
|
640
|
-
throw new FatalLoginError("Authorization state mismatch \u2014 start the login again.");
|
|
641
|
-
}
|
|
642
|
-
if (parsed.code) return parsed.code;
|
|
643
|
-
message = "That didn't contain an authorization code. Paste the full callback URL:";
|
|
644
|
-
}
|
|
645
|
-
throw new Error("No authorization code found in input.");
|
|
646
|
-
}
|
|
647
|
-
async function listenForCallback(expectedState, signal) {
|
|
648
|
-
return new Promise((resolve, reject) => {
|
|
649
|
-
let receivedCode = null;
|
|
650
|
-
const server = import_node_http.default.createServer((req, res) => {
|
|
651
|
-
const url = new URL(req.url || "", "http://localhost");
|
|
652
|
-
if (url.pathname !== "/auth/callback") {
|
|
653
|
-
res.statusCode = 404;
|
|
654
|
-
res.end("Not found");
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
if (url.searchParams.get("state") !== expectedState) {
|
|
658
|
-
res.statusCode = 400;
|
|
659
|
-
res.end("State mismatch");
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
receivedCode = url.searchParams.get("code");
|
|
663
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
664
|
-
res.end("<html><body><h1>Login successful!</h1><p>You can close this tab.</p></body></html>");
|
|
665
|
-
server.close();
|
|
666
|
-
});
|
|
667
|
-
server.on("error", (err) => {
|
|
668
|
-
reject(err);
|
|
669
|
-
});
|
|
670
|
-
server.listen(1455, "127.0.0.1");
|
|
671
|
-
const timeout = setTimeout(() => {
|
|
672
|
-
if (!receivedCode) server.close();
|
|
673
|
-
}, CALLBACK_TIMEOUT_MS);
|
|
674
|
-
timeout.unref();
|
|
675
|
-
const onAbort = () => {
|
|
676
|
-
server.close();
|
|
677
|
-
};
|
|
678
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
679
|
-
server.on("close", () => {
|
|
680
|
-
clearTimeout(timeout);
|
|
681
|
-
signal.removeEventListener("abort", onAbort);
|
|
682
|
-
if (receivedCode) {
|
|
683
|
-
resolve(receivedCode);
|
|
684
|
-
} else {
|
|
685
|
-
reject(new Error("Server closed without receiving code"));
|
|
686
|
-
}
|
|
687
|
-
});
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
async function exchangeOpenAICode(code, verifier) {
|
|
691
|
-
const response = await fetch(TOKEN_URL, {
|
|
692
|
-
method: "POST",
|
|
693
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
694
|
-
body: new URLSearchParams({
|
|
695
|
-
grant_type: "authorization_code",
|
|
696
|
-
client_id: CLIENT_ID3,
|
|
697
|
-
code,
|
|
698
|
-
redirect_uri: REDIRECT_URI2,
|
|
699
|
-
code_verifier: verifier
|
|
700
|
-
}),
|
|
701
|
-
signal: oauthRequestSignal()
|
|
702
|
-
});
|
|
703
|
-
if (!response.ok) {
|
|
704
|
-
const text = await response.text();
|
|
705
|
-
throw new Error(`OpenAI token exchange failed (${response.status}): ${text}`);
|
|
706
|
-
}
|
|
707
|
-
const data = await response.json();
|
|
708
|
-
return {
|
|
709
|
-
accessToken: data.access_token,
|
|
710
|
-
refreshToken: data.refresh_token,
|
|
711
|
-
expiresAt: Date.now() + data.expires_in * 1e3
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
async function refreshOpenAIToken(refreshToken) {
|
|
715
|
-
const response = await fetch(TOKEN_URL, {
|
|
716
|
-
method: "POST",
|
|
717
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
718
|
-
body: new URLSearchParams({
|
|
719
|
-
grant_type: "refresh_token",
|
|
720
|
-
refresh_token: refreshToken,
|
|
721
|
-
client_id: CLIENT_ID3
|
|
722
|
-
}),
|
|
723
|
-
signal: oauthRequestSignal()
|
|
724
|
-
});
|
|
725
|
-
if (!response.ok) {
|
|
726
|
-
const text = await response.text();
|
|
727
|
-
throw new Error(`OpenAI token refresh failed (${response.status}): ${text}`);
|
|
728
|
-
}
|
|
729
|
-
const data = await response.json();
|
|
730
|
-
const creds = {
|
|
731
|
-
accessToken: data.access_token,
|
|
732
|
-
refreshToken: data.refresh_token,
|
|
733
|
-
expiresAt: Date.now() + data.expires_in * 1e3
|
|
734
|
-
};
|
|
735
|
-
const accountId = getAccountId(creds.accessToken);
|
|
736
|
-
if (accountId) {
|
|
737
|
-
creds.accountId = accountId;
|
|
738
|
-
}
|
|
739
|
-
return creds;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
// src/oauth/gemini.ts
|
|
743
|
-
var import_node_http2 = __toESM(require("http"), 1);
|
|
744
|
-
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
745
|
-
var CLIENT_ID_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_ID";
|
|
746
|
-
var CLIENT_SECRET_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_SECRET";
|
|
747
|
-
var DEFAULT_CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
|
|
748
|
-
var DEFAULT_CLIENT_SECRET = "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";
|
|
749
|
-
var AUTHORIZE_URL3 = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
750
|
-
var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
751
|
-
var CODE_ASSIST_BASE_URL = "https://cloudcode-pa.googleapis.com";
|
|
752
|
-
var CODE_ASSIST_API_VERSION = "v1internal";
|
|
753
|
-
var CODE_ASSIST_POST_RETRIES = 3;
|
|
754
|
-
var CODE_ASSIST_POST_RETRY_DELAY_MS = 100;
|
|
755
|
-
var SCOPE2 = [
|
|
756
|
-
"https://www.googleapis.com/auth/cloud-platform",
|
|
757
|
-
"https://www.googleapis.com/auth/userinfo.email",
|
|
758
|
-
"https://www.googleapis.com/auth/userinfo.profile"
|
|
759
|
-
].join(" ");
|
|
760
|
-
var USER_TIER_FREE = "free-tier";
|
|
761
|
-
var USER_TIER_LEGACY = "legacy-tier";
|
|
762
|
-
var USER_TIER_STANDARD = "standard-tier";
|
|
763
|
-
var VALIDATION_REQUIRED_REASON = "VALIDATION_REQUIRED";
|
|
764
|
-
var VPC_SC_REASON = "SECURITY_POLICY_VIOLATED";
|
|
765
|
-
var CodeAssistHttpError = class extends Error {
|
|
766
|
-
status;
|
|
767
|
-
body;
|
|
768
|
-
constructor(label, status, body) {
|
|
769
|
-
super(`Gemini Code Assist ${label} failed (${status}): ${body}`);
|
|
770
|
-
this.name = "CodeAssistHttpError";
|
|
771
|
-
this.status = status;
|
|
772
|
-
this.body = body;
|
|
773
|
-
}
|
|
774
|
-
};
|
|
775
|
-
async function loginGemini(callbacks) {
|
|
776
|
-
const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
|
|
777
|
-
const { verifier, challenge } = await generatePKCE();
|
|
778
|
-
const state = import_node_crypto4.default.randomBytes(32).toString("hex");
|
|
779
|
-
const redirectUri = await getLoopbackRedirectUri();
|
|
780
|
-
const url = new URL(AUTHORIZE_URL3);
|
|
781
|
-
url.searchParams.set("response_type", "code");
|
|
782
|
-
url.searchParams.set("client_id", clientId2);
|
|
783
|
-
url.searchParams.set("redirect_uri", redirectUri);
|
|
784
|
-
url.searchParams.set("scope", SCOPE2);
|
|
785
|
-
url.searchParams.set("access_type", "offline");
|
|
786
|
-
url.searchParams.set("prompt", "consent");
|
|
787
|
-
url.searchParams.set("code_challenge", challenge);
|
|
788
|
-
url.searchParams.set("code_challenge_method", "S256");
|
|
789
|
-
url.searchParams.set("state", state);
|
|
790
|
-
let code;
|
|
791
|
-
try {
|
|
792
|
-
code = await loginWithServer(url.toString(), redirectUri, state, callbacks);
|
|
793
|
-
} catch {
|
|
794
|
-
callbacks.onOpenUrl(url.toString());
|
|
795
|
-
const raw = await callbacks.onPromptCode(
|
|
796
|
-
"Could not start local server. Paste the callback URL or code from the browser:"
|
|
797
|
-
);
|
|
798
|
-
const parsed = parseAuthorizationInput2(raw);
|
|
799
|
-
if (!parsed.code) {
|
|
800
|
-
throw new Error("No authorization code found in input.");
|
|
801
|
-
}
|
|
802
|
-
if (parsed.state && parsed.state !== state) {
|
|
803
|
-
throw new Error("Invalid state. Please try again.");
|
|
804
|
-
}
|
|
805
|
-
code = parsed.code;
|
|
806
|
-
}
|
|
807
|
-
const creds = await exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret);
|
|
808
|
-
callbacks.onStatus("Setting up Gemini Code Assist access...");
|
|
809
|
-
const projectId = await setupCodeAssistProject(creds.accessToken, callbacks);
|
|
810
|
-
return {
|
|
811
|
-
...creds,
|
|
812
|
-
projectId
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
async function refreshGeminiToken(refreshToken) {
|
|
816
|
-
const { clientId: clientId2, clientSecret } = getGeminiOAuthClientCredentials();
|
|
817
|
-
const data = await postTokenRequest2({
|
|
818
|
-
grant_type: "refresh_token",
|
|
819
|
-
refresh_token: refreshToken,
|
|
820
|
-
client_id: clientId2,
|
|
821
|
-
client_secret: clientSecret
|
|
822
|
-
});
|
|
823
|
-
return {
|
|
824
|
-
accessToken: data.access_token,
|
|
825
|
-
refreshToken: data.refresh_token ?? refreshToken,
|
|
826
|
-
expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3
|
|
827
|
-
};
|
|
828
|
-
}
|
|
829
|
-
function getGeminiOAuthClientCredentials() {
|
|
830
|
-
const clientId2 = process.env[CLIENT_ID_ENV]?.trim() || DEFAULT_CLIENT_ID;
|
|
831
|
-
const clientSecret = process.env[CLIENT_SECRET_ENV]?.trim() || DEFAULT_CLIENT_SECRET;
|
|
832
|
-
return { clientId: clientId2, clientSecret };
|
|
833
|
-
}
|
|
834
|
-
async function getLoopbackRedirectUri() {
|
|
835
|
-
return new Promise((resolve, reject) => {
|
|
836
|
-
const server = import_node_http2.default.createServer();
|
|
837
|
-
server.listen(0, "127.0.0.1", () => {
|
|
838
|
-
const addr = server.address();
|
|
839
|
-
server.close(() => {
|
|
840
|
-
if (addr && typeof addr === "object") {
|
|
841
|
-
resolve(`http://127.0.0.1:${addr.port}/oauth2callback`);
|
|
842
|
-
} else {
|
|
843
|
-
reject(new Error("Failed to allocate OAuth callback port."));
|
|
844
|
-
}
|
|
845
|
-
});
|
|
846
|
-
});
|
|
847
|
-
server.on("error", reject);
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
function parseAuthorizationInput2(input) {
|
|
1001
|
+
function parseCallbackInput(input) {
|
|
851
1002
|
const value = input.trim();
|
|
852
|
-
if (!value) return
|
|
1003
|
+
if (!value) return void 0;
|
|
853
1004
|
try {
|
|
854
|
-
|
|
855
|
-
return {
|
|
856
|
-
code: url.searchParams.get("code") ?? void 0,
|
|
857
|
-
state: url.searchParams.get("state") ?? void 0
|
|
858
|
-
};
|
|
1005
|
+
return new URL(value).searchParams;
|
|
859
1006
|
} catch {
|
|
860
1007
|
}
|
|
861
|
-
if (
|
|
862
|
-
|
|
863
|
-
return {
|
|
864
|
-
code: params.get("code") ?? void 0,
|
|
865
|
-
state: params.get("state") ?? void 0
|
|
866
|
-
};
|
|
1008
|
+
if (/[?&]?(code|error)=/.test(value)) {
|
|
1009
|
+
return new URLSearchParams(value.replace(/^.*\?/, ""));
|
|
867
1010
|
}
|
|
868
|
-
return
|
|
1011
|
+
return void 0;
|
|
869
1012
|
}
|
|
870
|
-
async function
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
if (url.searchParams.get("state") !== expectedState) {
|
|
883
|
-
res.statusCode = 400;
|
|
884
|
-
res.end("State mismatch");
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
receivedCode = url.searchParams.get("code");
|
|
888
|
-
res.writeHead(200, { "Content-Type": "text/html", Connection: "close" });
|
|
889
|
-
res.end("<html><body><h1>Login successful!</h1><p>You can close this tab.</p></body></html>");
|
|
890
|
-
server.close();
|
|
891
|
-
});
|
|
892
|
-
server.on("error", (err) => reject(err));
|
|
893
|
-
server.listen(port, "127.0.0.1", () => {
|
|
894
|
-
callbacks.onOpenUrl(authUrl);
|
|
895
|
-
callbacks.onStatus("Waiting for browser callback...");
|
|
896
|
-
});
|
|
897
|
-
const timeout = setTimeout(() => {
|
|
898
|
-
if (!receivedCode) server.close();
|
|
899
|
-
}, 12e4);
|
|
900
|
-
timeout.unref();
|
|
901
|
-
server.on("close", () => {
|
|
902
|
-
clearTimeout(timeout);
|
|
903
|
-
if (receivedCode) {
|
|
904
|
-
resolve(receivedCode);
|
|
905
|
-
} else {
|
|
906
|
-
reject(new Error("Server closed without receiving code."));
|
|
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.");
|
|
907
1023
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
async function exchangeGeminiCode(code, verifier, redirectUri, clientId2, clientSecret) {
|
|
912
|
-
const data = await postTokenRequest2({
|
|
913
|
-
grant_type: "authorization_code",
|
|
914
|
-
client_id: clientId2,
|
|
915
|
-
client_secret: clientSecret,
|
|
916
|
-
code,
|
|
917
|
-
redirect_uri: redirectUri,
|
|
918
|
-
code_verifier: verifier
|
|
919
|
-
});
|
|
920
|
-
if (!data.refresh_token) {
|
|
921
|
-
throw new Error("Gemini OAuth did not return a refresh token. Please try login again.");
|
|
1024
|
+
return params;
|
|
1025
|
+
}
|
|
1026
|
+
message = "That wasn't a callback URL. Paste the full URL from the browser's address bar:";
|
|
922
1027
|
}
|
|
923
|
-
|
|
924
|
-
accessToken: data.access_token,
|
|
925
|
-
refreshToken: data.refresh_token,
|
|
926
|
-
expiresAt: Date.now() + data.expires_in * 1e3 - 5 * 60 * 1e3
|
|
927
|
-
};
|
|
1028
|
+
throw new Error("No callback URL found in input.");
|
|
928
1029
|
}
|
|
929
|
-
async function
|
|
930
|
-
|
|
1030
|
+
async function postForm(url, form) {
|
|
1031
|
+
return fetch(url, {
|
|
931
1032
|
method: "POST",
|
|
932
1033
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
933
|
-
body: new URLSearchParams(
|
|
1034
|
+
body: new URLSearchParams(form),
|
|
934
1035
|
signal: oauthRequestSignal()
|
|
935
1036
|
});
|
|
936
|
-
if (!response.ok) {
|
|
937
|
-
const text = await response.text();
|
|
938
|
-
throw new Error(`Gemini token request failed (${response.status}): ${text}`);
|
|
939
|
-
}
|
|
940
|
-
return await response.json();
|
|
941
|
-
}
|
|
942
|
-
async function setupCodeAssistProject(accessToken, callbacks) {
|
|
943
|
-
const envProject = process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GOOGLE_CLOUD_PROJECT_ID;
|
|
944
|
-
if (envProject && /^\d+$/.test(envProject)) {
|
|
945
|
-
throw new Error("GOOGLE_CLOUD_PROJECT must be a project ID, not a numeric project number.");
|
|
946
|
-
}
|
|
947
|
-
const coreMetadata = {
|
|
948
|
-
ideType: "IDE_UNSPECIFIED",
|
|
949
|
-
platform: "PLATFORM_UNSPECIFIED",
|
|
950
|
-
pluginType: "GEMINI"
|
|
951
|
-
};
|
|
952
|
-
const projectMetadata = {
|
|
953
|
-
...coreMetadata,
|
|
954
|
-
...envProject ? { duetProject: envProject } : {}
|
|
955
|
-
};
|
|
956
|
-
let loadRes;
|
|
957
|
-
while (true) {
|
|
958
|
-
loadRes = await loadCodeAssist(accessToken, envProject, projectMetadata);
|
|
959
|
-
const validation = getValidationRequiredTier(loadRes);
|
|
960
|
-
if (!validation) break;
|
|
961
|
-
callbacks.onStatus(
|
|
962
|
-
`Gemini Code Assist requires account validation${validation.reasonMessage ? `: ${validation.reasonMessage}` : ""}`
|
|
963
|
-
);
|
|
964
|
-
callbacks.onOpenUrl(validation.validationUrl);
|
|
965
|
-
const answer = await callbacks.onPromptCode(
|
|
966
|
-
"Complete validation in the browser, then press Enter to retry (or type cancel):"
|
|
967
|
-
);
|
|
968
|
-
if (answer.trim().toLowerCase() === "cancel") {
|
|
969
|
-
throw new Error("Gemini Code Assist account validation was cancelled.");
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
if (loadRes.currentTier) {
|
|
973
|
-
const project2 = loadRes.cloudaicompanionProject ?? envProject;
|
|
974
|
-
if (!project2) throwProjectError(loadRes);
|
|
975
|
-
return project2;
|
|
976
|
-
}
|
|
977
|
-
const tier = getOnboardTier(loadRes);
|
|
978
|
-
const onboardReq = tier.id === USER_TIER_FREE ? {
|
|
979
|
-
tierId: tier.id,
|
|
980
|
-
cloudaicompanionProject: void 0,
|
|
981
|
-
metadata: coreMetadata
|
|
982
|
-
} : {
|
|
983
|
-
tierId: tier.id,
|
|
984
|
-
cloudaicompanionProject: envProject,
|
|
985
|
-
metadata: projectMetadata
|
|
986
|
-
};
|
|
987
|
-
let operation = await codeAssistPost(
|
|
988
|
-
accessToken,
|
|
989
|
-
"onboardUser",
|
|
990
|
-
onboardReq
|
|
991
|
-
);
|
|
992
|
-
while (!operation.done && operation.name) {
|
|
993
|
-
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
994
|
-
operation = await codeAssistGet(accessToken, operation.name);
|
|
995
|
-
}
|
|
996
|
-
const project = operation.response?.cloudaicompanionProject?.id ?? envProject;
|
|
997
|
-
if (!project) throwProjectError(loadRes);
|
|
998
|
-
return project;
|
|
999
|
-
}
|
|
1000
|
-
async function loadCodeAssist(accessToken, envProject, metadata) {
|
|
1001
|
-
try {
|
|
1002
|
-
return await codeAssistPost(accessToken, "loadCodeAssist", {
|
|
1003
|
-
...envProject ? { cloudaicompanionProject: envProject } : {},
|
|
1004
|
-
metadata
|
|
1005
|
-
});
|
|
1006
|
-
} catch (err) {
|
|
1007
|
-
if (err instanceof CodeAssistHttpError && isVpcScAffectedError(err)) {
|
|
1008
|
-
return { currentTier: { id: USER_TIER_STANDARD } };
|
|
1009
|
-
}
|
|
1010
|
-
if (err instanceof CodeAssistHttpError && err.status === 403 && envProject === "cloudshell-gca") {
|
|
1011
|
-
throw new Error(
|
|
1012
|
-
"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=...",
|
|
1013
|
-
{ cause: err }
|
|
1014
|
-
);
|
|
1015
|
-
}
|
|
1016
|
-
throw err;
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
function getValidationRequiredTier(response) {
|
|
1020
|
-
return response.ineligibleTiers?.find(
|
|
1021
|
-
(tier) => tier.reasonCode === VALIDATION_REQUIRED_REASON && typeof tier.validationUrl === "string"
|
|
1022
|
-
);
|
|
1023
|
-
}
|
|
1024
|
-
function getOnboardTier(response) {
|
|
1025
|
-
const defaultTier = response.allowedTiers?.find((tier) => tier.isDefault);
|
|
1026
|
-
return defaultTier ?? { id: USER_TIER_LEGACY, name: "" };
|
|
1027
|
-
}
|
|
1028
|
-
function throwProjectError(response) {
|
|
1029
|
-
const reasons = response.ineligibleTiers?.map((tier) => tier.reasonMessage ?? tier.tierName).filter((reason) => Boolean(reason));
|
|
1030
|
-
if (reasons && reasons.length > 0) {
|
|
1031
|
-
throw new Error(`Gemini Code Assist setup failed: ${reasons.join(", ")}`);
|
|
1032
|
-
}
|
|
1033
|
-
throw new Error(
|
|
1034
|
-
"Gemini requires a Google Cloud project for this account. Set GOOGLE_CLOUD_PROJECT and try again."
|
|
1035
|
-
);
|
|
1036
|
-
}
|
|
1037
|
-
async function codeAssistPost(accessToken, method, body) {
|
|
1038
|
-
let lastError;
|
|
1039
|
-
for (let attempt = 0; attempt <= CODE_ASSIST_POST_RETRIES; attempt++) {
|
|
1040
|
-
try {
|
|
1041
|
-
return await codeAssistRequest(getCodeAssistMethodUrl(method), accessToken, method, {
|
|
1042
|
-
method: "POST",
|
|
1043
|
-
body: JSON.stringify(body)
|
|
1044
|
-
});
|
|
1045
|
-
} catch (err) {
|
|
1046
|
-
if (!(err instanceof CodeAssistHttpError) || attempt === CODE_ASSIST_POST_RETRIES || !shouldRetryCodeAssistStatus(err.status)) {
|
|
1047
|
-
throw err;
|
|
1048
|
-
}
|
|
1049
|
-
lastError = err;
|
|
1050
|
-
}
|
|
1051
|
-
await new Promise((resolve) => setTimeout(resolve, CODE_ASSIST_POST_RETRY_DELAY_MS));
|
|
1052
|
-
}
|
|
1053
|
-
throw lastError ?? new Error(`Gemini Code Assist ${method} failed.`);
|
|
1054
|
-
}
|
|
1055
|
-
async function codeAssistGet(accessToken, operationName) {
|
|
1056
|
-
return codeAssistRequest(getCodeAssistOperationUrl(operationName), accessToken, "operation", {
|
|
1057
|
-
method: "GET"
|
|
1058
|
-
});
|
|
1059
1037
|
}
|
|
1060
|
-
async function
|
|
1061
|
-
const response = await
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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
|
|
1065
1046
|
});
|
|
1066
1047
|
if (!response.ok) {
|
|
1067
|
-
const text = await response.text();
|
|
1068
|
-
throw new
|
|
1048
|
+
const text = await response.text().catch(() => "");
|
|
1049
|
+
throw new Error(`ChatGPT token exchange failed (${response.status}): ${text.slice(0, 300)}`);
|
|
1069
1050
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
const endpoint = process.env.CODE_ASSIST_ENDPOINT ?? CODE_ASSIST_BASE_URL;
|
|
1074
|
-
const version = process.env.CODE_ASSIST_API_VERSION || CODE_ASSIST_API_VERSION;
|
|
1075
|
-
return `${endpoint}/${version}`;
|
|
1076
|
-
}
|
|
1077
|
-
function getCodeAssistMethodUrl(method) {
|
|
1078
|
-
return `${getCodeAssistBaseUrl()}:${method}`;
|
|
1079
|
-
}
|
|
1080
|
-
function getCodeAssistOperationUrl(operationName) {
|
|
1081
|
-
return `${getCodeAssistBaseUrl()}/${operationName}`;
|
|
1082
|
-
}
|
|
1083
|
-
function shouldRetryCodeAssistStatus(status) {
|
|
1084
|
-
return status === 429 || status === 499 || status >= 500 && status <= 599;
|
|
1085
|
-
}
|
|
1086
|
-
function isVpcScAffectedError(error) {
|
|
1087
|
-
try {
|
|
1088
|
-
const parsed = JSON.parse(error.body);
|
|
1089
|
-
if (!parsed || typeof parsed !== "object" || !("error" in parsed)) return false;
|
|
1090
|
-
const details = parsed.error?.details;
|
|
1091
|
-
return Array.isArray(details) ? details.some(
|
|
1092
|
-
(detail) => detail != null && typeof detail === "object" && "reason" in detail && detail.reason === VPC_SC_REASON
|
|
1093
|
-
) : false;
|
|
1094
|
-
} catch {
|
|
1095
|
-
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.");
|
|
1096
1054
|
}
|
|
1097
|
-
}
|
|
1098
|
-
function codeAssistHeaders(accessToken) {
|
|
1099
1055
|
return {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
// src/oauth/xai.ts
|
|
1108
|
-
var DEFAULT_CLIENT_ID2 = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
1109
|
-
var DEFAULT_ISSUER = "https://auth.x.ai";
|
|
1110
|
-
var DEFAULT_CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
|
|
1111
|
-
var OAUTH_SCOPE = "openid profile email offline_access grok-cli:access api:access";
|
|
1112
|
-
var DEFAULT_GROK_CLI_VERSION = "1.0.67";
|
|
1113
|
-
var DEVICE_TIMEOUT_FALLBACK_MS = 10 * 60 * 1e3;
|
|
1114
|
-
var DEFAULT_TOKEN_LIFETIME_SECONDS = 3600;
|
|
1115
|
-
function issuer() {
|
|
1116
|
-
return (process.env.XAI_OAUTH_ISSUER ?? DEFAULT_ISSUER).replace(/\/+$/, "");
|
|
1117
|
-
}
|
|
1118
|
-
function clientId() {
|
|
1119
|
-
return process.env.XAI_OAUTH_CLIENT_ID ?? DEFAULT_CLIENT_ID2;
|
|
1120
|
-
}
|
|
1121
|
-
function grokCliBaseUrl() {
|
|
1122
|
-
return (process.env.XAI_CLI_BASE_URL ?? DEFAULT_CLI_BASE_URL).replace(/\/+$/, "");
|
|
1123
|
-
}
|
|
1124
|
-
function grokCliVersion() {
|
|
1125
|
-
const raw = process.env.GROK_CLI_VERSION ?? DEFAULT_GROK_CLI_VERSION;
|
|
1126
|
-
const cleaned = raw.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
1127
|
-
return cleaned.length > 0 ? cleaned : DEFAULT_GROK_CLI_VERSION;
|
|
1128
|
-
}
|
|
1129
|
-
function grokCliHeaders(modelId) {
|
|
1130
|
-
return {
|
|
1131
|
-
"X-XAI-Token-Auth": "xai-grok-cli",
|
|
1132
|
-
"x-grok-client-version": grokCliVersion(),
|
|
1133
|
-
"x-grok-client-identifier": "ggcoder",
|
|
1134
|
-
...modelId ? { "x-grok-model-override": modelId } : {}
|
|
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)
|
|
1135
1061
|
};
|
|
1136
1062
|
}
|
|
1137
|
-
function
|
|
1138
|
-
if (
|
|
1139
|
-
|
|
1140
|
-
return normalized === grokCliBaseUrl() || /(^|\.)grok\.com/i.test(normalized);
|
|
1141
|
-
}
|
|
1142
|
-
async function postForm2(endpoint, params) {
|
|
1143
|
-
const response = await fetch(`${issuer()}${endpoint}`, {
|
|
1144
|
-
method: "POST",
|
|
1145
|
-
headers: {
|
|
1146
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
1147
|
-
Accept: "application/json",
|
|
1148
|
-
"User-Agent": `grok-cli/${grokCliVersion()}`
|
|
1149
|
-
},
|
|
1150
|
-
body: new URLSearchParams(params).toString(),
|
|
1151
|
-
signal: oauthRequestSignal()
|
|
1152
|
-
});
|
|
1153
|
-
let data = {};
|
|
1154
|
-
try {
|
|
1155
|
-
const parsed = await response.json();
|
|
1156
|
-
if (parsed && typeof parsed === "object") data = parsed;
|
|
1157
|
-
} catch {
|
|
1158
|
-
}
|
|
1159
|
-
return { status: response.status, data };
|
|
1160
|
-
}
|
|
1161
|
-
function errorDetail2(data) {
|
|
1162
|
-
const desc = data.error_description ?? data.message ?? data.error;
|
|
1163
|
-
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
1164
|
-
}
|
|
1165
|
-
function jwtExpirySeconds(token) {
|
|
1166
|
-
const payload = token.split(".")[1];
|
|
1167
|
-
if (!payload) return void 0;
|
|
1168
|
-
try {
|
|
1169
|
-
const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString(
|
|
1170
|
-
"utf-8"
|
|
1171
|
-
);
|
|
1172
|
-
const claims = JSON.parse(json);
|
|
1173
|
-
if (!claims || typeof claims !== "object") return void 0;
|
|
1174
|
-
const exp = claims.exp;
|
|
1175
|
-
return typeof exp === "number" && Number.isFinite(exp) && exp > 0 ? exp : void 0;
|
|
1176
|
-
} catch {
|
|
1177
|
-
return void 0;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
function credsFromTokenResponse2(data, opts) {
|
|
1181
|
-
const accessToken = data.access_token;
|
|
1182
|
-
const responseRefreshToken = data.refresh_token;
|
|
1183
|
-
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
1184
|
-
throw new Error("Grok OAuth response missing access_token.");
|
|
1185
|
-
}
|
|
1186
|
-
const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
|
|
1187
|
-
if (refreshToken.length === 0) {
|
|
1188
|
-
throw new Error(
|
|
1189
|
-
"Grok OAuth response missing refresh_token \u2014 the offline_access scope was not granted."
|
|
1190
|
-
);
|
|
1191
|
-
}
|
|
1192
|
-
const responseExpiresIn = Number(data.expires_in);
|
|
1193
|
-
let expiresIn;
|
|
1194
|
-
if (Number.isFinite(responseExpiresIn) && responseExpiresIn > 0) {
|
|
1195
|
-
expiresIn = responseExpiresIn;
|
|
1196
|
-
} else {
|
|
1197
|
-
const exp = jwtExpirySeconds(accessToken);
|
|
1198
|
-
const fromJwt = exp !== void 0 ? exp - Math.floor(Date.now() / 1e3) : 0;
|
|
1199
|
-
expiresIn = fromJwt > 0 ? fromJwt : DEFAULT_TOKEN_LIFETIME_SECONDS;
|
|
1063
|
+
async function refreshChatGPTToken(creds) {
|
|
1064
|
+
if (!creds.clientId) {
|
|
1065
|
+
throw new Error("ChatGPT token refresh failed (401): no issued client ID saved");
|
|
1200
1066
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
baseUrl: grokCliBaseUrl()
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
async function requestDeviceAuthorization2() {
|
|
1210
|
-
const { status, data } = await postForm2("/oauth2/device/code", {
|
|
1211
|
-
client_id: clientId(),
|
|
1212
|
-
scope: OAUTH_SCOPE
|
|
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
|
|
1213
1072
|
});
|
|
1214
|
-
if (
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
const userCode = data.user_code;
|
|
1218
|
-
const deviceCode = data.device_code;
|
|
1219
|
-
const verificationUriComplete = data.verification_uri_complete;
|
|
1220
|
-
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
1221
|
-
throw new Error("Grok device authorization response missing user_code/device_code.");
|
|
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)}`);
|
|
1222
1076
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
1227
|
-
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
1228
|
-
interval: Number(data.interval ?? 5) || 5,
|
|
1229
|
-
// RFC 8628 §3.2: the server states how long the device code lives. Honor it
|
|
1230
|
-
// rather than imposing our own budget — polling a code we know is dead only
|
|
1231
|
-
// burns requests, and a longer local window would keep a user waiting past
|
|
1232
|
-
// the point the code can ever succeed.
|
|
1233
|
-
expiresInMs: (Number(data.expires_in) || 0) > 0 ? Number(data.expires_in) * 1e3 : DEVICE_TIMEOUT_FALLBACK_MS
|
|
1234
|
-
};
|
|
1235
|
-
}
|
|
1236
|
-
async function pollDeviceToken2(deviceCode) {
|
|
1237
|
-
const { status, data } = await postForm2("/oauth2/token", {
|
|
1238
|
-
client_id: clientId(),
|
|
1239
|
-
device_code: deviceCode,
|
|
1240
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1241
|
-
});
|
|
1242
|
-
if (status === 200 && typeof data.access_token === "string") {
|
|
1243
|
-
return { kind: "success", creds: credsFromTokenResponse2(data) };
|
|
1244
|
-
}
|
|
1245
|
-
if (status >= 500) {
|
|
1246
|
-
throw new Error(`Grok token polling server error (${status}): ${errorDetail2(data)}`);
|
|
1247
|
-
}
|
|
1248
|
-
const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
|
|
1249
|
-
switch (errorCode) {
|
|
1250
|
-
case "authorization_pending":
|
|
1251
|
-
return { kind: "pending" };
|
|
1252
|
-
case "slow_down":
|
|
1253
|
-
return { kind: "slow_down" };
|
|
1254
|
-
case "expired_token":
|
|
1255
|
-
return { kind: "expired" };
|
|
1256
|
-
case "access_denied":
|
|
1257
|
-
return { kind: "denied" };
|
|
1258
|
-
default:
|
|
1259
|
-
throw new Error(`Grok token polling failed (${status}): ${errorDetail2(data)}`);
|
|
1077
|
+
const data = await response.json();
|
|
1078
|
+
if (!data.access_token || !data.refresh_token) {
|
|
1079
|
+
throw new Error("ChatGPT token refresh returned no tokens.");
|
|
1260
1080
|
}
|
|
1081
|
+
const expiresIn = data.expires_in ?? 3600;
|
|
1082
|
+
return {
|
|
1083
|
+
accessToken: data.access_token,
|
|
1084
|
+
refreshToken: data.refresh_token,
|
|
1085
|
+
expiresAt: Date.now() + expiresIn * 1e3,
|
|
1086
|
+
expiresIn,
|
|
1087
|
+
...data.id_token ? { idToken: data.id_token } : {},
|
|
1088
|
+
...data.scope ? { scopes: data.scope.split(/\s+/).filter(Boolean) } : {}
|
|
1089
|
+
};
|
|
1261
1090
|
}
|
|
1262
|
-
function
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
let interval = Math.max(auth.interval, 1);
|
|
1276
|
-
while (Date.now() < deadline) {
|
|
1277
|
-
const remaining = deadline - Date.now();
|
|
1278
|
-
await sleep2(Math.min(interval * 1e3, remaining));
|
|
1279
|
-
const result = await pollDeviceToken2(auth.deviceCode);
|
|
1280
|
-
if (result.kind === "success") return result.creds;
|
|
1281
|
-
if (result.kind === "denied") {
|
|
1282
|
-
throw new Error("Grok authorization was denied.");
|
|
1283
|
-
}
|
|
1284
|
-
if (result.kind === "expired") {
|
|
1285
|
-
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 {
|
|
1286
1104
|
}
|
|
1287
|
-
if (
|
|
1288
|
-
|
|
1105
|
+
if (attempt < attempts - 1) {
|
|
1106
|
+
await new Promise((r) => setTimeout(r, (options.backoffMs ?? 500) * 2 ** attempt));
|
|
1289
1107
|
}
|
|
1290
1108
|
}
|
|
1291
|
-
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
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.");
|
|
1301
1138
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1139
|
+
if (typeof claims.exp !== "number" || claims.exp + ID_TOKEN_CLOCK_SKEW_S < now) {
|
|
1140
|
+
throw new Error("ChatGPT ID token has expired.");
|
|
1141
|
+
}
|
|
1142
|
+
if (claims.nonce !== expected.nonce) throw new Error("ChatGPT ID token nonce mismatch.");
|
|
1143
|
+
return claims;
|
|
1304
1144
|
}
|
|
1305
1145
|
|
|
1306
1146
|
// src/file-lock.ts
|
|
1307
|
-
var
|
|
1308
|
-
var
|
|
1147
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
1148
|
+
var import_promises5 = require("timers/promises");
|
|
1309
1149
|
var STALE_TIMEOUT_MS = 1e4;
|
|
1310
1150
|
var RETRY_INTERVAL_MS = 50;
|
|
1311
1151
|
var MAX_WAIT_MS = 5e3;
|
|
@@ -1325,7 +1165,7 @@ async function acquireLock(lockPath) {
|
|
|
1325
1165
|
while (true) {
|
|
1326
1166
|
try {
|
|
1327
1167
|
const info = { pid: process.pid, timestamp: Date.now() };
|
|
1328
|
-
await
|
|
1168
|
+
await import_promises4.default.writeFile(lockPath, JSON.stringify(info), { flag: "wx" });
|
|
1329
1169
|
return;
|
|
1330
1170
|
} catch (err) {
|
|
1331
1171
|
const code = err.code;
|
|
@@ -1334,47 +1174,47 @@ async function acquireLock(lockPath) {
|
|
|
1334
1174
|
const lockPresent = await lockFileExists(lockPath);
|
|
1335
1175
|
if (!lockPresent && ++refusedWithoutLock > 3) throw err;
|
|
1336
1176
|
if (Date.now() - startTime > MAX_WAIT_MS) throw err;
|
|
1337
|
-
await (0,
|
|
1177
|
+
await (0, import_promises5.setTimeout)(RETRY_INTERVAL_MS);
|
|
1338
1178
|
continue;
|
|
1339
1179
|
}
|
|
1340
1180
|
try {
|
|
1341
|
-
const content = await
|
|
1181
|
+
const content = await import_promises4.default.readFile(lockPath, "utf-8");
|
|
1342
1182
|
const info = JSON.parse(content);
|
|
1343
1183
|
const isProcessAlive = isAlive(info.pid);
|
|
1344
1184
|
const isStale = Date.now() - info.timestamp > STALE_TIMEOUT_MS;
|
|
1345
1185
|
if (!isProcessAlive || isStale) {
|
|
1346
|
-
await
|
|
1186
|
+
await import_promises4.default.unlink(lockPath).catch(() => {
|
|
1347
1187
|
});
|
|
1348
1188
|
continue;
|
|
1349
1189
|
}
|
|
1350
1190
|
} catch {
|
|
1351
|
-
const stat = await
|
|
1191
|
+
const stat = await import_promises4.default.stat(lockPath).catch(() => null);
|
|
1352
1192
|
if (!stat) continue;
|
|
1353
1193
|
if (Date.now() - stat.mtimeMs > STALE_TIMEOUT_MS) {
|
|
1354
|
-
await
|
|
1194
|
+
await import_promises4.default.unlink(lockPath).catch(() => {
|
|
1355
1195
|
});
|
|
1356
1196
|
continue;
|
|
1357
1197
|
}
|
|
1358
1198
|
}
|
|
1359
1199
|
if (Date.now() - startTime > MAX_WAIT_MS) {
|
|
1360
|
-
await
|
|
1200
|
+
await import_promises4.default.unlink(lockPath).catch(() => {
|
|
1361
1201
|
});
|
|
1362
1202
|
continue;
|
|
1363
1203
|
}
|
|
1364
|
-
await (0,
|
|
1204
|
+
await (0, import_promises5.setTimeout)(RETRY_INTERVAL_MS);
|
|
1365
1205
|
}
|
|
1366
1206
|
}
|
|
1367
1207
|
}
|
|
1368
1208
|
async function lockFileExists(lockPath) {
|
|
1369
1209
|
try {
|
|
1370
|
-
await
|
|
1210
|
+
await import_promises4.default.stat(lockPath);
|
|
1371
1211
|
return true;
|
|
1372
1212
|
} catch (err) {
|
|
1373
1213
|
return err.code !== "ENOENT";
|
|
1374
1214
|
}
|
|
1375
1215
|
}
|
|
1376
1216
|
async function releaseLock(lockPath) {
|
|
1377
|
-
await
|
|
1217
|
+
await import_promises4.default.unlink(lockPath).catch(() => {
|
|
1378
1218
|
});
|
|
1379
1219
|
}
|
|
1380
1220
|
function isAlive(pid) {
|
|
@@ -1388,9 +1228,9 @@ function isAlive(pid) {
|
|
|
1388
1228
|
}
|
|
1389
1229
|
|
|
1390
1230
|
// src/logger.ts
|
|
1391
|
-
var
|
|
1392
|
-
var
|
|
1393
|
-
var
|
|
1231
|
+
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
1232
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
1233
|
+
var import_node_crypto3 = require("crypto");
|
|
1394
1234
|
var import_gg_ai2 = require("@kenkaiiii/gg-ai");
|
|
1395
1235
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
1396
1236
|
var MIN_HEADROOM_BYTES = 64 * 1024;
|
|
@@ -1406,14 +1246,14 @@ var cleanups = [];
|
|
|
1406
1246
|
var exactSecrets = [];
|
|
1407
1247
|
function rotateIfNeeded(filePath, force = false) {
|
|
1408
1248
|
try {
|
|
1409
|
-
const st =
|
|
1249
|
+
const st = import_node_fs3.default.statSync(filePath);
|
|
1410
1250
|
if (!force && st.size <= MAX_BYTES - MIN_HEADROOM_BYTES) return;
|
|
1411
1251
|
const rotated = `${filePath}.1`;
|
|
1412
1252
|
try {
|
|
1413
|
-
|
|
1253
|
+
import_node_fs3.default.unlinkSync(rotated);
|
|
1414
1254
|
} catch {
|
|
1415
1255
|
}
|
|
1416
|
-
|
|
1256
|
+
import_node_fs3.default.renameSync(filePath, rotated);
|
|
1417
1257
|
} catch {
|
|
1418
1258
|
}
|
|
1419
1259
|
}
|
|
@@ -1421,18 +1261,18 @@ function rotateOpenLog() {
|
|
|
1421
1261
|
if (fd === null || logPath === null) return false;
|
|
1422
1262
|
const sizeBefore = fileBytes;
|
|
1423
1263
|
try {
|
|
1424
|
-
|
|
1264
|
+
import_node_fs3.default.closeSync(fd);
|
|
1425
1265
|
} catch {
|
|
1426
1266
|
}
|
|
1427
1267
|
fd = null;
|
|
1428
1268
|
rotateIfNeeded(logPath, true);
|
|
1429
1269
|
try {
|
|
1430
|
-
fd =
|
|
1431
|
-
fileBytes =
|
|
1270
|
+
fd = import_node_fs3.default.openSync(logPath, "a");
|
|
1271
|
+
fileBytes = import_node_fs3.default.fstatSync(fd).size;
|
|
1432
1272
|
} catch {
|
|
1433
1273
|
if (fd !== null) {
|
|
1434
1274
|
try {
|
|
1435
|
-
|
|
1275
|
+
import_node_fs3.default.closeSync(fd);
|
|
1436
1276
|
} catch {
|
|
1437
1277
|
}
|
|
1438
1278
|
}
|
|
@@ -1448,22 +1288,22 @@ function openLog(filePath, name) {
|
|
|
1448
1288
|
appName = name;
|
|
1449
1289
|
exactSecrets = (0, import_gg_ai2.environmentSecrets)(process.env);
|
|
1450
1290
|
try {
|
|
1451
|
-
|
|
1291
|
+
import_node_fs3.default.mkdirSync(import_node_path4.default.dirname(filePath), { recursive: true, mode: 448 });
|
|
1452
1292
|
} catch {
|
|
1453
1293
|
}
|
|
1454
1294
|
rotateIfNeeded(filePath);
|
|
1455
1295
|
try {
|
|
1456
|
-
fd =
|
|
1296
|
+
fd = import_node_fs3.default.openSync(filePath, "a");
|
|
1457
1297
|
} catch {
|
|
1458
1298
|
fd = null;
|
|
1459
1299
|
fileBytes = 0;
|
|
1460
1300
|
return false;
|
|
1461
1301
|
}
|
|
1462
1302
|
try {
|
|
1463
|
-
fileBytes =
|
|
1303
|
+
fileBytes = import_node_fs3.default.fstatSync(fd).size;
|
|
1464
1304
|
} catch {
|
|
1465
1305
|
try {
|
|
1466
|
-
|
|
1306
|
+
import_node_fs3.default.closeSync(fd);
|
|
1467
1307
|
} catch {
|
|
1468
1308
|
}
|
|
1469
1309
|
fd = null;
|
|
@@ -1473,10 +1313,10 @@ function openLog(filePath, name) {
|
|
|
1473
1313
|
logPath = filePath;
|
|
1474
1314
|
processBytes = 0;
|
|
1475
1315
|
rotationFailed = false;
|
|
1476
|
-
sessionId = (0,
|
|
1316
|
+
sessionId = (0, import_node_crypto3.randomBytes)(4).toString("hex");
|
|
1477
1317
|
try {
|
|
1478
1318
|
if (fileBytes < MAX_BYTES) {
|
|
1479
|
-
const written =
|
|
1319
|
+
const written = import_node_fs3.default.writeSync(fd, "\n");
|
|
1480
1320
|
fileBytes += written;
|
|
1481
1321
|
processBytes += written;
|
|
1482
1322
|
}
|
|
@@ -1511,9 +1351,9 @@ function log(level, category, message, data) {
|
|
|
1511
1351
|
`;
|
|
1512
1352
|
try {
|
|
1513
1353
|
if (fileBytes + Buffer.byteLength(capLine) <= MAX_BYTES) {
|
|
1514
|
-
fileBytes +=
|
|
1354
|
+
fileBytes += import_node_fs3.default.writeSync(fd, capLine);
|
|
1515
1355
|
}
|
|
1516
|
-
|
|
1356
|
+
import_node_fs3.default.closeSync(fd);
|
|
1517
1357
|
} catch {
|
|
1518
1358
|
}
|
|
1519
1359
|
fd = null;
|
|
@@ -1523,7 +1363,7 @@ function log(level, category, message, data) {
|
|
|
1523
1363
|
if (fileBytes + lineBytes > MAX_BYTES && !rotationFailed && !rotateOpenLog()) return;
|
|
1524
1364
|
if (fd === null) return;
|
|
1525
1365
|
try {
|
|
1526
|
-
const written =
|
|
1366
|
+
const written = import_node_fs3.default.writeSync(fd, line);
|
|
1527
1367
|
fileBytes += written;
|
|
1528
1368
|
processBytes += written;
|
|
1529
1369
|
} catch {
|
|
@@ -1536,7 +1376,7 @@ function closeLogger(opts) {
|
|
|
1536
1376
|
if (fd !== null) {
|
|
1537
1377
|
if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
|
|
1538
1378
|
try {
|
|
1539
|
-
if (fd !== null)
|
|
1379
|
+
if (fd !== null) import_node_fs3.default.closeSync(fd);
|
|
1540
1380
|
} catch {
|
|
1541
1381
|
}
|
|
1542
1382
|
}
|
|
@@ -1552,7 +1392,7 @@ function closeLogger(opts) {
|
|
|
1552
1392
|
}
|
|
1553
1393
|
|
|
1554
1394
|
// src/hidden-providers.ts
|
|
1555
|
-
var HIDDEN_PROVIDERS = [
|
|
1395
|
+
var HIDDEN_PROVIDERS = [];
|
|
1556
1396
|
function isHiddenProvider(provider) {
|
|
1557
1397
|
return HIDDEN_PROVIDERS.includes(provider);
|
|
1558
1398
|
}
|
|
@@ -1643,22 +1483,76 @@ async function detectXiaomiTokenPlanBaseUrl(apiKey, options = {}) {
|
|
|
1643
1483
|
}
|
|
1644
1484
|
|
|
1645
1485
|
// src/auth-storage.ts
|
|
1646
|
-
var
|
|
1647
|
-
var
|
|
1648
|
-
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
|
+
},
|
|
1649
1499
|
{
|
|
1650
1500
|
provider: "moonshot",
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
apiKeyLabel: "Moonshot API key",
|
|
1654
|
-
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."
|
|
1655
1503
|
},
|
|
1656
1504
|
{
|
|
1657
1505
|
provider: "xai",
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
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.'
|
|
1662
1556
|
}
|
|
1663
1557
|
];
|
|
1664
1558
|
function dualAuthProvider(provider) {
|
|
@@ -1683,14 +1577,20 @@ function activeBaseUrlEntry(data, provider) {
|
|
|
1683
1577
|
const dual = dualAuthProvider(provider);
|
|
1684
1578
|
if (dual) {
|
|
1685
1579
|
const oauth = data[dual.oauthKey];
|
|
1580
|
+
const apiKey = usableEntry(data, dual.provider);
|
|
1686
1581
|
if (oauth) {
|
|
1687
1582
|
const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
|
|
1688
|
-
if (Date.now() < exhaustedUntil &&
|
|
1583
|
+
if (Date.now() < exhaustedUntil && apiKey) return apiKey;
|
|
1689
1584
|
return oauth;
|
|
1690
1585
|
}
|
|
1691
|
-
return
|
|
1586
|
+
return apiKey;
|
|
1692
1587
|
}
|
|
1693
|
-
|
|
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 };
|
|
1592
|
+
}
|
|
1593
|
+
return entry;
|
|
1694
1594
|
}
|
|
1695
1595
|
var MINIMAX_REGION_RETRY_MS = 5 * 60 * 1e3;
|
|
1696
1596
|
var miniMaxRegionProbes = /* @__PURE__ */ new Map();
|
|
@@ -1701,9 +1601,17 @@ function cachedMiniMaxRegion(apiKey) {
|
|
|
1701
1601
|
miniMaxRegionProbes.set(apiKey, { at: Date.now(), result });
|
|
1702
1602
|
return result;
|
|
1703
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
|
+
}
|
|
1704
1612
|
function readStoredBaseUrlSync(authFile, provider) {
|
|
1705
1613
|
try {
|
|
1706
|
-
const data = JSON.parse((0,
|
|
1614
|
+
const data = JSON.parse((0, import_node_fs4.readFileSync)(authFile, "utf-8"));
|
|
1707
1615
|
return activeBaseUrlEntry(data, provider)?.baseUrl;
|
|
1708
1616
|
} catch {
|
|
1709
1617
|
return void 0;
|
|
@@ -1716,19 +1624,7 @@ function refreshThresholdMs(creds) {
|
|
|
1716
1624
|
return Math.max(MIN_REFRESH_THRESHOLD_MS, lifetimeMs * REFRESH_THRESHOLD_RATIO);
|
|
1717
1625
|
}
|
|
1718
1626
|
var USAGE_EXHAUSTED_DEFAULT_MS = 15 * 60 * 1e3;
|
|
1719
|
-
var
|
|
1720
|
-
"glm",
|
|
1721
|
-
"moonshot",
|
|
1722
|
-
"xiaomi",
|
|
1723
|
-
"minimax",
|
|
1724
|
-
"deepseek",
|
|
1725
|
-
"openrouter",
|
|
1726
|
-
"huggingface",
|
|
1727
|
-
"sakana",
|
|
1728
|
-
"xai",
|
|
1729
|
-
// Local endpoints: a fixed (usually placeholder) key, never refreshable.
|
|
1730
|
-
"local"
|
|
1731
|
-
]);
|
|
1627
|
+
var REFRESHABLE_STORAGE_KEYS = /* @__PURE__ */ new Set([OPENAI_CHATGPT_KEY]);
|
|
1732
1628
|
var AuthStorage = class {
|
|
1733
1629
|
data = {};
|
|
1734
1630
|
filePath;
|
|
@@ -1749,10 +1645,12 @@ var AuthStorage = class {
|
|
|
1749
1645
|
refreshLocks = /* @__PURE__ */ new Map();
|
|
1750
1646
|
detectMiniMaxRegion;
|
|
1751
1647
|
detectXiaomiCluster;
|
|
1648
|
+
detectKimiRegion;
|
|
1752
1649
|
constructor(filePath, options) {
|
|
1753
1650
|
this.filePath = filePath ?? getAppPaths().authFile;
|
|
1754
1651
|
this.detectMiniMaxRegion = options?.detectMiniMaxBaseUrl ?? cachedMiniMaxRegion;
|
|
1755
1652
|
this.detectXiaomiCluster = options?.detectXiaomiTokenPlanBaseUrl ?? detectXiaomiTokenPlanBaseUrl;
|
|
1653
|
+
this.detectKimiRegion = options?.detectKimiCodeBaseUrl ?? cachedKimiRegion;
|
|
1756
1654
|
}
|
|
1757
1655
|
/** Path to the on-disk auth file. Useful for status output. */
|
|
1758
1656
|
get path() {
|
|
@@ -1780,27 +1678,78 @@ var AuthStorage = class {
|
|
|
1780
1678
|
return keys.find((key) => Boolean(this.data[key]));
|
|
1781
1679
|
}
|
|
1782
1680
|
/**
|
|
1783
|
-
* True if the user has any usable auth for the logical provider
|
|
1784
|
-
*
|
|
1785
|
-
* 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.
|
|
1786
1683
|
*/
|
|
1787
1684
|
async hasProviderAuth(provider) {
|
|
1788
1685
|
if (isHiddenProvider(provider)) return false;
|
|
1789
1686
|
await this.ensureFresh();
|
|
1790
1687
|
const dual = dualAuthProvider(provider);
|
|
1791
1688
|
if (dual) {
|
|
1792
|
-
return Boolean(this.data[dual.oauthKey] || this.data
|
|
1689
|
+
return Boolean(this.data[dual.oauthKey] || usableEntry(this.data, dual.provider));
|
|
1793
1690
|
}
|
|
1794
|
-
if (provider === "
|
|
1795
|
-
return Boolean(this.data
|
|
1796
|
-
}
|
|
1797
|
-
if (provider === "gemini") {
|
|
1798
|
-
return Boolean(this.data["gemini"] || this.data[GEMINI_API_KEY]);
|
|
1691
|
+
if (provider === "anthropic") {
|
|
1692
|
+
return Boolean(this.data.anthropic) && !isLegacyClaudeOAuth(this.data.anthropic) || isClaudeCodeAuthMarker(this.data[CLAUDE_CODE_AUTH_KEY]);
|
|
1799
1693
|
}
|
|
1800
1694
|
if (provider === "local") {
|
|
1801
1695
|
return Object.keys(this.data).some((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX));
|
|
1802
1696
|
}
|
|
1803
|
-
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);
|
|
1716
|
+
}
|
|
1717
|
+
/** True when the user signed in with Claude through Claude Code (marker only, no token). */
|
|
1718
|
+
async hasClaudeCodeLogin() {
|
|
1719
|
+
await this.ensureFresh();
|
|
1720
|
+
return isClaudeCodeAuthMarker(this.data[CLAUDE_CODE_AUTH_KEY]);
|
|
1721
|
+
}
|
|
1722
|
+
/** Record a Claude Code sign-in. Claude Code keeps the token; auth.json gets only the marker. */
|
|
1723
|
+
async setClaudeCodeLogin() {
|
|
1724
|
+
await this.mutateLatest((latest) => {
|
|
1725
|
+
latest[CLAUDE_CODE_AUTH_KEY] = { ...CLAUDE_CODE_AUTH_MARKER };
|
|
1726
|
+
if (isLegacyClaudeOAuth(latest.anthropic)) delete latest.anthropic;
|
|
1727
|
+
});
|
|
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
|
+
}
|
|
1734
|
+
/**
|
|
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.
|
|
1737
|
+
*/
|
|
1738
|
+
async takeLegacySignIn(provider) {
|
|
1739
|
+
const retired = RETIRED_SIGN_INS.find((r) => r.provider === provider);
|
|
1740
|
+
if (!retired) return false;
|
|
1741
|
+
await this.ensureFresh();
|
|
1742
|
+
if (!isRetiredEntry(retired, this.data[retired.storageKey])) return false;
|
|
1743
|
+
let removed = false;
|
|
1744
|
+
await this.mutateLatest((latest) => {
|
|
1745
|
+
if (!isRetiredEntry(retired, latest[retired.storageKey])) return;
|
|
1746
|
+
delete latest[retired.storageKey];
|
|
1747
|
+
removed = true;
|
|
1748
|
+
});
|
|
1749
|
+
return removed;
|
|
1750
|
+
}
|
|
1751
|
+
async clearClaudeCodeLogin() {
|
|
1752
|
+
await this.clearCredentials(CLAUDE_CODE_AUTH_KEY);
|
|
1804
1753
|
}
|
|
1805
1754
|
/** Endpoint ids that currently have a `local:<id>` credential stored. */
|
|
1806
1755
|
async listLocalEndpointIds() {
|
|
@@ -1835,18 +1784,18 @@ var AuthStorage = class {
|
|
|
1835
1784
|
const oauthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
1836
1785
|
if (dual && oauthCreds) {
|
|
1837
1786
|
const exhaustedUntil = oauthCreds.usageExhaustedUntil ?? 0;
|
|
1838
|
-
const apiKeyActive = Date.now() < exhaustedUntil && Boolean(this.data
|
|
1787
|
+
const apiKeyActive = Date.now() < exhaustedUntil && Boolean(usableEntry(this.data, dual.provider));
|
|
1839
1788
|
if (!apiKeyActive) return false;
|
|
1840
1789
|
}
|
|
1841
|
-
return
|
|
1790
|
+
return !REFRESHABLE_STORAGE_KEYS.has(provider);
|
|
1842
1791
|
}
|
|
1843
1792
|
/**
|
|
1844
1793
|
* The base URL on the credential that is active right now, if any.
|
|
1845
1794
|
* Synchronous — call only after load()/resolveCredentials() populated the
|
|
1846
|
-
* snapshot. For a dual-auth provider this is the
|
|
1847
|
-
*
|
|
1848
|
-
*
|
|
1849
|
-
*
|
|
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.
|
|
1850
1799
|
*/
|
|
1851
1800
|
getStoredBaseUrl(provider) {
|
|
1852
1801
|
return activeBaseUrlEntry(this.data, provider)?.baseUrl;
|
|
@@ -1855,7 +1804,7 @@ var AuthStorage = class {
|
|
|
1855
1804
|
const first = !this.loaded;
|
|
1856
1805
|
await withFileLock(this.filePath, async () => {
|
|
1857
1806
|
try {
|
|
1858
|
-
const content = await
|
|
1807
|
+
const content = await import_promises6.default.readFile(this.filePath, "utf-8");
|
|
1859
1808
|
this.data = JSON.parse(content);
|
|
1860
1809
|
if (first) {
|
|
1861
1810
|
log("INFO", "auth", `Loaded credentials from ${this.filePath}`, {
|
|
@@ -1901,7 +1850,7 @@ var AuthStorage = class {
|
|
|
1901
1850
|
}
|
|
1902
1851
|
let changed;
|
|
1903
1852
|
try {
|
|
1904
|
-
const stat = await
|
|
1853
|
+
const stat = await import_promises6.default.stat(this.filePath);
|
|
1905
1854
|
changed = stat.ino !== this.snapshotIno || stat.mtimeMs !== this.snapshotMtimeMs || stat.size !== this.snapshotSize;
|
|
1906
1855
|
} catch {
|
|
1907
1856
|
changed = this.snapshotSize !== -1;
|
|
@@ -1914,7 +1863,7 @@ var AuthStorage = class {
|
|
|
1914
1863
|
*/
|
|
1915
1864
|
async rememberSnapshot() {
|
|
1916
1865
|
try {
|
|
1917
|
-
const stat = await
|
|
1866
|
+
const stat = await import_promises6.default.stat(this.filePath);
|
|
1918
1867
|
this.snapshotMtimeMs = stat.mtimeMs;
|
|
1919
1868
|
this.snapshotSize = stat.size;
|
|
1920
1869
|
this.snapshotIno = stat.ino;
|
|
@@ -1969,9 +1918,34 @@ var AuthStorage = class {
|
|
|
1969
1918
|
});
|
|
1970
1919
|
return { ...creds, baseUrl };
|
|
1971
1920
|
}
|
|
1972
|
-
|
|
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);
|
|
1973
1943
|
await this.mutateLatest((latest) => {
|
|
1974
|
-
|
|
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;
|
|
1975
1949
|
});
|
|
1976
1950
|
}
|
|
1977
1951
|
/**
|
|
@@ -2008,9 +1982,9 @@ var AuthStorage = class {
|
|
|
2008
1982
|
* Mark the credential stored under `storageKey` as usage-exhausted until
|
|
2009
1983
|
* `resetsAt` (unix SECONDS, from the provider's rate-limit response) or a
|
|
2010
1984
|
* 15-minute default when no reset time is known. While the mark is in the
|
|
2011
|
-
* future, `resolveCredentials("
|
|
2012
|
-
*
|
|
2013
|
-
*
|
|
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
|
|
2014
1988
|
* lapses. Persisted to auth.json so a restart (or another gg-app window)
|
|
2015
1989
|
* doesn't burn a request rediscovering the same exhausted window. No-op if
|
|
2016
1990
|
* nothing is stored under `storageKey`.
|
|
@@ -2055,12 +2029,12 @@ var AuthStorage = class {
|
|
|
2055
2029
|
await this.ensureFresh();
|
|
2056
2030
|
}
|
|
2057
2031
|
const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : providerStorageKeys(provider);
|
|
2058
|
-
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])) {
|
|
2059
2033
|
await this.reloadLatest();
|
|
2060
2034
|
}
|
|
2061
2035
|
if (opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider)) {
|
|
2062
2036
|
for (const key of opts.storageKeys) {
|
|
2063
|
-
const creds2 = this.data
|
|
2037
|
+
const creds2 = usableEntry(this.data, key);
|
|
2064
2038
|
if (!creds2) continue;
|
|
2065
2039
|
if (key !== provider && dualAuthProviderByOAuthKey(key)) {
|
|
2066
2040
|
return await this.resolveCredentials(key, {
|
|
@@ -2074,21 +2048,29 @@ var AuthStorage = class {
|
|
|
2074
2048
|
...opts.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
|
|
2075
2049
|
});
|
|
2076
2050
|
}
|
|
2051
|
+
if (key === KIMI_CODE_KEY && !creds2.baseUrl) return await this.withKimiRegion(creds2);
|
|
2077
2052
|
return creds2;
|
|
2078
2053
|
}
|
|
2079
2054
|
throw new NotLoggedInError(provider);
|
|
2080
2055
|
}
|
|
2056
|
+
if (provider === "anthropic" && (!this.data.anthropic || isLegacyClaudeOAuth(this.data.anthropic))) {
|
|
2057
|
+
if (isClaudeCodeAuthMarker(this.data[CLAUDE_CODE_AUTH_KEY])) {
|
|
2058
|
+
return { accessToken: "", refreshToken: "", expiresAt: Number.MAX_SAFE_INTEGER };
|
|
2059
|
+
}
|
|
2060
|
+
if (this.data.anthropic) throw new NotLoggedInError(provider);
|
|
2061
|
+
}
|
|
2081
2062
|
const dual = dualAuthProvider(provider);
|
|
2082
2063
|
const dualOAuthCreds = dual ? this.data[dual.oauthKey] : void 0;
|
|
2064
|
+
const dualApiKey = dual ? usableEntry(this.data, dual.provider) : void 0;
|
|
2083
2065
|
if (dual && dualOAuthCreds) {
|
|
2084
2066
|
const exhaustedUntil = dualOAuthCreds.usageExhaustedUntil ?? 0;
|
|
2085
|
-
if (Date.now() < exhaustedUntil &&
|
|
2067
|
+
if (Date.now() < exhaustedUntil && dualApiKey) {
|
|
2086
2068
|
log(
|
|
2087
2069
|
"WARN",
|
|
2088
2070
|
"auth",
|
|
2089
2071
|
`${dual.oauthLabel} usage window is exhausted \u2014 using the ${dual.apiKeyLabel} until ${new Date(exhaustedUntil).toISOString()} (OAuth resumes automatically).`
|
|
2090
2072
|
);
|
|
2091
|
-
return
|
|
2073
|
+
return dualApiKey;
|
|
2092
2074
|
}
|
|
2093
2075
|
try {
|
|
2094
2076
|
return await this.resolveCredentials(dual.oauthKey, {
|
|
@@ -2096,23 +2078,24 @@ var AuthStorage = class {
|
|
|
2096
2078
|
...opts?.rejectedToken !== void 0 ? { rejectedToken: opts.rejectedToken } : {}
|
|
2097
2079
|
});
|
|
2098
2080
|
} catch (err) {
|
|
2099
|
-
if (err instanceof NotLoggedInError &&
|
|
2081
|
+
if (err instanceof NotLoggedInError && dualApiKey) {
|
|
2100
2082
|
log(
|
|
2101
2083
|
"WARN",
|
|
2102
2084
|
"auth",
|
|
2103
2085
|
`${dual.oauthLabel} credential is no longer valid \u2014 falling back to the ${dual.apiKeyLabel}. ${dual.restoreHint}`
|
|
2104
2086
|
);
|
|
2105
|
-
return
|
|
2087
|
+
return dualApiKey;
|
|
2106
2088
|
}
|
|
2107
2089
|
throw err;
|
|
2108
2090
|
}
|
|
2109
2091
|
}
|
|
2110
|
-
const creds = this.data
|
|
2092
|
+
const creds = usableEntry(this.data, provider);
|
|
2111
2093
|
if (!creds) {
|
|
2112
2094
|
throw new NotLoggedInError(provider);
|
|
2113
2095
|
}
|
|
2114
|
-
if (
|
|
2096
|
+
if (!REFRESHABLE_STORAGE_KEYS.has(provider)) {
|
|
2115
2097
|
if (provider === "minimax" && !creds.baseUrl) return await this.withMiniMaxRegion(creds);
|
|
2098
|
+
if (provider === KIMI_CODE_KEY && !creds.baseUrl) return await this.withKimiRegion(creds);
|
|
2116
2099
|
return creds;
|
|
2117
2100
|
}
|
|
2118
2101
|
if (!opts?.forceRefresh && Date.now() < creds.expiresAt - refreshThresholdMs(creds)) {
|
|
@@ -2140,13 +2123,14 @@ var AuthStorage = class {
|
|
|
2140
2123
|
this.data = latest;
|
|
2141
2124
|
return latestCreds;
|
|
2142
2125
|
}
|
|
2143
|
-
const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : provider === XAI_OAUTH_KEY ? refreshXaiToken : refreshOpenAIToken;
|
|
2144
2126
|
let refreshed;
|
|
2145
2127
|
try {
|
|
2146
|
-
refreshed = await
|
|
2128
|
+
refreshed = await refreshChatGPTToken(latestCreds);
|
|
2147
2129
|
} catch (err) {
|
|
2148
2130
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2149
|
-
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);
|
|
2150
2134
|
if (isAuthFailure) {
|
|
2151
2135
|
const current = await readAuthData(this.filePath);
|
|
2152
2136
|
const currentCreds = current[provider];
|
|
@@ -2166,15 +2150,10 @@ var AuthStorage = class {
|
|
|
2166
2150
|
}
|
|
2167
2151
|
throw err;
|
|
2168
2152
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
refreshed.projectId = latestCreds.projectId;
|
|
2174
|
-
}
|
|
2175
|
-
if (!refreshed.baseUrl && latestCreds.baseUrl) {
|
|
2176
|
-
refreshed.baseUrl = latestCreds.baseUrl;
|
|
2177
|
-
}
|
|
2153
|
+
refreshed = {
|
|
2154
|
+
...pick(latestCreds, ["accountId", "baseUrl", "clientId", "idToken", "scopes"]),
|
|
2155
|
+
...refreshed
|
|
2156
|
+
};
|
|
2178
2157
|
latest[provider] = refreshed;
|
|
2179
2158
|
this.data = latest;
|
|
2180
2159
|
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
@@ -2197,9 +2176,14 @@ var AuthStorage = class {
|
|
|
2197
2176
|
return creds.accessToken;
|
|
2198
2177
|
}
|
|
2199
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
|
+
}
|
|
2200
2184
|
async function readAuthData(filePath) {
|
|
2201
2185
|
try {
|
|
2202
|
-
const content = await
|
|
2186
|
+
const content = await import_promises6.default.readFile(filePath, "utf-8");
|
|
2203
2187
|
return JSON.parse(content);
|
|
2204
2188
|
} catch (error) {
|
|
2205
2189
|
if (error.code === "ENOENT") return {};
|
|
@@ -2207,12 +2191,12 @@ async function readAuthData(filePath) {
|
|
|
2207
2191
|
}
|
|
2208
2192
|
}
|
|
2209
2193
|
async function atomicWriteFile(filePath, content) {
|
|
2210
|
-
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${
|
|
2194
|
+
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${import_node_crypto4.default.randomUUID().slice(0, 8)}.tmp`;
|
|
2211
2195
|
try {
|
|
2212
|
-
await
|
|
2213
|
-
await
|
|
2196
|
+
await import_promises6.default.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
|
|
2197
|
+
await import_promises6.default.rename(tmpPath, filePath);
|
|
2214
2198
|
} catch (err) {
|
|
2215
|
-
await
|
|
2199
|
+
await import_promises6.default.unlink(tmpPath).catch(() => {
|
|
2216
2200
|
});
|
|
2217
2201
|
throw err;
|
|
2218
2202
|
}
|
|
@@ -2244,7 +2228,8 @@ var CODEX_FAST_BILLING = {
|
|
|
2244
2228
|
detail: "Codex Fast mode uses your ChatGPT plan limits at 2.5x the standard rate (purchased credits at 2x).",
|
|
2245
2229
|
paid: false
|
|
2246
2230
|
};
|
|
2247
|
-
var GEMINI_AUTH_KEYS = [GEMINI_API_KEY
|
|
2231
|
+
var GEMINI_AUTH_KEYS = [GEMINI_API_KEY];
|
|
2232
|
+
var KIMI_AUTH_KEYS = [KIMI_CODE_KEY, "moonshot"];
|
|
2248
2233
|
var MODELS = [
|
|
2249
2234
|
// ── Anthropic ──────────────────────────────────────────
|
|
2250
2235
|
// NOTE: Claude Mythos 5.1 (`claude-mythos-5-1`, 2026-09-01) is kept commented
|
|
@@ -2374,14 +2359,13 @@ var MODELS = [
|
|
|
2374
2359
|
{
|
|
2375
2360
|
// GPT-6 Astra — "Our most capable model for complex, demanding work."
|
|
2376
2361
|
// (Codex catalog priority 2, listed for every ChatGPT plan, requires a
|
|
2377
|
-
// Codex client >= 0.153.0
|
|
2378
|
-
// 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
|
|
2379
2364
|
// (openai/codex models.json, `gpt-6-astra`). Reasoning ladder low → medium
|
|
2380
2365
|
// → high → xhigh → max → ultra; `ultra` is the Codex orchestration preset
|
|
2381
2366
|
// (multi_agent v2) and is Codex-only — the public API tops out at `max`.
|
|
2382
|
-
//
|
|
2383
|
-
//
|
|
2384
|
-
// 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.
|
|
2385
2369
|
id: "gpt-6-astra",
|
|
2386
2370
|
name: "GPT-6 Astra",
|
|
2387
2371
|
provider: "openai",
|
|
@@ -2415,18 +2399,16 @@ var MODELS = [
|
|
|
2415
2399
|
billing: CODEX_FAST_BILLING
|
|
2416
2400
|
},
|
|
2417
2401
|
// GPT-6 Sol + Luna — released 2026-09-22 below Astra, replacing the whole
|
|
2418
|
-
// GPT-5.6 family (Sol/Terra/Luna; there
|
|
2402
|
+
// GPT-5.6 family (Sol/Terra/Luna; there was no GPT-6 Terra — OpenAI's Codex
|
|
2419
2403
|
// catalog upgrades 5.6 Terra to 6 Sol). GPT-6.1 Sol (2026-09-29) then replaced
|
|
2420
|
-
// GPT-6 Sol. Same window split as Astra: 1.05M on the public Responses API,
|
|
2404
|
+
// GPT-6 Sol, and GPT-6.1 Terra (2026-10-26) sits between Sol and Luna. Same window split as Astra: 1.05M on the public Responses API,
|
|
2421
2405
|
// 272K on the Codex route; 128K output, text+image input, freeform
|
|
2422
2406
|
// apply_patch, responses-lite transport. The GPT-5.6 ids and `gpt-6-sol` are
|
|
2423
2407
|
// retired — a saved session on one falls back to the provider default on
|
|
2424
2408
|
// next start.
|
|
2425
2409
|
{
|
|
2426
2410
|
// GPT-6.1 Sol — "Latest workhorse model for coding and everyday work."
|
|
2427
|
-
// (Codex priority 1, default low).
|
|
2428
|
-
// but the ChatGPT backend only serves it from 0.159.0 (see
|
|
2429
|
-
// 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 →
|
|
2430
2412
|
// medium → high → xhigh → max → ultra; ultra is the Codex orchestration
|
|
2431
2413
|
// preset (xhigh effort on the wire + proactive local subagent delegation).
|
|
2432
2414
|
id: "gpt-6.1-sol",
|
|
@@ -2459,6 +2441,40 @@ var MODELS = [
|
|
|
2459
2441
|
maxThinkingLevel: "ultra",
|
|
2460
2442
|
billing: CODEX_FAST_BILLING
|
|
2461
2443
|
},
|
|
2444
|
+
{
|
|
2445
|
+
// GPT-6.1 Terra (Codex priority 7, default low, client >= 0.153.0). $1/$5
|
|
2446
|
+
// MTok, cached input $0.05. Unlike Sol/Astra the Codex route gets a 500K
|
|
2447
|
+
// window (catalog `context_window`). Same ladder and Ultra preset as Sol.
|
|
2448
|
+
id: "gpt-6.1-terra",
|
|
2449
|
+
name: "GPT-6.1 Terra",
|
|
2450
|
+
provider: "openai",
|
|
2451
|
+
contextWindow: 105e4,
|
|
2452
|
+
codexContextWindow: 5e5,
|
|
2453
|
+
maxOutputTokens: 128e3,
|
|
2454
|
+
supportsThinking: true,
|
|
2455
|
+
defaultThinkingLevel: "low",
|
|
2456
|
+
supportsImages: true,
|
|
2457
|
+
supportsVideo: false,
|
|
2458
|
+
costTier: "medium",
|
|
2459
|
+
maxThinkingLevel: "ultra"
|
|
2460
|
+
},
|
|
2461
|
+
{
|
|
2462
|
+
// Codex Fast mode for 6.1 Terra ("1.5x speed") — see `gpt-6-astra-fast`
|
|
2463
|
+
// for the wire mapping and billing.
|
|
2464
|
+
id: "gpt-6.1-terra-fast",
|
|
2465
|
+
name: "GPT-6.1 Terra Fast",
|
|
2466
|
+
provider: "openai",
|
|
2467
|
+
contextWindow: 105e4,
|
|
2468
|
+
codexContextWindow: 5e5,
|
|
2469
|
+
maxOutputTokens: 128e3,
|
|
2470
|
+
supportsThinking: true,
|
|
2471
|
+
defaultThinkingLevel: "low",
|
|
2472
|
+
supportsImages: true,
|
|
2473
|
+
supportsVideo: false,
|
|
2474
|
+
costTier: "medium",
|
|
2475
|
+
maxThinkingLevel: "ultra",
|
|
2476
|
+
billing: CODEX_FAST_BILLING
|
|
2477
|
+
},
|
|
2462
2478
|
{
|
|
2463
2479
|
// Luna — "Fast and affordable model for easier tasks." (Codex priority 4,
|
|
2464
2480
|
// default medium, needs a Codex client >= 0.155.0). $0.10/$0.50 MTok.
|
|
@@ -2579,8 +2595,8 @@ var MODELS = [
|
|
|
2579
2595
|
maxThinkingLevel: "high",
|
|
2580
2596
|
authStorageKeys: GEMINI_AUTH_KEYS
|
|
2581
2597
|
},
|
|
2582
|
-
// Keep 3.1 Flash Lite first for the
|
|
2583
|
-
//
|
|
2598
|
+
// Keep 3.1 Flash Lite first for the default and fast-model routing.
|
|
2599
|
+
// Gemini 3.7
|
|
2584
2600
|
// Flash is retired: Google deprecated it on 2026-10-08 and routes every
|
|
2585
2601
|
// `gemini-3.7-flash` request to 3.8 Flash (ai.google.dev/gemini-api/docs/
|
|
2586
2602
|
// changelog), so a saved session on it falls back to the provider default.
|
|
@@ -2613,9 +2629,7 @@ var MODELS = [
|
|
|
2613
2629
|
authStorageKeys: GEMINI_AUTH_KEYS
|
|
2614
2630
|
},
|
|
2615
2631
|
{
|
|
2616
|
-
// Wire name `gemini-3-flash
|
|
2617
|
-
// display string `gemini-3.5-flash` with a 404, so gemini-cli keeps this
|
|
2618
|
-
// 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.
|
|
2619
2633
|
id: "gemini-3-flash",
|
|
2620
2634
|
name: "Gemini 3.5 Flash",
|
|
2621
2635
|
provider: "gemini",
|
|
@@ -2630,9 +2644,7 @@ var MODELS = [
|
|
|
2630
2644
|
authStorageKeys: GEMINI_AUTH_KEYS
|
|
2631
2645
|
},
|
|
2632
2646
|
{
|
|
2633
|
-
// Gemini 3.1 Pro is public preview
|
|
2634
|
-
// enablement, so free/personal OAuth accounts 404 on it (see
|
|
2635
|
-
// ACCOUNT_GATED_MODELS in gg-ai's gemini provider).
|
|
2647
|
+
// Gemini 3.1 Pro is public preview.
|
|
2636
2648
|
id: "gemini-3.1-pro-preview",
|
|
2637
2649
|
name: "Gemini 3.1 Pro (Preview)",
|
|
2638
2650
|
provider: "gemini",
|
|
@@ -2649,8 +2661,8 @@ var MODELS = [
|
|
|
2649
2661
|
// ── Moonshot (Kimi) ────────────────────────────────────
|
|
2650
2662
|
// K3 is Kimi's 2.8T-parameter flagship for long-horizon coding, knowledge
|
|
2651
2663
|
// work, and deep reasoning. Its effort ladder is server-declared as
|
|
2652
|
-
// low/high/max on both the public API (default max) and the Kimi
|
|
2653
|
-
//
|
|
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.
|
|
2654
2666
|
{
|
|
2655
2667
|
id: "kimi-k3",
|
|
2656
2668
|
name: "Kimi K3",
|
|
@@ -2664,13 +2676,14 @@ var MODELS = [
|
|
|
2664
2676
|
supportsVideo: true,
|
|
2665
2677
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
2666
2678
|
costTier: "high",
|
|
2667
|
-
maxThinkingLevel: "max"
|
|
2679
|
+
maxThinkingLevel: "max",
|
|
2680
|
+
authStorageKeys: KIMI_AUTH_KEYS
|
|
2668
2681
|
},
|
|
2669
|
-
// K2.8 Preview (2026-09-11) is served only on the Kimi
|
|
2670
|
-
//
|
|
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:
|
|
2671
2684
|
// display_name "K2.8 Preview", 1M context, image + video input, efforts
|
|
2672
|
-
// low/high/max default max). The public API
|
|
2673
|
-
//
|
|
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.
|
|
2674
2687
|
{
|
|
2675
2688
|
id: "kimi-for-coding",
|
|
2676
2689
|
name: "Kimi K2.8 Preview",
|
|
@@ -2683,7 +2696,7 @@ var MODELS = [
|
|
|
2683
2696
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
2684
2697
|
costTier: "medium",
|
|
2685
2698
|
maxThinkingLevel: "max",
|
|
2686
|
-
authStorageKeys: [
|
|
2699
|
+
authStorageKeys: [KIMI_CODE_KEY]
|
|
2687
2700
|
},
|
|
2688
2701
|
// K2.7 Code is requested by its pinned id (not the `kimi-for-coding` alias
|
|
2689
2702
|
// that moved to K2.8), so it stays the real K2.7 on both endpoints.
|
|
@@ -2698,7 +2711,8 @@ var MODELS = [
|
|
|
2698
2711
|
supportsVideo: true,
|
|
2699
2712
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
2700
2713
|
costTier: "medium",
|
|
2701
|
-
maxThinkingLevel: "high"
|
|
2714
|
+
maxThinkingLevel: "high",
|
|
2715
|
+
authStorageKeys: KIMI_AUTH_KEYS
|
|
2702
2716
|
},
|
|
2703
2717
|
// K2.7 Code HighSpeed — the same model as K2.7 Code served at ~180 tok/s
|
|
2704
2718
|
// (up to ~260 on short contexts), 256K context (platform.kimi.ai model list,
|
|
@@ -2717,7 +2731,8 @@ var MODELS = [
|
|
|
2717
2731
|
supportsVideo: true,
|
|
2718
2732
|
maxVideoBytes: 100 * 1024 * 1024,
|
|
2719
2733
|
costTier: "medium",
|
|
2720
|
-
maxThinkingLevel: "high"
|
|
2734
|
+
maxThinkingLevel: "high",
|
|
2735
|
+
authStorageKeys: KIMI_AUTH_KEYS
|
|
2721
2736
|
},
|
|
2722
2737
|
// ── Z.AI (GLM) ─────────────────────────────────────────
|
|
2723
2738
|
// Two GLM entries, both live on the coding endpoint (verified against its
|
|
@@ -3643,101 +3658,238 @@ function findProbedModel(probes, modelId) {
|
|
|
3643
3658
|
return void 0;
|
|
3644
3659
|
}
|
|
3645
3660
|
|
|
3661
|
+
// src/claude-code/usage.ts
|
|
3662
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
3663
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
3664
|
+
var USAGE_FILE = "usage.json";
|
|
3665
|
+
var WINDOWS = {
|
|
3666
|
+
five_hour: { kind: "current", label: "5-hour" },
|
|
3667
|
+
seven_day: { kind: "weekly", label: "Weekly" }
|
|
3668
|
+
};
|
|
3669
|
+
function finite(value) {
|
|
3670
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3671
|
+
}
|
|
3672
|
+
function toWindow(key, raw) {
|
|
3673
|
+
const shape = WINDOWS[key];
|
|
3674
|
+
const fraction = finite(raw?.utilization);
|
|
3675
|
+
if (!shape || fraction === void 0) return null;
|
|
3676
|
+
const resetsAt = finite(raw?.resetsAt);
|
|
3677
|
+
return {
|
|
3678
|
+
...shape,
|
|
3679
|
+
usedPercent: Math.min(100, Math.max(0, fraction * 100)),
|
|
3680
|
+
...resetsAt !== void 0 ? { resetsAt: resetsAt * 1e3 } : {}
|
|
3681
|
+
};
|
|
3682
|
+
}
|
|
3683
|
+
function mergeClaudeCodeRateLimit(info, previous, now = Date.now()) {
|
|
3684
|
+
const reported = info.unifiedWindows ? Object.entries(info.unifiedWindows).map(([key, raw]) => toWindow(key, raw)) : info.rateLimitType ? [toWindow(info.rateLimitType, info)] : [];
|
|
3685
|
+
const byKind = new Map(previous?.windows.map((w) => [w.kind, w]) ?? []);
|
|
3686
|
+
for (const window of reported) if (window) byKind.set(window.kind, window);
|
|
3687
|
+
const windows = [...byKind.values()].sort(
|
|
3688
|
+
(a, b) => a.kind === b.kind ? 0 : a.kind === "current" ? -1 : 1
|
|
3689
|
+
);
|
|
3690
|
+
return { provider: "anthropic", displayName: "Anthropic", windows, fetchedAt: now };
|
|
3691
|
+
}
|
|
3692
|
+
function usagePath(opts) {
|
|
3693
|
+
return import_node_path5.default.join(claudeCodeHome(opts), USAGE_FILE);
|
|
3694
|
+
}
|
|
3695
|
+
async function readClaudeCodeUsage(opts = {}) {
|
|
3696
|
+
let parsed;
|
|
3697
|
+
try {
|
|
3698
|
+
parsed = JSON.parse(await import_promises7.default.readFile(usagePath(opts), "utf8"));
|
|
3699
|
+
} catch {
|
|
3700
|
+
return null;
|
|
3701
|
+
}
|
|
3702
|
+
if (!Array.isArray(parsed?.windows) || typeof parsed.fetchedAt !== "number") return null;
|
|
3703
|
+
const now = opts.now ?? Date.now();
|
|
3704
|
+
const windows = parsed.windows.filter(
|
|
3705
|
+
(w) => (w.kind === "current" || w.kind === "weekly") && typeof w.usedPercent === "number" && (w.resetsAt === void 0 || w.resetsAt > now)
|
|
3706
|
+
);
|
|
3707
|
+
return { provider: "anthropic", displayName: "Anthropic", windows, fetchedAt: parsed.fetchedAt };
|
|
3708
|
+
}
|
|
3709
|
+
var writeQueue = Promise.resolve();
|
|
3710
|
+
function recordClaudeCodeRateLimit(info, opts = {}) {
|
|
3711
|
+
const write = async () => {
|
|
3712
|
+
const now = opts.now ?? Date.now();
|
|
3713
|
+
const next = mergeClaudeCodeRateLimit(info, await readClaudeCodeUsage({ ...opts, now }), now);
|
|
3714
|
+
if (next.windows.length === 0) return;
|
|
3715
|
+
const file = usagePath(opts);
|
|
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");
|
|
3718
|
+
};
|
|
3719
|
+
writeQueue = writeQueue.then(write).catch(() => {
|
|
3720
|
+
});
|
|
3721
|
+
return writeQueue;
|
|
3722
|
+
}
|
|
3723
|
+
async function clearClaudeCodeUsage(opts = {}) {
|
|
3724
|
+
await writeQueue;
|
|
3725
|
+
await import_promises7.default.rm(usagePath(opts), { force: true }).catch(() => {
|
|
3726
|
+
});
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
// src/claude-code/sign-in.ts
|
|
3730
|
+
async function signInWithClaudeCode(auth, callbacks, opts = {}) {
|
|
3731
|
+
if (!opts.binary && !resolveClaudeCodeBinary(opts)) {
|
|
3732
|
+
callbacks.onStatus("Downloading Claude Code (one time)\u2026");
|
|
3733
|
+
await ensureClaudeCodeBinary({
|
|
3734
|
+
...opts,
|
|
3735
|
+
onProgress: (progress) => {
|
|
3736
|
+
callbacks.onStatus(formatClaudeCodeInstallProgress(progress));
|
|
3737
|
+
opts.onProgress?.(progress);
|
|
3738
|
+
}
|
|
3739
|
+
});
|
|
3740
|
+
}
|
|
3741
|
+
let status = await claudeCodeAuthStatus(opts);
|
|
3742
|
+
if (!status.loggedIn) {
|
|
3743
|
+
callbacks.onStatus("Opening Claude sign-in\u2026");
|
|
3744
|
+
await loginClaudeCode(callbacks, opts);
|
|
3745
|
+
status = await claudeCodeAuthStatus(opts);
|
|
3746
|
+
}
|
|
3747
|
+
if (!status.loggedIn) throw new Error("Claude sign-in didn't finish. Try again.");
|
|
3748
|
+
await auth.setClaudeCodeLogin();
|
|
3749
|
+
return status;
|
|
3750
|
+
}
|
|
3751
|
+
async function signOutOfClaudeCode(auth, opts = {}) {
|
|
3752
|
+
await auth.clearClaudeCodeLogin();
|
|
3753
|
+
await clearClaudeCodeUsage(opts);
|
|
3754
|
+
try {
|
|
3755
|
+
await logoutClaudeCode(opts);
|
|
3756
|
+
} catch (err) {
|
|
3757
|
+
log(
|
|
3758
|
+
"WARN",
|
|
3759
|
+
"auth",
|
|
3760
|
+
`claude auth logout failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3761
|
+
);
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3646
3765
|
// src/auth-providers.ts
|
|
3766
|
+
function apiKeyDetails(label, billing, when = "") {
|
|
3767
|
+
return { apikey: { method: "apikey", label, billing, when } };
|
|
3768
|
+
}
|
|
3647
3769
|
var ALL_AUTH_PROVIDERS = [
|
|
3648
3770
|
{
|
|
3649
3771
|
value: "anthropic",
|
|
3650
3772
|
label: "Anthropic",
|
|
3651
3773
|
description: "Claude Fable 5.1, Opus 5.5, Opus 5.5 Fast, Sonnet 5.5, Haiku 5.5",
|
|
3652
|
-
methods: ["oauth"]
|
|
3653
|
-
},
|
|
3654
|
-
{
|
|
3655
|
-
value: "openai",
|
|
3656
|
-
label: "OpenAI",
|
|
3657
|
-
description: "GPT-6 Astra, GPT-6 Astra Fast, GPT-6.1 Sol, GPT-6.1 Sol Fast, GPT-6 Luna, GPT-6 Luna Fast",
|
|
3658
|
-
methods: ["oauth"]
|
|
3659
|
-
},
|
|
3660
|
-
{
|
|
3661
|
-
value: "gemini",
|
|
3662
|
-
label: "Gemini",
|
|
3663
|
-
description: "Gemini 3.8 Flash, 3.5 Flash Lite, 3.1 Flash Lite, 3.5 Flash, 3.1 Pro (Preview)",
|
|
3664
3774
|
methods: ["oauth", "apikey"],
|
|
3665
|
-
apiKeyLabel: "
|
|
3666
|
-
apiKeyBaseUrl: GEMINI_API_KEY_BASE_URL,
|
|
3667
|
-
// Stored under its own key so it never overwrites the `gemini` OAuth login.
|
|
3668
|
-
apiKeyVariants: [
|
|
3669
|
-
{ key: GEMINI_API_KEY, label: "Google AI Studio", baseUrl: GEMINI_API_KEY_BASE_URL }
|
|
3670
|
-
],
|
|
3775
|
+
apiKeyLabel: "Anthropic",
|
|
3671
3776
|
methodDetails: {
|
|
3672
3777
|
oauth: {
|
|
3673
3778
|
method: "oauth",
|
|
3674
|
-
label: "Sign in with
|
|
3675
|
-
billing: "
|
|
3676
|
-
when: "
|
|
3779
|
+
label: "Sign in with Claude",
|
|
3780
|
+
billing: "Uses your Claude Pro or Max plan.",
|
|
3781
|
+
when: "Runs through Anthropic's Claude Code, which GG Coder downloads once (about 140 MB) the first time you pick this.",
|
|
3782
|
+
requires: "A Claude Pro or Max subscription."
|
|
3677
3783
|
},
|
|
3678
3784
|
apikey: {
|
|
3679
3785
|
method: "apikey",
|
|
3680
|
-
label: "
|
|
3681
|
-
billing: "Pay-per-token from
|
|
3682
|
-
when: "Used
|
|
3786
|
+
label: "Anthropic API key",
|
|
3787
|
+
billing: "Pay-per-token from an Anthropic Console key.",
|
|
3788
|
+
when: "No subscription needed. Used only while you are not signed in with Claude."
|
|
3683
3789
|
}
|
|
3684
3790
|
}
|
|
3685
3791
|
},
|
|
3686
3792
|
{
|
|
3687
|
-
value: "
|
|
3688
|
-
label: "
|
|
3689
|
-
description: "
|
|
3793
|
+
value: "openai",
|
|
3794
|
+
label: "OpenAI",
|
|
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",
|
|
3690
3796
|
methods: ["oauth", "apikey"],
|
|
3691
|
-
apiKeyLabel: "
|
|
3797
|
+
apiKeyLabel: "OpenAI",
|
|
3692
3798
|
methodDetails: {
|
|
3693
3799
|
oauth: {
|
|
3694
3800
|
method: "oauth",
|
|
3695
|
-
label: "
|
|
3696
|
-
billing: "
|
|
3697
|
-
when: ""
|
|
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."
|
|
3698
3805
|
},
|
|
3699
3806
|
apikey: {
|
|
3700
3807
|
method: "apikey",
|
|
3701
|
-
label: "
|
|
3702
|
-
billing: "Pay-per-token
|
|
3703
|
-
when: ""
|
|
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."
|
|
3704
3811
|
}
|
|
3705
3812
|
}
|
|
3706
3813
|
},
|
|
3814
|
+
{
|
|
3815
|
+
value: "gemini",
|
|
3816
|
+
label: "Gemini",
|
|
3817
|
+
description: "Gemini 3.8 Flash, 3.5 Flash Lite, 3.1 Flash Lite, 3.5 Flash, 3.1 Pro (Preview)",
|
|
3818
|
+
methods: ["apikey"],
|
|
3819
|
+
apiKeyLabel: "Gemini",
|
|
3820
|
+
apiKeyBaseUrl: GEMINI_API_KEY_BASE_URL,
|
|
3821
|
+
// Stored under its own key: `gemini` held the retired Google sign-in.
|
|
3822
|
+
apiKeyVariants: [
|
|
3823
|
+
{
|
|
3824
|
+
key: GEMINI_API_KEY,
|
|
3825
|
+
label: "Google AI Studio",
|
|
3826
|
+
kind: "payg",
|
|
3827
|
+
billing: "Pay-per-token from a Google AI Studio key.",
|
|
3828
|
+
baseUrl: GEMINI_API_KEY_BASE_URL
|
|
3829
|
+
}
|
|
3830
|
+
],
|
|
3831
|
+
methodDetails: apiKeyDetails("Gemini API key", "Pay-per-token from a Google AI Studio key.")
|
|
3832
|
+
},
|
|
3833
|
+
{
|
|
3834
|
+
value: "xai",
|
|
3835
|
+
label: "xAI (Grok)",
|
|
3836
|
+
description: "Grok 4.7",
|
|
3837
|
+
methods: ["apikey"],
|
|
3838
|
+
apiKeyLabel: "xAI",
|
|
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
|
+
)
|
|
3844
|
+
},
|
|
3707
3845
|
{
|
|
3708
3846
|
value: "moonshot",
|
|
3709
|
-
label: "Moonshot",
|
|
3710
|
-
description: "Kimi K3, K2.8 Preview (Kimi
|
|
3711
|
-
methods: ["
|
|
3712
|
-
apiKeyLabel: "
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
label: "
|
|
3717
|
-
|
|
3718
|
-
|
|
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."
|
|
3719
3857
|
},
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
label: "Moonshot API
|
|
3723
|
-
|
|
3724
|
-
|
|
3858
|
+
{
|
|
3859
|
+
key: "moonshot",
|
|
3860
|
+
label: "Moonshot API",
|
|
3861
|
+
kind: "payg",
|
|
3862
|
+
billing: "Pay-per-token from platform.moonshot.ai."
|
|
3725
3863
|
}
|
|
3726
|
-
|
|
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
|
+
)
|
|
3727
3869
|
},
|
|
3728
3870
|
{
|
|
3729
3871
|
value: "glm",
|
|
3730
3872
|
label: "Z.AI (GLM)",
|
|
3731
3873
|
description: "GLM-5.3, GLM-5.3-Flash",
|
|
3732
3874
|
methods: ["apikey"],
|
|
3733
|
-
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
|
+
)
|
|
3734
3881
|
},
|
|
3735
3882
|
{
|
|
3736
3883
|
value: "minimax",
|
|
3737
3884
|
label: "MiniMax",
|
|
3738
3885
|
description: "MiniMax M3.1 Flash, M3",
|
|
3739
3886
|
methods: ["apikey"],
|
|
3740
|
-
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
|
+
)
|
|
3741
3893
|
},
|
|
3742
3894
|
{
|
|
3743
3895
|
value: "xiaomi",
|
|
@@ -3750,44 +3902,66 @@ var ALL_AUTH_PROVIDERS = [
|
|
|
3750
3902
|
{
|
|
3751
3903
|
key: "xiaomi",
|
|
3752
3904
|
label: "Token Plan",
|
|
3905
|
+
kind: "plan",
|
|
3906
|
+
billing: "Uses your MiMo Token Plan.",
|
|
3753
3907
|
baseUrl: "https://token-plan-sgp.xiaomimimo.com/v1"
|
|
3754
3908
|
},
|
|
3755
3909
|
{
|
|
3756
3910
|
key: XIAOMI_CREDITS_KEY,
|
|
3757
|
-
label: "API Credits
|
|
3911
|
+
label: "API Credits",
|
|
3912
|
+
kind: "payg",
|
|
3913
|
+
billing: "Pay-per-token from MiMo API Credits. Required for UltraSpeed.",
|
|
3758
3914
|
baseUrl: "https://api.xiaomimimo.com/v1"
|
|
3759
3915
|
}
|
|
3760
|
-
]
|
|
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
|
+
)
|
|
3761
3921
|
},
|
|
3762
3922
|
{
|
|
3763
3923
|
value: "deepseek",
|
|
3764
3924
|
label: "DeepSeek",
|
|
3765
3925
|
description: "DeepSeek V4 Pro, V4.1 Flash",
|
|
3766
3926
|
methods: ["apikey"],
|
|
3767
|
-
apiKeyLabel: "DeepSeek"
|
|
3927
|
+
apiKeyLabel: "DeepSeek",
|
|
3928
|
+
methodDetails: apiKeyDetails("DeepSeek API key", "Pay-per-token from platform.deepseek.com.")
|
|
3768
3929
|
},
|
|
3769
3930
|
{
|
|
3770
3931
|
value: "sakana",
|
|
3771
3932
|
label: "Sakana (Fugu)",
|
|
3772
3933
|
description: "Fugu, Fugu Max, Fugu Ultra",
|
|
3773
3934
|
methods: ["apikey"],
|
|
3774
|
-
apiKeyLabel: "Sakana"
|
|
3935
|
+
apiKeyLabel: "Sakana",
|
|
3936
|
+
methodDetails: apiKeyDetails("Sakana API key", "Pay-per-token from your Sakana API account.")
|
|
3775
3937
|
},
|
|
3776
3938
|
{
|
|
3777
3939
|
value: "openrouter",
|
|
3778
3940
|
label: "OpenRouter",
|
|
3779
3941
|
description: "Qwen3.8 Max \xB7 multi-provider gateway",
|
|
3780
3942
|
methods: ["apikey"],
|
|
3781
|
-
apiKeyLabel: "OpenRouter"
|
|
3943
|
+
apiKeyLabel: "OpenRouter",
|
|
3944
|
+
methodDetails: apiKeyDetails("OpenRouter API key", "Pay-per-token from OpenRouter credits.")
|
|
3782
3945
|
},
|
|
3783
3946
|
{
|
|
3784
3947
|
value: "huggingface",
|
|
3785
3948
|
label: "Hugging Face",
|
|
3786
3949
|
description: "Kimi K2.7 Code, DeepSeek V4.1 Flash, GPT-OSS 120B",
|
|
3787
3950
|
methods: ["apikey"],
|
|
3788
|
-
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
|
+
)
|
|
3789
3956
|
}
|
|
3790
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
|
+
}
|
|
3791
3965
|
var AUTH_PROVIDERS = ALL_AUTH_PROVIDERS.filter(
|
|
3792
3966
|
(p) => !isHiddenProvider(p.value)
|
|
3793
3967
|
);
|
|
@@ -3814,15 +3988,24 @@ function describeAuthMethods(provider) {
|
|
|
3814
3988
|
});
|
|
3815
3989
|
}
|
|
3816
3990
|
function authPriorityNote(provider) {
|
|
3817
|
-
if (provider === "
|
|
3818
|
-
return "Uses
|
|
3991
|
+
if (provider === "anthropic") {
|
|
3992
|
+
return "Uses Sign in with Claude while you are signed in; otherwise the Anthropic API key.";
|
|
3819
3993
|
}
|
|
3820
3994
|
const dual = dualAuthProvider(provider);
|
|
3821
|
-
if (
|
|
3822
|
-
|
|
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;
|
|
3823
4005
|
}
|
|
3824
4006
|
|
|
3825
4007
|
// src/provider-usage.ts
|
|
4008
|
+
var import_gg_ai5 = require("@kenkaiiii/gg-ai");
|
|
3826
4009
|
var SubscriptionUsageError = class extends Error {
|
|
3827
4010
|
constructor(message, status, retryAfterMs2) {
|
|
3828
4011
|
super(message);
|
|
@@ -3831,52 +4014,14 @@ var SubscriptionUsageError = class extends Error {
|
|
|
3831
4014
|
this.name = "SubscriptionUsageError";
|
|
3832
4015
|
}
|
|
3833
4016
|
};
|
|
3834
|
-
var ANTHROPIC_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
3835
|
-
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
3836
4017
|
function finiteNumber(value) {
|
|
3837
4018
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3838
4019
|
}
|
|
3839
|
-
function clampPercent(value) {
|
|
3840
|
-
const number = finiteNumber(value);
|
|
3841
|
-
return number === void 0 ? void 0 : Math.min(100, Math.max(0, number));
|
|
3842
|
-
}
|
|
3843
4020
|
function isoTimestamp(value) {
|
|
3844
4021
|
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
3845
4022
|
const timestamp = Date.parse(value);
|
|
3846
4023
|
return Number.isFinite(timestamp) ? timestamp : void 0;
|
|
3847
4024
|
}
|
|
3848
|
-
function unixTimestamp(value) {
|
|
3849
|
-
const seconds = finiteNumber(value);
|
|
3850
|
-
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
3851
|
-
}
|
|
3852
|
-
function codexResetAt(window, now) {
|
|
3853
|
-
const absolute = unixTimestamp(window.reset_at);
|
|
3854
|
-
if (absolute !== void 0) return absolute;
|
|
3855
|
-
const afterSeconds = finiteNumber(window.reset_after_seconds);
|
|
3856
|
-
return afterSeconds === void 0 ? void 0 : now + afterSeconds * 1e3;
|
|
3857
|
-
}
|
|
3858
|
-
function currentWindowLabel(seconds, fallbackHours) {
|
|
3859
|
-
const duration = finiteNumber(seconds);
|
|
3860
|
-
const hours = Math.max(1, Math.round((duration ?? fallbackHours * 3600) / 3600));
|
|
3861
|
-
return `${hours}-hour`;
|
|
3862
|
-
}
|
|
3863
|
-
function codexWindowKind(window, fallback) {
|
|
3864
|
-
const duration = finiteNumber(window.limit_window_seconds);
|
|
3865
|
-
if (duration === void 0) return fallback;
|
|
3866
|
-
return duration >= 6 * 24 * 60 * 60 ? "weekly" : "current";
|
|
3867
|
-
}
|
|
3868
|
-
function normalizedCodexWindow(window, fallbackKind, now) {
|
|
3869
|
-
if (!window) return null;
|
|
3870
|
-
const usedPercent = clampPercent(window.used_percent);
|
|
3871
|
-
if (usedPercent === void 0) return null;
|
|
3872
|
-
const kind = codexWindowKind(window, fallbackKind);
|
|
3873
|
-
return {
|
|
3874
|
-
kind,
|
|
3875
|
-
label: kind === "weekly" ? "Weekly" : currentWindowLabel(window.limit_window_seconds, 5),
|
|
3876
|
-
usedPercent,
|
|
3877
|
-
resetsAt: codexResetAt(window, now)
|
|
3878
|
-
};
|
|
3879
|
-
}
|
|
3880
4025
|
function retryAfterMs(response, now) {
|
|
3881
4026
|
const value = response.headers.get("retry-after")?.trim();
|
|
3882
4027
|
if (!value) return void 0;
|
|
@@ -3899,70 +4044,6 @@ async function readUsageResponse(response, now) {
|
|
|
3899
4044
|
throw new SubscriptionUsageError("Subscription usage response was not valid JSON");
|
|
3900
4045
|
}
|
|
3901
4046
|
}
|
|
3902
|
-
async function fetchAnthropicUsage(credentials, fetchFn, signal, now) {
|
|
3903
|
-
const response = await fetchFn(ANTHROPIC_USAGE_URL, {
|
|
3904
|
-
method: "GET",
|
|
3905
|
-
signal,
|
|
3906
|
-
headers: {
|
|
3907
|
-
Authorization: `Bearer ${credentials.accessToken}`,
|
|
3908
|
-
Accept: "application/json",
|
|
3909
|
-
"anthropic-version": "2023-06-01",
|
|
3910
|
-
"anthropic-beta": "oauth-2025-04-20",
|
|
3911
|
-
"User-Agent": "ggcoder"
|
|
3912
|
-
}
|
|
3913
|
-
});
|
|
3914
|
-
const data = await readUsageResponse(response, now());
|
|
3915
|
-
const windows = [];
|
|
3916
|
-
const currentPercent = clampPercent(data.five_hour?.utilization);
|
|
3917
|
-
if (currentPercent !== void 0) {
|
|
3918
|
-
windows.push({
|
|
3919
|
-
kind: "current",
|
|
3920
|
-
label: "5-hour",
|
|
3921
|
-
usedPercent: currentPercent,
|
|
3922
|
-
resetsAt: isoTimestamp(data.five_hour?.resets_at)
|
|
3923
|
-
});
|
|
3924
|
-
}
|
|
3925
|
-
const weeklyPercent = clampPercent(data.seven_day?.utilization);
|
|
3926
|
-
if (weeklyPercent !== void 0) {
|
|
3927
|
-
windows.push({
|
|
3928
|
-
kind: "weekly",
|
|
3929
|
-
label: "Weekly",
|
|
3930
|
-
usedPercent: weeklyPercent,
|
|
3931
|
-
resetsAt: isoTimestamp(data.seven_day?.resets_at)
|
|
3932
|
-
});
|
|
3933
|
-
}
|
|
3934
|
-
return {
|
|
3935
|
-
provider: "anthropic",
|
|
3936
|
-
displayName: "Anthropic",
|
|
3937
|
-
windows,
|
|
3938
|
-
fetchedAt: now()
|
|
3939
|
-
};
|
|
3940
|
-
}
|
|
3941
|
-
async function fetchCodexUsage(credentials, fetchFn, signal, now) {
|
|
3942
|
-
const headers = {
|
|
3943
|
-
Authorization: `Bearer ${credentials.accessToken}`,
|
|
3944
|
-
Accept: "application/json",
|
|
3945
|
-
originator: "ggcoder",
|
|
3946
|
-
"User-Agent": "ggcoder"
|
|
3947
|
-
};
|
|
3948
|
-
if (credentials.accountId) headers["ChatGPT-Account-Id"] = credentials.accountId;
|
|
3949
|
-
const response = await fetchFn(CODEX_USAGE_URL, { method: "GET", signal, headers });
|
|
3950
|
-
const data = await readUsageResponse(response, now());
|
|
3951
|
-
const windows = [
|
|
3952
|
-
normalizedCodexWindow(data.rate_limit?.primary_window, "current", now()),
|
|
3953
|
-
normalizedCodexWindow(data.rate_limit?.secondary_window, "weekly", now())
|
|
3954
|
-
].filter((window) => window !== null);
|
|
3955
|
-
windows.sort((left, right) => {
|
|
3956
|
-
if (left.kind === right.kind) return 0;
|
|
3957
|
-
return left.kind === "current" ? -1 : 1;
|
|
3958
|
-
});
|
|
3959
|
-
return {
|
|
3960
|
-
provider: "openai",
|
|
3961
|
-
displayName: "Codex",
|
|
3962
|
-
windows,
|
|
3963
|
-
fetchedAt: now()
|
|
3964
|
-
};
|
|
3965
|
-
}
|
|
3966
4047
|
function kimiNumber(value) {
|
|
3967
4048
|
if (typeof value === "string" && value.trim()) {
|
|
3968
4049
|
const parsed = Number(value);
|
|
@@ -3993,15 +4074,14 @@ function kimiWindowMinutes(window) {
|
|
|
3993
4074
|
}
|
|
3994
4075
|
}
|
|
3995
4076
|
async function fetchKimiUsage(credentials, fetchFn, signal, now) {
|
|
3996
|
-
const
|
|
4077
|
+
const baseUrl = (credentials.baseUrl ?? KIMI_CODE_DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
4078
|
+
const response = await fetchFn(`${baseUrl}/usages`, {
|
|
3997
4079
|
method: "GET",
|
|
3998
4080
|
signal,
|
|
3999
4081
|
headers: {
|
|
4000
4082
|
Authorization: "Bearer " + credentials.accessToken,
|
|
4001
4083
|
Accept: "application/json",
|
|
4002
|
-
|
|
4003
|
-
// same as model requests.
|
|
4004
|
-
...kimiCodingHeaders()
|
|
4084
|
+
"User-Agent": import_gg_ai5.GG_CODER_USER_AGENT
|
|
4005
4085
|
}
|
|
4006
4086
|
});
|
|
4007
4087
|
const data = await readUsageResponse(response, now());
|
|
@@ -4053,7 +4133,7 @@ async function fetchSubscriptionUsage(provider, credentials, options = {}) {
|
|
|
4053
4133
|
const now = options.now ?? Date.now;
|
|
4054
4134
|
const signal = AbortSignal.timeout(options.timeoutMs ?? 8e3);
|
|
4055
4135
|
try {
|
|
4056
|
-
return
|
|
4136
|
+
return await fetchKimiUsage(credentials, fetchFn, signal, now);
|
|
4057
4137
|
} catch (error) {
|
|
4058
4138
|
if (error instanceof SubscriptionUsageError) throw error;
|
|
4059
4139
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4118,7 +4198,7 @@ var TelegramBot = class {
|
|
|
4118
4198
|
} catch (err) {
|
|
4119
4199
|
if (!this.running) break;
|
|
4120
4200
|
console.error(`[telegram] Poll error: ${err instanceof Error ? err.message : err}`);
|
|
4121
|
-
await
|
|
4201
|
+
await sleep(3e3);
|
|
4122
4202
|
}
|
|
4123
4203
|
}
|
|
4124
4204
|
}
|
|
@@ -4304,7 +4384,7 @@ function splitMessage(text) {
|
|
|
4304
4384
|
}
|
|
4305
4385
|
return chunks;
|
|
4306
4386
|
}
|
|
4307
|
-
function
|
|
4387
|
+
function sleep(ms) {
|
|
4308
4388
|
return new Promise((r) => setTimeout(r, ms));
|
|
4309
4389
|
}
|
|
4310
4390
|
|
|
@@ -4390,8 +4470,8 @@ async function transcribeVoice(fileUrl) {
|
|
|
4390
4470
|
|
|
4391
4471
|
// src/auto-update.ts
|
|
4392
4472
|
var import_node_child_process2 = require("child_process");
|
|
4393
|
-
var
|
|
4394
|
-
var
|
|
4473
|
+
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
4474
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
4395
4475
|
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
4396
4476
|
var FETCH_TIMEOUT_MS = 1e4;
|
|
4397
4477
|
function compareVersions(a, b) {
|
|
@@ -4424,7 +4504,7 @@ function createAutoUpdater(config) {
|
|
|
4424
4504
|
}
|
|
4425
4505
|
function readState() {
|
|
4426
4506
|
try {
|
|
4427
|
-
const raw =
|
|
4507
|
+
const raw = import_node_fs5.default.readFileSync(stateFilePath(), "utf-8");
|
|
4428
4508
|
return JSON.parse(raw);
|
|
4429
4509
|
} catch {
|
|
4430
4510
|
return null;
|
|
@@ -4433,8 +4513,8 @@ function createAutoUpdater(config) {
|
|
|
4433
4513
|
function writeState(state) {
|
|
4434
4514
|
try {
|
|
4435
4515
|
const filePath = stateFilePath();
|
|
4436
|
-
|
|
4437
|
-
|
|
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));
|
|
4438
4518
|
} catch {
|
|
4439
4519
|
}
|
|
4440
4520
|
}
|
|
@@ -4560,34 +4640,55 @@ function createAutoUpdater(config) {
|
|
|
4560
4640
|
0 && (module.exports = {
|
|
4561
4641
|
AUTH_PROVIDERS,
|
|
4562
4642
|
AuthStorage,
|
|
4643
|
+
CHATGPT_PLAN_BASE_URL,
|
|
4644
|
+
CHATGPT_PLAN_NOT_ENABLED_MESSAGE,
|
|
4645
|
+
CHATGPT_PLAN_SCOPE,
|
|
4646
|
+
CHATGPT_USAGE_URL,
|
|
4647
|
+
CLAUDE_CODE_AUTH_KEY,
|
|
4648
|
+
CLAUDE_CODE_AUTH_MARKER,
|
|
4649
|
+
CLAUDE_CODE_MANIFEST,
|
|
4650
|
+
ChatGPTPlanNotEnabledError,
|
|
4563
4651
|
DEFAULT_LOCAL_ENDPOINTS,
|
|
4564
4652
|
DEFAULT_MAX_VIDEO_BYTES,
|
|
4565
4653
|
FALLBACK_CONTEXT_WINDOW,
|
|
4566
4654
|
GEMINI_API_KEY,
|
|
4567
4655
|
GEMINI_API_KEY_BASE_URL,
|
|
4568
4656
|
HIDDEN_PROVIDERS,
|
|
4657
|
+
KIMI_CODE_DEFAULT_BASE_URL,
|
|
4658
|
+
KIMI_CODE_KEY,
|
|
4659
|
+
KIMI_CODE_OVERSEAS_BASE_URL,
|
|
4569
4660
|
LOCAL_API_KEY_PLACEHOLDER,
|
|
4570
4661
|
LOCAL_AUTH_KEY_PREFIX,
|
|
4571
4662
|
MINIMAX_CN_BASE_URL,
|
|
4572
4663
|
MINIMAX_GLOBAL_BASE_URL,
|
|
4573
4664
|
MODELS,
|
|
4574
|
-
MOONSHOT_OAUTH_KEY,
|
|
4575
4665
|
NotLoggedInError,
|
|
4666
|
+
OPENAI_CHATGPT_KEY,
|
|
4576
4667
|
OPENROUTER_DEFAULT_BASE_URL,
|
|
4577
4668
|
PLAN_MODE_THINKING_CAP,
|
|
4669
|
+
RETIRED_SIGN_INS,
|
|
4578
4670
|
SubscriptionUsageError,
|
|
4579
4671
|
TelegramBot,
|
|
4580
|
-
XAI_OAUTH_KEY,
|
|
4581
4672
|
XIAOMI_CREDITS_KEY,
|
|
4582
4673
|
XIAOMI_TOKEN_PLAN_BASE_URLS,
|
|
4674
|
+
apiKeyStorageKeys,
|
|
4583
4675
|
authPriorityNote,
|
|
4584
4676
|
clampThinkingForPlanMode,
|
|
4677
|
+
claudeCodeAuthStatus,
|
|
4678
|
+
claudeCodeBinaryPath,
|
|
4679
|
+
claudeCodeCallbackUrl,
|
|
4680
|
+
claudeCodeConfigDir,
|
|
4681
|
+
claudeCodeEnv,
|
|
4682
|
+
claudeCodeHome,
|
|
4683
|
+
claudeCodePlatformKey,
|
|
4684
|
+
clearClaudeCodeUsage,
|
|
4585
4685
|
clearLocalDiscoveryCache,
|
|
4586
4686
|
clearRuntimeModels,
|
|
4587
4687
|
closeLogger,
|
|
4588
4688
|
createAutoUpdater,
|
|
4589
4689
|
decodeOggOpus,
|
|
4590
4690
|
describeAuthMethods,
|
|
4691
|
+
detectKimiCodeBaseUrl,
|
|
4591
4692
|
detectMiniMaxBaseUrl,
|
|
4592
4693
|
detectXiaomiTokenPlanBaseUrl,
|
|
4593
4694
|
discoverLocalModels,
|
|
@@ -4595,8 +4696,11 @@ function createAutoUpdater(config) {
|
|
|
4595
4696
|
dualAuthProvider,
|
|
4596
4697
|
dualAuthProviderByOAuthKey,
|
|
4597
4698
|
endpointRoot,
|
|
4699
|
+
ensureClaudeCodeBinary,
|
|
4700
|
+
fetchChatGPTPlanModels,
|
|
4598
4701
|
fetchSubscriptionUsage,
|
|
4599
4702
|
findProbedModel,
|
|
4703
|
+
formatClaudeCodeInstallProgress,
|
|
4600
4704
|
formatLocalModelId,
|
|
4601
4705
|
generatePKCE,
|
|
4602
4706
|
getAllModels,
|
|
@@ -4604,8 +4708,7 @@ function createAutoUpdater(config) {
|
|
|
4604
4708
|
getAuthProvider,
|
|
4605
4709
|
getAuthStorageKey,
|
|
4606
4710
|
getAuthStorageKeys,
|
|
4607
|
-
|
|
4608
|
-
getClaudeCodeVersion,
|
|
4711
|
+
getChatGPTHostId,
|
|
4609
4712
|
getContextWindow,
|
|
4610
4713
|
getDefaultModel,
|
|
4611
4714
|
getDefaultThinkingLevel,
|
|
@@ -4621,48 +4724,49 @@ function createAutoUpdater(config) {
|
|
|
4621
4724
|
getSupportedThinkingLevels,
|
|
4622
4725
|
getToolResultCharLimit,
|
|
4623
4726
|
getVideoByteLimit,
|
|
4624
|
-
|
|
4625
|
-
grokCliHeaders,
|
|
4626
|
-
isGrokCliEndpoint,
|
|
4727
|
+
isClaudeCodeAuthMarker,
|
|
4627
4728
|
isHiddenProvider,
|
|
4628
4729
|
isKimiCodingEndpoint,
|
|
4730
|
+
isLegacyClaudeOAuth,
|
|
4629
4731
|
isLocalModelId,
|
|
4630
4732
|
isLoggerOpen,
|
|
4631
4733
|
isModelLoaded,
|
|
4632
4734
|
isPaidModel,
|
|
4633
4735
|
isThinkingLevelSupported,
|
|
4634
|
-
kimiCodeBaseUrl,
|
|
4635
|
-
kimiCodingHeaders,
|
|
4636
4736
|
localAuthStorageKey,
|
|
4637
4737
|
localModelShortName,
|
|
4638
4738
|
log,
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
loginXai,
|
|
4739
|
+
loginChatGPT,
|
|
4740
|
+
loginClaudeCode,
|
|
4741
|
+
logoutClaudeCode,
|
|
4742
|
+
mergeClaudeCodeRateLimit,
|
|
4644
4743
|
oauthStorageKey,
|
|
4645
4744
|
ollamaServedContext,
|
|
4646
4745
|
openLog,
|
|
4746
|
+
parseCallbackInput,
|
|
4647
4747
|
parseLocalModelId,
|
|
4648
4748
|
parseOpenRouterCatalog,
|
|
4649
|
-
pendingClaudeCodeVersionRefresh,
|
|
4650
4749
|
probeEndpoint,
|
|
4651
4750
|
providerStorageKeys,
|
|
4751
|
+
readClaudeCodeUsage,
|
|
4652
4752
|
readStoredBaseUrlSync,
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
refreshKimiToken,
|
|
4656
|
-
refreshOpenAIToken,
|
|
4657
|
-
refreshXaiToken,
|
|
4753
|
+
recordClaudeCodeRateLimit,
|
|
4754
|
+
refreshChatGPTToken,
|
|
4658
4755
|
registerLogCleanup,
|
|
4659
4756
|
registerRuntimeModels,
|
|
4757
|
+
requiredKeyLabel,
|
|
4660
4758
|
resample,
|
|
4759
|
+
resolveClaudeCodeBinary,
|
|
4760
|
+
revokeChatGPTSignIn,
|
|
4661
4761
|
setProgressCallback,
|
|
4662
4762
|
setProviderCatalog,
|
|
4763
|
+
signInWithClaudeCode,
|
|
4764
|
+
signOutOfClaudeCode,
|
|
4765
|
+
storageKeyProvider,
|
|
4663
4766
|
toModelInfo,
|
|
4664
4767
|
transcribeVoice,
|
|
4665
4768
|
usesOpenAICodexTransport,
|
|
4769
|
+
verifyIdToken,
|
|
4666
4770
|
withFileLock,
|
|
4667
4771
|
withoutPaidTier
|
|
4668
4772
|
});
|