@prestyj/core 5.8.0 → 5.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-GMAX7TES.js → chunk-TPGCBL7I.js} +483 -298
- package/dist/chunk-TPGCBL7I.js.map +1 -0
- package/dist/index.cjs +957 -381
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +168 -8
- package/dist/index.d.ts +168 -8
- package/dist/index.js +395 -13
- package/dist/index.js.map +1 -1
- package/dist/model-registry.cjs +92 -29
- package/dist/model-registry.cjs.map +1 -1
- package/dist/model-registry.d.cts +25 -3
- package/dist/model-registry.d.ts +25 -3
- package/dist/model-registry.js +9 -1
- package/package.json +2 -2
- package/dist/chunk-GMAX7TES.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -31,19 +31,30 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AuthStorage: () => AuthStorage,
|
|
34
|
+
DEFAULT_LOCAL_ENDPOINTS: () => DEFAULT_LOCAL_ENDPOINTS,
|
|
34
35
|
DEFAULT_MAX_VIDEO_BYTES: () => DEFAULT_MAX_VIDEO_BYTES,
|
|
36
|
+
FALLBACK_CONTEXT_WINDOW: () => FALLBACK_CONTEXT_WINDOW,
|
|
37
|
+
LOCAL_API_KEY_PLACEHOLDER: () => LOCAL_API_KEY_PLACEHOLDER,
|
|
38
|
+
LOCAL_AUTH_KEY_PREFIX: () => LOCAL_AUTH_KEY_PREFIX,
|
|
35
39
|
MODELS: () => MODELS,
|
|
36
40
|
MOONSHOT_OAUTH_KEY: () => MOONSHOT_OAUTH_KEY,
|
|
37
41
|
NotLoggedInError: () => NotLoggedInError,
|
|
38
42
|
SubscriptionUsageError: () => SubscriptionUsageError,
|
|
39
43
|
TelegramBot: () => TelegramBot,
|
|
40
44
|
XIAOMI_CREDITS_KEY: () => XIAOMI_CREDITS_KEY,
|
|
45
|
+
clearLocalDiscoveryCache: () => clearLocalDiscoveryCache,
|
|
46
|
+
clearRuntimeModels: () => clearRuntimeModels,
|
|
41
47
|
closeLogger: () => closeLogger,
|
|
42
48
|
createAutoUpdater: () => createAutoUpdater,
|
|
43
49
|
decodeOggOpus: () => decodeOggOpus,
|
|
50
|
+
discoverLocalModels: () => discoverLocalModels,
|
|
44
51
|
downmixToMono: () => downmixToMono,
|
|
52
|
+
endpointRoot: () => endpointRoot,
|
|
45
53
|
fetchSubscriptionUsage: () => fetchSubscriptionUsage,
|
|
54
|
+
findProbedModel: () => findProbedModel,
|
|
55
|
+
formatLocalModelId: () => formatLocalModelId,
|
|
46
56
|
generatePKCE: () => generatePKCE,
|
|
57
|
+
getAllModels: () => getAllModels,
|
|
47
58
|
getAppPaths: () => getAppPaths,
|
|
48
59
|
getAuthStorageKey: () => getAuthStorageKey,
|
|
49
60
|
getAuthStorageKeys: () => getAuthStorageKeys,
|
|
@@ -51,6 +62,7 @@ __export(index_exports, {
|
|
|
51
62
|
getClaudeCodeVersion: () => getClaudeCodeVersion,
|
|
52
63
|
getContextWindow: () => getContextWindow,
|
|
53
64
|
getDefaultModel: () => getDefaultModel,
|
|
65
|
+
getDefaultThinkingLevel: () => getDefaultThinkingLevel,
|
|
54
66
|
getFastModel: () => getFastModel,
|
|
55
67
|
getMaxThinkingLevel: () => getMaxThinkingLevel,
|
|
56
68
|
getModel: () => getModel,
|
|
@@ -62,33 +74,43 @@ __export(index_exports, {
|
|
|
62
74
|
getToolResultCharLimit: () => getToolResultCharLimit,
|
|
63
75
|
getVideoByteLimit: () => getVideoByteLimit,
|
|
64
76
|
isKimiCodingEndpoint: () => isKimiCodingEndpoint,
|
|
77
|
+
isLocalModelId: () => isLocalModelId,
|
|
65
78
|
isLoggerOpen: () => isLoggerOpen,
|
|
66
79
|
isModelLoaded: () => isModelLoaded,
|
|
67
80
|
isThinkingLevelSupported: () => isThinkingLevelSupported,
|
|
68
81
|
kimiCodeBaseUrl: () => kimiCodeBaseUrl,
|
|
69
82
|
kimiCodingHeaders: () => kimiCodingHeaders,
|
|
83
|
+
localAuthStorageKey: () => localAuthStorageKey,
|
|
70
84
|
log: () => log,
|
|
71
85
|
loginAnthropic: () => loginAnthropic,
|
|
72
86
|
loginGemini: () => loginGemini,
|
|
73
87
|
loginKimi: () => loginKimi,
|
|
74
88
|
loginOpenAI: () => loginOpenAI,
|
|
75
89
|
openLog: () => openLog,
|
|
90
|
+
parseLocalModelId: () => parseLocalModelId,
|
|
91
|
+
probeEndpoint: () => probeEndpoint,
|
|
92
|
+
readStoredBaseUrlSync: () => readStoredBaseUrlSync,
|
|
76
93
|
refreshAnthropicToken: () => refreshAnthropicToken,
|
|
77
94
|
refreshGeminiToken: () => refreshGeminiToken,
|
|
78
95
|
refreshKimiToken: () => refreshKimiToken,
|
|
79
96
|
refreshOpenAIToken: () => refreshOpenAIToken,
|
|
80
97
|
registerLogCleanup: () => registerLogCleanup,
|
|
98
|
+
registerRuntimeModels: () => registerRuntimeModels,
|
|
81
99
|
resample: () => resample,
|
|
82
100
|
setProgressCallback: () => setProgressCallback,
|
|
101
|
+
toModelInfo: () => toModelInfo,
|
|
83
102
|
transcribeVoice: () => transcribeVoice,
|
|
84
103
|
usesOpenAICodexTransport: () => usesOpenAICodexTransport,
|
|
85
104
|
withFileLock: () => withFileLock
|
|
86
105
|
});
|
|
87
106
|
module.exports = __toCommonJS(index_exports);
|
|
88
107
|
|
|
89
|
-
// src/
|
|
90
|
-
var
|
|
91
|
-
var
|
|
108
|
+
// src/oauth/kimi.ts
|
|
109
|
+
var import_node_child_process = require("child_process");
|
|
110
|
+
var import_node_crypto = require("crypto");
|
|
111
|
+
var import_node_fs = require("fs");
|
|
112
|
+
var import_node_os2 = require("os");
|
|
113
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
92
114
|
|
|
93
115
|
// src/paths.ts
|
|
94
116
|
var import_node_path = __toESM(require("path"), 1);
|
|
@@ -115,8 +137,226 @@ function getAppPaths() {
|
|
|
115
137
|
};
|
|
116
138
|
}
|
|
117
139
|
|
|
140
|
+
// src/oauth/kimi.ts
|
|
141
|
+
var CLIENT_ID = "17e5f671-d194-4dfb-9706-5516cb48c098";
|
|
142
|
+
var DEFAULT_OAUTH_HOST = "https://auth.kimi.com";
|
|
143
|
+
var DEFAULT_CODING_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
144
|
+
var KIMI_PLATFORM = "kimi_code_cli";
|
|
145
|
+
var DEFAULT_KIMI_VERSION = "1.0.11";
|
|
146
|
+
var DEVICE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
147
|
+
function oauthHost() {
|
|
148
|
+
const host = process.env.KIMI_CODE_OAUTH_HOST ?? process.env.KIMI_OAUTH_HOST ?? DEFAULT_OAUTH_HOST;
|
|
149
|
+
return host.replace(/\/+$/, "");
|
|
150
|
+
}
|
|
151
|
+
function kimiCodeBaseUrl() {
|
|
152
|
+
return (process.env.KIMI_CODE_BASE_URL ?? DEFAULT_CODING_BASE_URL).replace(/\/+$/, "");
|
|
153
|
+
}
|
|
154
|
+
function kimiVersion() {
|
|
155
|
+
const v = process.env.KIMI_CODE_VERSION ?? DEFAULT_KIMI_VERSION;
|
|
156
|
+
return asciiHeader(v, DEFAULT_KIMI_VERSION);
|
|
157
|
+
}
|
|
158
|
+
function asciiHeader(value, fallback = "unknown") {
|
|
159
|
+
const cleaned = value.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
160
|
+
return cleaned.length > 0 ? cleaned : fallback;
|
|
161
|
+
}
|
|
162
|
+
function macOsProductVersion() {
|
|
163
|
+
try {
|
|
164
|
+
const version = (0, import_node_child_process.execFileSync)("/usr/bin/sw_vers", ["-productVersion"], {
|
|
165
|
+
encoding: "utf-8",
|
|
166
|
+
timeout: 1e3
|
|
167
|
+
}).trim();
|
|
168
|
+
return version.length > 0 ? version : void 0;
|
|
169
|
+
} catch {
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function deviceModel() {
|
|
174
|
+
const os2 = (0, import_node_os2.type)();
|
|
175
|
+
const version = (0, import_node_os2.release)();
|
|
176
|
+
const osArch = (0, import_node_os2.arch)();
|
|
177
|
+
if (os2 === "Darwin") return `macOS ${macOsProductVersion() ?? version} ${osArch}`;
|
|
178
|
+
if (os2 === "Windows_NT") return `Windows ${version} ${osArch}`;
|
|
179
|
+
return `${os2} ${version} ${osArch}`.trim();
|
|
180
|
+
}
|
|
181
|
+
function deviceId() {
|
|
182
|
+
const idPath = import_node_path2.default.join(getAppPaths().agentDir, "kimi_device_id");
|
|
183
|
+
if ((0, import_node_fs.existsSync)(idPath)) {
|
|
184
|
+
try {
|
|
185
|
+
const text = (0, import_node_fs.readFileSync)(idPath, "utf-8").trim();
|
|
186
|
+
if (text.length > 0) return text;
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const id = (0, import_node_crypto.randomUUID)();
|
|
191
|
+
try {
|
|
192
|
+
(0, import_node_fs.mkdirSync)(getAppPaths().agentDir, { recursive: true, mode: 448 });
|
|
193
|
+
(0, import_node_fs.writeFileSync)(idPath, id, { encoding: "utf-8", mode: 384 });
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
return id;
|
|
197
|
+
}
|
|
198
|
+
function deviceHeaders() {
|
|
199
|
+
return {
|
|
200
|
+
"X-Msh-Platform": KIMI_PLATFORM,
|
|
201
|
+
"X-Msh-Version": kimiVersion(),
|
|
202
|
+
"X-Msh-Device-Name": asciiHeader((0, import_node_os2.hostname)()),
|
|
203
|
+
"X-Msh-Device-Model": asciiHeader(deviceModel()),
|
|
204
|
+
"X-Msh-Os-Version": asciiHeader((0, import_node_os2.release)()),
|
|
205
|
+
"X-Msh-Device-Id": deviceId()
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function kimiCodingHeaders() {
|
|
209
|
+
return {
|
|
210
|
+
"User-Agent": `kimi-code-cli/${kimiVersion()}`,
|
|
211
|
+
...deviceHeaders()
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function isKimiCodingEndpoint(baseUrl) {
|
|
215
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
|
|
216
|
+
const normalized = baseUrl.replace(/\/+$/, "");
|
|
217
|
+
return normalized === kimiCodeBaseUrl() || /(^|\.)kimi\.com/i.test(normalized);
|
|
218
|
+
}
|
|
219
|
+
async function postForm(endpoint, params) {
|
|
220
|
+
const response = await fetch(`${oauthHost()}${endpoint}`, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: {
|
|
223
|
+
...deviceHeaders(),
|
|
224
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
225
|
+
Accept: "application/json"
|
|
226
|
+
},
|
|
227
|
+
body: new URLSearchParams(params).toString()
|
|
228
|
+
});
|
|
229
|
+
let data = {};
|
|
230
|
+
try {
|
|
231
|
+
const parsed = await response.json();
|
|
232
|
+
if (parsed && typeof parsed === "object") data = parsed;
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
return { status: response.status, data };
|
|
236
|
+
}
|
|
237
|
+
function errorDetail(data) {
|
|
238
|
+
const desc = data.error_description ?? data.message ?? data.error;
|
|
239
|
+
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
240
|
+
}
|
|
241
|
+
function credsFromTokenResponse(data, opts) {
|
|
242
|
+
const accessToken = data.access_token;
|
|
243
|
+
const responseRefreshToken = data.refresh_token;
|
|
244
|
+
const expiresIn = Number(data.expires_in);
|
|
245
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
246
|
+
throw new Error("Kimi OAuth response missing access_token.");
|
|
247
|
+
}
|
|
248
|
+
const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
|
|
249
|
+
if (refreshToken.length === 0) {
|
|
250
|
+
throw new Error("Kimi OAuth response missing refresh_token.");
|
|
251
|
+
}
|
|
252
|
+
if (!Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
253
|
+
throw new Error("Kimi OAuth response missing or invalid expires_in.");
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
accessToken,
|
|
257
|
+
refreshToken,
|
|
258
|
+
expiresAt: Date.now() + expiresIn * 1e3,
|
|
259
|
+
expiresIn,
|
|
260
|
+
baseUrl: kimiCodeBaseUrl()
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function requestDeviceAuthorization() {
|
|
264
|
+
const { status, data } = await postForm("/api/oauth/device_authorization", {
|
|
265
|
+
client_id: CLIENT_ID
|
|
266
|
+
});
|
|
267
|
+
if (status !== 200) {
|
|
268
|
+
throw new Error(`Kimi device authorization failed (${status}): ${errorDetail(data)}`);
|
|
269
|
+
}
|
|
270
|
+
const userCode = data.user_code;
|
|
271
|
+
const deviceCode = data.device_code;
|
|
272
|
+
const verificationUriComplete = data.verification_uri_complete;
|
|
273
|
+
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
274
|
+
throw new Error("Kimi device authorization response missing user_code/device_code.");
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
userCode,
|
|
278
|
+
deviceCode,
|
|
279
|
+
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
280
|
+
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
281
|
+
interval: Number(data.interval ?? 5) || 5
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
async function pollDeviceToken(deviceCode) {
|
|
285
|
+
const { status, data } = await postForm("/api/oauth/token", {
|
|
286
|
+
client_id: CLIENT_ID,
|
|
287
|
+
device_code: deviceCode,
|
|
288
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
289
|
+
});
|
|
290
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
291
|
+
return { kind: "success", creds: credsFromTokenResponse(data) };
|
|
292
|
+
}
|
|
293
|
+
if (status >= 500) {
|
|
294
|
+
throw new Error(`Kimi token polling server error (${status}): ${errorDetail(data)}`);
|
|
295
|
+
}
|
|
296
|
+
const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
|
|
297
|
+
switch (errorCode) {
|
|
298
|
+
case "authorization_pending":
|
|
299
|
+
return { kind: "pending" };
|
|
300
|
+
case "slow_down":
|
|
301
|
+
return { kind: "slow_down" };
|
|
302
|
+
case "expired_token":
|
|
303
|
+
return { kind: "expired" };
|
|
304
|
+
case "access_denied":
|
|
305
|
+
return { kind: "denied" };
|
|
306
|
+
default:
|
|
307
|
+
throw new Error(`Kimi token polling failed (${status}): ${errorDetail(data)}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function sleep(ms) {
|
|
311
|
+
return new Promise((resolve) => {
|
|
312
|
+
setTimeout(resolve, ms);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
async function loginKimi(callbacks) {
|
|
316
|
+
const auth = await requestDeviceAuthorization();
|
|
317
|
+
callbacks.onStatus(
|
|
318
|
+
`Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
|
|
319
|
+
);
|
|
320
|
+
callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
|
|
321
|
+
callbacks.onStatus("Waiting for you to authorize in the browser...");
|
|
322
|
+
const deadline = Date.now() + DEVICE_TIMEOUT_MS;
|
|
323
|
+
let interval = Math.max(auth.interval, 1);
|
|
324
|
+
while (Date.now() < deadline) {
|
|
325
|
+
await sleep(interval * 1e3);
|
|
326
|
+
const result = await pollDeviceToken(auth.deviceCode);
|
|
327
|
+
if (result.kind === "success") return result.creds;
|
|
328
|
+
if (result.kind === "denied") {
|
|
329
|
+
throw new Error("Kimi authorization was denied.");
|
|
330
|
+
}
|
|
331
|
+
if (result.kind === "expired") {
|
|
332
|
+
throw new Error("Kimi device code expired. Please run login again.");
|
|
333
|
+
}
|
|
334
|
+
if (result.kind === "slow_down") {
|
|
335
|
+
interval += 5;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
throw new Error("Kimi login timed out. Please run login again.");
|
|
339
|
+
}
|
|
340
|
+
async function refreshKimiToken(refreshToken) {
|
|
341
|
+
const { status, data } = await postForm("/api/oauth/token", {
|
|
342
|
+
client_id: CLIENT_ID,
|
|
343
|
+
grant_type: "refresh_token",
|
|
344
|
+
refresh_token: refreshToken
|
|
345
|
+
});
|
|
346
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
347
|
+
return credsFromTokenResponse(data, { fallbackRefreshToken: refreshToken });
|
|
348
|
+
}
|
|
349
|
+
const errorCode = typeof data.error === "string" ? data.error : "";
|
|
350
|
+
throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/auth-storage.ts
|
|
354
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
355
|
+
var import_node_fs3 = require("fs");
|
|
356
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
357
|
+
|
|
118
358
|
// src/oauth/anthropic.ts
|
|
119
|
-
var
|
|
359
|
+
var import_node_crypto3 = __toESM(require("crypto"), 1);
|
|
120
360
|
|
|
121
361
|
// src/oauth/pkce.ts
|
|
122
362
|
function base64urlEncode(bytes) {
|
|
@@ -138,49 +378,66 @@ async function generatePKCE() {
|
|
|
138
378
|
|
|
139
379
|
// src/claude-code-version.ts
|
|
140
380
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
141
|
-
var
|
|
381
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
142
382
|
|
|
143
383
|
// src/logger.ts
|
|
144
|
-
var
|
|
145
|
-
var
|
|
146
|
-
var
|
|
384
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
385
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
386
|
+
var import_node_crypto2 = require("crypto");
|
|
147
387
|
var import_ai = require("@prestyj/ai");
|
|
148
388
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
149
389
|
var fd = null;
|
|
390
|
+
var bytesWritten = 0;
|
|
391
|
+
var capped = false;
|
|
150
392
|
var sessionId = "";
|
|
151
393
|
var appName = "app";
|
|
152
394
|
var cleanups = [];
|
|
153
395
|
var exactSecrets = [];
|
|
154
396
|
function rotateIfNeeded(filePath) {
|
|
155
397
|
try {
|
|
156
|
-
const st =
|
|
398
|
+
const st = import_node_fs2.default.statSync(filePath);
|
|
157
399
|
if (st.size < MAX_BYTES) return;
|
|
158
400
|
const rotated = `${filePath}.1`;
|
|
159
401
|
try {
|
|
160
|
-
|
|
402
|
+
import_node_fs2.default.unlinkSync(rotated);
|
|
161
403
|
} catch {
|
|
162
404
|
}
|
|
163
|
-
|
|
405
|
+
import_node_fs2.default.renameSync(filePath, rotated);
|
|
164
406
|
} catch {
|
|
165
407
|
}
|
|
166
408
|
}
|
|
167
409
|
function openLog(filePath, name) {
|
|
168
|
-
if (fd !== null) return false;
|
|
410
|
+
if (fd !== null || capped) return false;
|
|
169
411
|
appName = name;
|
|
170
412
|
exactSecrets = (0, import_ai.environmentSecrets)(process.env);
|
|
171
413
|
try {
|
|
172
|
-
|
|
414
|
+
import_node_fs2.default.mkdirSync(import_node_path3.default.dirname(filePath), { recursive: true, mode: 448 });
|
|
173
415
|
} catch {
|
|
174
416
|
}
|
|
175
417
|
rotateIfNeeded(filePath);
|
|
176
418
|
try {
|
|
177
|
-
fd =
|
|
419
|
+
fd = import_node_fs2.default.openSync(filePath, "a");
|
|
178
420
|
} catch {
|
|
421
|
+
fd = null;
|
|
422
|
+
bytesWritten = 0;
|
|
179
423
|
return false;
|
|
180
424
|
}
|
|
181
|
-
sessionId = (0, import_node_crypto.randomBytes)(4).toString("hex");
|
|
182
425
|
try {
|
|
183
|
-
|
|
426
|
+
bytesWritten = import_node_fs2.default.fstatSync(fd).size;
|
|
427
|
+
} catch {
|
|
428
|
+
try {
|
|
429
|
+
import_node_fs2.default.closeSync(fd);
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
fd = null;
|
|
433
|
+
bytesWritten = 0;
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
sessionId = (0, import_node_crypto2.randomBytes)(4).toString("hex");
|
|
437
|
+
try {
|
|
438
|
+
if (bytesWritten < MAX_BYTES) {
|
|
439
|
+
bytesWritten += import_node_fs2.default.writeSync(fd, "\n");
|
|
440
|
+
}
|
|
184
441
|
} catch {
|
|
185
442
|
}
|
|
186
443
|
return true;
|
|
@@ -206,8 +463,23 @@ function log(level, category, message, data) {
|
|
|
206
463
|
if (pairs) line += ` ${pairs}`;
|
|
207
464
|
}
|
|
208
465
|
line += "\n";
|
|
466
|
+
const lineBytes = Buffer.byteLength(line);
|
|
467
|
+
if (bytesWritten + lineBytes > MAX_BYTES) {
|
|
468
|
+
const capLine = `[${ts}] [sid=${sessionId}] [WARN] [logger] Log cap reached; file logging disabled until restart
|
|
469
|
+
`;
|
|
470
|
+
try {
|
|
471
|
+
if (bytesWritten + Buffer.byteLength(capLine) <= MAX_BYTES) {
|
|
472
|
+
bytesWritten += import_node_fs2.default.writeSync(fd, capLine);
|
|
473
|
+
}
|
|
474
|
+
import_node_fs2.default.closeSync(fd);
|
|
475
|
+
} catch {
|
|
476
|
+
}
|
|
477
|
+
fd = null;
|
|
478
|
+
capped = true;
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
209
481
|
try {
|
|
210
|
-
|
|
482
|
+
bytesWritten += import_node_fs2.default.writeSync(fd, line);
|
|
211
483
|
} catch {
|
|
212
484
|
}
|
|
213
485
|
}
|
|
@@ -215,13 +487,16 @@ function registerLogCleanup(fn) {
|
|
|
215
487
|
cleanups.push(fn);
|
|
216
488
|
}
|
|
217
489
|
function closeLogger(opts) {
|
|
218
|
-
if (fd
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
490
|
+
if (fd !== null) {
|
|
491
|
+
if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
|
|
492
|
+
try {
|
|
493
|
+
if (fd !== null) import_node_fs2.default.closeSync(fd);
|
|
494
|
+
} catch {
|
|
495
|
+
}
|
|
223
496
|
}
|
|
224
497
|
fd = null;
|
|
498
|
+
bytesWritten = 0;
|
|
499
|
+
capped = false;
|
|
225
500
|
exactSecrets = [];
|
|
226
501
|
for (const unsub of cleanups) unsub();
|
|
227
502
|
cleanups = [];
|
|
@@ -235,7 +510,7 @@ var FALLBACK_VERSION = "2.1.88";
|
|
|
235
510
|
var memoryCache = null;
|
|
236
511
|
var inflight = null;
|
|
237
512
|
function cachePath() {
|
|
238
|
-
return
|
|
513
|
+
return import_node_path4.default.join(getAppPaths().agentDir, "claude-code-version.json");
|
|
239
514
|
}
|
|
240
515
|
async function readDiskCache() {
|
|
241
516
|
try {
|
|
@@ -317,7 +592,7 @@ async function getClaudeCliUserAgent() {
|
|
|
317
592
|
}
|
|
318
593
|
|
|
319
594
|
// src/oauth/anthropic.ts
|
|
320
|
-
var
|
|
595
|
+
var CLIENT_ID2 = atob("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
|
|
321
596
|
var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
322
597
|
var TOKEN_URLS = [
|
|
323
598
|
"https://platform.claude.com/v1/oauth/token",
|
|
@@ -361,10 +636,10 @@ function toCredentials(data) {
|
|
|
361
636
|
}
|
|
362
637
|
async function loginAnthropic(callbacks) {
|
|
363
638
|
const { verifier, challenge } = await generatePKCE();
|
|
364
|
-
const state =
|
|
639
|
+
const state = import_node_crypto3.default.randomBytes(16).toString("hex");
|
|
365
640
|
const params = new URLSearchParams({
|
|
366
641
|
code: "true",
|
|
367
|
-
client_id:
|
|
642
|
+
client_id: CLIENT_ID2,
|
|
368
643
|
response_type: "code",
|
|
369
644
|
redirect_uri: REDIRECT_URI,
|
|
370
645
|
scope: SCOPES,
|
|
@@ -385,7 +660,7 @@ async function exchangeAnthropicCode(code, state, verifier) {
|
|
|
385
660
|
const data = await postTokenRequest(
|
|
386
661
|
{
|
|
387
662
|
grant_type: "authorization_code",
|
|
388
|
-
client_id:
|
|
663
|
+
client_id: CLIENT_ID2,
|
|
389
664
|
code,
|
|
390
665
|
state,
|
|
391
666
|
redirect_uri: REDIRECT_URI,
|
|
@@ -399,7 +674,7 @@ async function refreshAnthropicToken(refreshToken) {
|
|
|
399
674
|
const data = await postTokenRequest(
|
|
400
675
|
{
|
|
401
676
|
grant_type: "refresh_token",
|
|
402
|
-
client_id:
|
|
677
|
+
client_id: CLIENT_ID2,
|
|
403
678
|
refresh_token: refreshToken
|
|
404
679
|
},
|
|
405
680
|
"token refresh"
|
|
@@ -409,8 +684,8 @@ async function refreshAnthropicToken(refreshToken) {
|
|
|
409
684
|
|
|
410
685
|
// src/oauth/openai.ts
|
|
411
686
|
var import_node_http = __toESM(require("http"), 1);
|
|
412
|
-
var
|
|
413
|
-
var
|
|
687
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
688
|
+
var CLIENT_ID3 = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
414
689
|
var AUTHORIZE_URL2 = "https://auth.openai.com/oauth/authorize";
|
|
415
690
|
var TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
416
691
|
var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
|
|
@@ -418,10 +693,10 @@ var SCOPE = "openid profile email offline_access api.connectors.read api.connect
|
|
|
418
693
|
var JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
419
694
|
async function loginOpenAI(callbacks) {
|
|
420
695
|
const { verifier, challenge } = await generatePKCE();
|
|
421
|
-
const state =
|
|
696
|
+
const state = import_node_crypto4.default.randomBytes(16).toString("hex");
|
|
422
697
|
const url = new URL(AUTHORIZE_URL2);
|
|
423
698
|
url.searchParams.set("response_type", "code");
|
|
424
|
-
url.searchParams.set("client_id",
|
|
699
|
+
url.searchParams.set("client_id", CLIENT_ID3);
|
|
425
700
|
url.searchParams.set("redirect_uri", REDIRECT_URI2);
|
|
426
701
|
url.searchParams.set("scope", SCOPE);
|
|
427
702
|
url.searchParams.set("code_challenge", challenge);
|
|
@@ -542,7 +817,7 @@ async function exchangeOpenAICode(code, verifier) {
|
|
|
542
817
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
543
818
|
body: new URLSearchParams({
|
|
544
819
|
grant_type: "authorization_code",
|
|
545
|
-
client_id:
|
|
820
|
+
client_id: CLIENT_ID3,
|
|
546
821
|
code,
|
|
547
822
|
redirect_uri: REDIRECT_URI2,
|
|
548
823
|
code_verifier: verifier
|
|
@@ -566,7 +841,7 @@ async function refreshOpenAIToken(refreshToken) {
|
|
|
566
841
|
body: new URLSearchParams({
|
|
567
842
|
grant_type: "refresh_token",
|
|
568
843
|
refresh_token: refreshToken,
|
|
569
|
-
client_id:
|
|
844
|
+
client_id: CLIENT_ID3
|
|
570
845
|
})
|
|
571
846
|
});
|
|
572
847
|
if (!response.ok) {
|
|
@@ -588,7 +863,7 @@ async function refreshOpenAIToken(refreshToken) {
|
|
|
588
863
|
|
|
589
864
|
// src/oauth/gemini.ts
|
|
590
865
|
var import_node_http2 = __toESM(require("http"), 1);
|
|
591
|
-
var
|
|
866
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
592
867
|
var CLIENT_ID_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_ID";
|
|
593
868
|
var CLIENT_SECRET_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_SECRET";
|
|
594
869
|
var DEFAULT_CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
|
|
@@ -622,7 +897,7 @@ var CodeAssistHttpError = class extends Error {
|
|
|
622
897
|
async function loginGemini(callbacks) {
|
|
623
898
|
const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
|
|
624
899
|
const { verifier, challenge } = await generatePKCE();
|
|
625
|
-
const state =
|
|
900
|
+
const state = import_node_crypto5.default.randomBytes(32).toString("hex");
|
|
626
901
|
const redirectUri = await getLoopbackRedirectUri();
|
|
627
902
|
const url = new URL(AUTHORIZE_URL3);
|
|
628
903
|
url.searchParams.set("response_type", "code");
|
|
@@ -883,287 +1158,70 @@ function throwProjectError(response) {
|
|
|
883
1158
|
async function codeAssistPost(accessToken, method, body) {
|
|
884
1159
|
let lastError;
|
|
885
1160
|
for (let attempt = 0; attempt <= CODE_ASSIST_POST_RETRIES; attempt++) {
|
|
886
|
-
try {
|
|
887
|
-
return await codeAssistRequest(getCodeAssistMethodUrl(method), accessToken, method, {
|
|
888
|
-
method: "POST",
|
|
889
|
-
body: JSON.stringify(body)
|
|
890
|
-
});
|
|
891
|
-
} catch (err) {
|
|
892
|
-
if (!(err instanceof CodeAssistHttpError) || attempt === CODE_ASSIST_POST_RETRIES || !shouldRetryCodeAssistStatus(err.status)) {
|
|
893
|
-
throw err;
|
|
894
|
-
}
|
|
895
|
-
lastError = err;
|
|
896
|
-
}
|
|
897
|
-
await new Promise((resolve) => setTimeout(resolve, CODE_ASSIST_POST_RETRY_DELAY_MS));
|
|
898
|
-
}
|
|
899
|
-
throw lastError ?? new Error(`Gemini Code Assist ${method} failed.`);
|
|
900
|
-
}
|
|
901
|
-
async function codeAssistGet(accessToken, operationName) {
|
|
902
|
-
return codeAssistRequest(getCodeAssistOperationUrl(operationName), accessToken, "operation", {
|
|
903
|
-
method: "GET"
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
async function codeAssistRequest(url, accessToken, label, init) {
|
|
907
|
-
const response = await fetch(url, {
|
|
908
|
-
...init,
|
|
909
|
-
headers: codeAssistHeaders(accessToken)
|
|
910
|
-
});
|
|
911
|
-
if (!response.ok) {
|
|
912
|
-
const text = await response.text();
|
|
913
|
-
throw new CodeAssistHttpError(label, response.status, text);
|
|
914
|
-
}
|
|
915
|
-
return await response.json();
|
|
916
|
-
}
|
|
917
|
-
function getCodeAssistBaseUrl() {
|
|
918
|
-
const endpoint = process.env.CODE_ASSIST_ENDPOINT ?? CODE_ASSIST_BASE_URL;
|
|
919
|
-
const version = process.env.CODE_ASSIST_API_VERSION || CODE_ASSIST_API_VERSION;
|
|
920
|
-
return `${endpoint}/${version}`;
|
|
921
|
-
}
|
|
922
|
-
function getCodeAssistMethodUrl(method) {
|
|
923
|
-
return `${getCodeAssistBaseUrl()}:${method}`;
|
|
924
|
-
}
|
|
925
|
-
function getCodeAssistOperationUrl(operationName) {
|
|
926
|
-
return `${getCodeAssistBaseUrl()}/${operationName}`;
|
|
927
|
-
}
|
|
928
|
-
function shouldRetryCodeAssistStatus(status) {
|
|
929
|
-
return status === 429 || status === 499 || status >= 500 && status <= 599;
|
|
930
|
-
}
|
|
931
|
-
function isVpcScAffectedError(error) {
|
|
932
|
-
try {
|
|
933
|
-
const parsed = JSON.parse(error.body);
|
|
934
|
-
if (!parsed || typeof parsed !== "object" || !("error" in parsed)) return false;
|
|
935
|
-
const details = parsed.error?.details;
|
|
936
|
-
return Array.isArray(details) ? details.some(
|
|
937
|
-
(detail) => detail != null && typeof detail === "object" && "reason" in detail && detail.reason === VPC_SC_REASON
|
|
938
|
-
) : false;
|
|
939
|
-
} catch {
|
|
940
|
-
return false;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
function codeAssistHeaders(accessToken) {
|
|
944
|
-
return {
|
|
945
|
-
Authorization: `Bearer ${accessToken}`,
|
|
946
|
-
"Content-Type": "application/json",
|
|
947
|
-
"User-Agent": "google-gemini-cli",
|
|
948
|
-
"X-Goog-Api-Client": "gemini-cli/0.0.0"
|
|
949
|
-
};
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
// src/oauth/kimi.ts
|
|
953
|
-
var import_node_child_process = require("child_process");
|
|
954
|
-
var import_node_crypto5 = require("crypto");
|
|
955
|
-
var import_node_fs2 = require("fs");
|
|
956
|
-
var import_node_os2 = require("os");
|
|
957
|
-
var import_node_path4 = __toESM(require("path"), 1);
|
|
958
|
-
var CLIENT_ID3 = "17e5f671-d194-4dfb-9706-5516cb48c098";
|
|
959
|
-
var DEFAULT_OAUTH_HOST = "https://auth.kimi.com";
|
|
960
|
-
var DEFAULT_CODING_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
961
|
-
var KIMI_PLATFORM = "kimi_code_cli";
|
|
962
|
-
var DEFAULT_KIMI_VERSION = "1.0.11";
|
|
963
|
-
var DEVICE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
964
|
-
function oauthHost() {
|
|
965
|
-
const host = process.env.KIMI_CODE_OAUTH_HOST ?? process.env.KIMI_OAUTH_HOST ?? DEFAULT_OAUTH_HOST;
|
|
966
|
-
return host.replace(/\/+$/, "");
|
|
967
|
-
}
|
|
968
|
-
function kimiCodeBaseUrl() {
|
|
969
|
-
return (process.env.KIMI_CODE_BASE_URL ?? DEFAULT_CODING_BASE_URL).replace(/\/+$/, "");
|
|
970
|
-
}
|
|
971
|
-
function kimiVersion() {
|
|
972
|
-
const v = process.env.KIMI_CODE_VERSION ?? DEFAULT_KIMI_VERSION;
|
|
973
|
-
return asciiHeader(v, DEFAULT_KIMI_VERSION);
|
|
974
|
-
}
|
|
975
|
-
function asciiHeader(value, fallback = "unknown") {
|
|
976
|
-
const cleaned = value.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
977
|
-
return cleaned.length > 0 ? cleaned : fallback;
|
|
978
|
-
}
|
|
979
|
-
function macOsProductVersion() {
|
|
980
|
-
try {
|
|
981
|
-
const version = (0, import_node_child_process.execFileSync)("/usr/bin/sw_vers", ["-productVersion"], {
|
|
982
|
-
encoding: "utf-8",
|
|
983
|
-
timeout: 1e3
|
|
984
|
-
}).trim();
|
|
985
|
-
return version.length > 0 ? version : void 0;
|
|
986
|
-
} catch {
|
|
987
|
-
return void 0;
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
function deviceModel() {
|
|
991
|
-
const os2 = (0, import_node_os2.type)();
|
|
992
|
-
const version = (0, import_node_os2.release)();
|
|
993
|
-
const osArch = (0, import_node_os2.arch)();
|
|
994
|
-
if (os2 === "Darwin") return `macOS ${macOsProductVersion() ?? version} ${osArch}`;
|
|
995
|
-
if (os2 === "Windows_NT") return `Windows ${version} ${osArch}`;
|
|
996
|
-
return `${os2} ${version} ${osArch}`.trim();
|
|
997
|
-
}
|
|
998
|
-
function deviceId() {
|
|
999
|
-
const idPath = import_node_path4.default.join(getAppPaths().agentDir, "kimi_device_id");
|
|
1000
|
-
if ((0, import_node_fs2.existsSync)(idPath)) {
|
|
1001
|
-
try {
|
|
1002
|
-
const text = (0, import_node_fs2.readFileSync)(idPath, "utf-8").trim();
|
|
1003
|
-
if (text.length > 0) return text;
|
|
1004
|
-
} catch {
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
const id = (0, import_node_crypto5.randomUUID)();
|
|
1008
|
-
try {
|
|
1009
|
-
(0, import_node_fs2.mkdirSync)(getAppPaths().agentDir, { recursive: true, mode: 448 });
|
|
1010
|
-
(0, import_node_fs2.writeFileSync)(idPath, id, { encoding: "utf-8", mode: 384 });
|
|
1011
|
-
} catch {
|
|
1012
|
-
}
|
|
1013
|
-
return id;
|
|
1014
|
-
}
|
|
1015
|
-
function deviceHeaders() {
|
|
1016
|
-
return {
|
|
1017
|
-
"X-Msh-Platform": KIMI_PLATFORM,
|
|
1018
|
-
"X-Msh-Version": kimiVersion(),
|
|
1019
|
-
"X-Msh-Device-Name": asciiHeader((0, import_node_os2.hostname)()),
|
|
1020
|
-
"X-Msh-Device-Model": asciiHeader(deviceModel()),
|
|
1021
|
-
"X-Msh-Os-Version": asciiHeader((0, import_node_os2.release)()),
|
|
1022
|
-
"X-Msh-Device-Id": deviceId()
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
function kimiCodingHeaders() {
|
|
1026
|
-
return {
|
|
1027
|
-
"User-Agent": `kimi-code-cli/${kimiVersion()}`,
|
|
1028
|
-
...deviceHeaders()
|
|
1029
|
-
};
|
|
1030
|
-
}
|
|
1031
|
-
function isKimiCodingEndpoint(baseUrl) {
|
|
1032
|
-
if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
|
|
1033
|
-
const normalized = baseUrl.replace(/\/+$/, "");
|
|
1034
|
-
return normalized === kimiCodeBaseUrl() || /(^|\.)kimi\.com/i.test(normalized);
|
|
1035
|
-
}
|
|
1036
|
-
async function postForm(endpoint, params) {
|
|
1037
|
-
const response = await fetch(`${oauthHost()}${endpoint}`, {
|
|
1038
|
-
method: "POST",
|
|
1039
|
-
headers: {
|
|
1040
|
-
...deviceHeaders(),
|
|
1041
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
1042
|
-
Accept: "application/json"
|
|
1043
|
-
},
|
|
1044
|
-
body: new URLSearchParams(params).toString()
|
|
1045
|
-
});
|
|
1046
|
-
let data = {};
|
|
1047
|
-
try {
|
|
1048
|
-
const parsed = await response.json();
|
|
1049
|
-
if (parsed && typeof parsed === "object") data = parsed;
|
|
1050
|
-
} catch {
|
|
1051
|
-
}
|
|
1052
|
-
return { status: response.status, data };
|
|
1053
|
-
}
|
|
1054
|
-
function errorDetail(data) {
|
|
1055
|
-
const desc = data.error_description ?? data.message ?? data.error;
|
|
1056
|
-
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
1057
|
-
}
|
|
1058
|
-
function credsFromTokenResponse(data, opts) {
|
|
1059
|
-
const accessToken = data.access_token;
|
|
1060
|
-
const responseRefreshToken = data.refresh_token;
|
|
1061
|
-
const expiresIn = Number(data.expires_in);
|
|
1062
|
-
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
1063
|
-
throw new Error("Kimi OAuth response missing access_token.");
|
|
1064
|
-
}
|
|
1065
|
-
const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
|
|
1066
|
-
if (refreshToken.length === 0) {
|
|
1067
|
-
throw new Error("Kimi OAuth response missing refresh_token.");
|
|
1068
|
-
}
|
|
1069
|
-
if (!Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
1070
|
-
throw new Error("Kimi OAuth response missing or invalid expires_in.");
|
|
1071
|
-
}
|
|
1072
|
-
return {
|
|
1073
|
-
accessToken,
|
|
1074
|
-
refreshToken,
|
|
1075
|
-
expiresAt: Date.now() + expiresIn * 1e3,
|
|
1076
|
-
baseUrl: kimiCodeBaseUrl()
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
async function requestDeviceAuthorization() {
|
|
1080
|
-
const { status, data } = await postForm("/api/oauth/device_authorization", {
|
|
1081
|
-
client_id: CLIENT_ID3
|
|
1082
|
-
});
|
|
1083
|
-
if (status !== 200) {
|
|
1084
|
-
throw new Error(`Kimi device authorization failed (${status}): ${errorDetail(data)}`);
|
|
1085
|
-
}
|
|
1086
|
-
const userCode = data.user_code;
|
|
1087
|
-
const deviceCode = data.device_code;
|
|
1088
|
-
const verificationUriComplete = data.verification_uri_complete;
|
|
1089
|
-
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
1090
|
-
throw new Error("Kimi device authorization response missing user_code/device_code.");
|
|
1091
|
-
}
|
|
1092
|
-
return {
|
|
1093
|
-
userCode,
|
|
1094
|
-
deviceCode,
|
|
1095
|
-
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
1096
|
-
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
1097
|
-
interval: Number(data.interval ?? 5) || 5
|
|
1098
|
-
};
|
|
1099
|
-
}
|
|
1100
|
-
async function pollDeviceToken(deviceCode) {
|
|
1101
|
-
const { status, data } = await postForm("/api/oauth/token", {
|
|
1102
|
-
client_id: CLIENT_ID3,
|
|
1103
|
-
device_code: deviceCode,
|
|
1104
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1105
|
-
});
|
|
1106
|
-
if (status === 200 && typeof data.access_token === "string") {
|
|
1107
|
-
return { kind: "success", creds: credsFromTokenResponse(data) };
|
|
1108
|
-
}
|
|
1109
|
-
if (status >= 500) {
|
|
1110
|
-
throw new Error(`Kimi token polling server error (${status}): ${errorDetail(data)}`);
|
|
1111
|
-
}
|
|
1112
|
-
const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
|
|
1113
|
-
switch (errorCode) {
|
|
1114
|
-
case "authorization_pending":
|
|
1115
|
-
return { kind: "pending" };
|
|
1116
|
-
case "slow_down":
|
|
1117
|
-
return { kind: "slow_down" };
|
|
1118
|
-
case "expired_token":
|
|
1119
|
-
return { kind: "expired" };
|
|
1120
|
-
case "access_denied":
|
|
1121
|
-
return { kind: "denied" };
|
|
1122
|
-
default:
|
|
1123
|
-
throw new Error(`Kimi token polling failed (${status}): ${errorDetail(data)}`);
|
|
1161
|
+
try {
|
|
1162
|
+
return await codeAssistRequest(getCodeAssistMethodUrl(method), accessToken, method, {
|
|
1163
|
+
method: "POST",
|
|
1164
|
+
body: JSON.stringify(body)
|
|
1165
|
+
});
|
|
1166
|
+
} catch (err) {
|
|
1167
|
+
if (!(err instanceof CodeAssistHttpError) || attempt === CODE_ASSIST_POST_RETRIES || !shouldRetryCodeAssistStatus(err.status)) {
|
|
1168
|
+
throw err;
|
|
1169
|
+
}
|
|
1170
|
+
lastError = err;
|
|
1171
|
+
}
|
|
1172
|
+
await new Promise((resolve) => setTimeout(resolve, CODE_ASSIST_POST_RETRY_DELAY_MS));
|
|
1124
1173
|
}
|
|
1174
|
+
throw lastError ?? new Error(`Gemini Code Assist ${method} failed.`);
|
|
1125
1175
|
}
|
|
1126
|
-
function
|
|
1127
|
-
return
|
|
1128
|
-
|
|
1176
|
+
async function codeAssistGet(accessToken, operationName) {
|
|
1177
|
+
return codeAssistRequest(getCodeAssistOperationUrl(operationName), accessToken, "operation", {
|
|
1178
|
+
method: "GET"
|
|
1129
1179
|
});
|
|
1130
1180
|
}
|
|
1131
|
-
async function
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
);
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
let interval = Math.max(auth.interval, 1);
|
|
1140
|
-
while (Date.now() < deadline) {
|
|
1141
|
-
await sleep(interval * 1e3);
|
|
1142
|
-
const result = await pollDeviceToken(auth.deviceCode);
|
|
1143
|
-
if (result.kind === "success") return result.creds;
|
|
1144
|
-
if (result.kind === "denied") {
|
|
1145
|
-
throw new Error("Kimi authorization was denied.");
|
|
1146
|
-
}
|
|
1147
|
-
if (result.kind === "expired") {
|
|
1148
|
-
throw new Error("Kimi device code expired. Please run login again.");
|
|
1149
|
-
}
|
|
1150
|
-
if (result.kind === "slow_down") {
|
|
1151
|
-
interval += 5;
|
|
1152
|
-
}
|
|
1181
|
+
async function codeAssistRequest(url, accessToken, label, init) {
|
|
1182
|
+
const response = await fetch(url, {
|
|
1183
|
+
...init,
|
|
1184
|
+
headers: codeAssistHeaders(accessToken)
|
|
1185
|
+
});
|
|
1186
|
+
if (!response.ok) {
|
|
1187
|
+
const text = await response.text();
|
|
1188
|
+
throw new CodeAssistHttpError(label, response.status, text);
|
|
1153
1189
|
}
|
|
1154
|
-
|
|
1190
|
+
return await response.json();
|
|
1155
1191
|
}
|
|
1156
|
-
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1192
|
+
function getCodeAssistBaseUrl() {
|
|
1193
|
+
const endpoint = process.env.CODE_ASSIST_ENDPOINT ?? CODE_ASSIST_BASE_URL;
|
|
1194
|
+
const version = process.env.CODE_ASSIST_API_VERSION || CODE_ASSIST_API_VERSION;
|
|
1195
|
+
return `${endpoint}/${version}`;
|
|
1196
|
+
}
|
|
1197
|
+
function getCodeAssistMethodUrl(method) {
|
|
1198
|
+
return `${getCodeAssistBaseUrl()}:${method}`;
|
|
1199
|
+
}
|
|
1200
|
+
function getCodeAssistOperationUrl(operationName) {
|
|
1201
|
+
return `${getCodeAssistBaseUrl()}/${operationName}`;
|
|
1202
|
+
}
|
|
1203
|
+
function shouldRetryCodeAssistStatus(status) {
|
|
1204
|
+
return status === 429 || status === 499 || status >= 500 && status <= 599;
|
|
1205
|
+
}
|
|
1206
|
+
function isVpcScAffectedError(error) {
|
|
1207
|
+
try {
|
|
1208
|
+
const parsed = JSON.parse(error.body);
|
|
1209
|
+
if (!parsed || typeof parsed !== "object" || !("error" in parsed)) return false;
|
|
1210
|
+
const details = parsed.error?.details;
|
|
1211
|
+
return Array.isArray(details) ? details.some(
|
|
1212
|
+
(detail) => detail != null && typeof detail === "object" && "reason" in detail && detail.reason === VPC_SC_REASON
|
|
1213
|
+
) : false;
|
|
1214
|
+
} catch {
|
|
1215
|
+
return false;
|
|
1164
1216
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1217
|
+
}
|
|
1218
|
+
function codeAssistHeaders(accessToken) {
|
|
1219
|
+
return {
|
|
1220
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1221
|
+
"Content-Type": "application/json",
|
|
1222
|
+
"User-Agent": "google-gemini-cli",
|
|
1223
|
+
"X-Goog-Api-Client": "gemini-cli/0.0.0"
|
|
1224
|
+
};
|
|
1167
1225
|
}
|
|
1168
1226
|
|
|
1169
1227
|
// src/file-lock.ts
|
|
@@ -1231,7 +1289,34 @@ function isAlive(pid) {
|
|
|
1231
1289
|
// src/auth-storage.ts
|
|
1232
1290
|
var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
|
|
1233
1291
|
var XIAOMI_CREDITS_KEY = "xiaomi-credits";
|
|
1234
|
-
var
|
|
1292
|
+
var LOCAL_AUTH_KEY_PREFIX = "local:";
|
|
1293
|
+
var LOCAL_CREDENTIAL_LIFETIME_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
|
|
1294
|
+
function activeBaseUrlEntry(data, provider) {
|
|
1295
|
+
if (provider === "moonshot") {
|
|
1296
|
+
const oauth = data[MOONSHOT_OAUTH_KEY];
|
|
1297
|
+
if (oauth) {
|
|
1298
|
+
const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
|
|
1299
|
+
if (Date.now() < exhaustedUntil && data["moonshot"]) return data["moonshot"];
|
|
1300
|
+
return oauth;
|
|
1301
|
+
}
|
|
1302
|
+
return data["moonshot"];
|
|
1303
|
+
}
|
|
1304
|
+
return data[provider];
|
|
1305
|
+
}
|
|
1306
|
+
function readStoredBaseUrlSync(authFile, provider) {
|
|
1307
|
+
try {
|
|
1308
|
+
const data = JSON.parse((0, import_node_fs3.readFileSync)(authFile, "utf-8"));
|
|
1309
|
+
return activeBaseUrlEntry(data, provider)?.baseUrl;
|
|
1310
|
+
} catch {
|
|
1311
|
+
return void 0;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
var MIN_REFRESH_THRESHOLD_MS = 3e5;
|
|
1315
|
+
var REFRESH_THRESHOLD_RATIO = 0.5;
|
|
1316
|
+
function refreshThresholdMs(creds) {
|
|
1317
|
+
const lifetimeMs = (creds.expiresIn ?? 0) * 1e3;
|
|
1318
|
+
return Math.max(MIN_REFRESH_THRESHOLD_MS, lifetimeMs * REFRESH_THRESHOLD_RATIO);
|
|
1319
|
+
}
|
|
1235
1320
|
var USAGE_EXHAUSTED_DEFAULT_MS = 15 * 60 * 1e3;
|
|
1236
1321
|
var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
|
|
1237
1322
|
"glm",
|
|
@@ -1241,7 +1326,9 @@ var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
|
|
|
1241
1326
|
"deepseek",
|
|
1242
1327
|
"openrouter",
|
|
1243
1328
|
"sakana",
|
|
1244
|
-
"xai"
|
|
1329
|
+
"xai",
|
|
1330
|
+
// Local endpoints: a fixed (usually placeholder) key, never refreshable.
|
|
1331
|
+
"local"
|
|
1245
1332
|
]);
|
|
1246
1333
|
var AuthStorage = class {
|
|
1247
1334
|
data = {};
|
|
@@ -1290,8 +1377,33 @@ var AuthStorage = class {
|
|
|
1290
1377
|
if (provider === "xiaomi") {
|
|
1291
1378
|
return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
|
|
1292
1379
|
}
|
|
1380
|
+
if (provider === "local") {
|
|
1381
|
+
return Object.keys(this.data).some((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX));
|
|
1382
|
+
}
|
|
1293
1383
|
return Boolean(this.data[provider]);
|
|
1294
1384
|
}
|
|
1385
|
+
/** Endpoint ids that currently have a `local:<id>` credential stored. */
|
|
1386
|
+
async listLocalEndpointIds() {
|
|
1387
|
+
await this.ensureLoaded();
|
|
1388
|
+
return Object.keys(this.data).filter((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX)).map((key) => key.slice(LOCAL_AUTH_KEY_PREFIX.length));
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Write (or refresh) the credential for one local endpoint. The `baseUrl` is
|
|
1392
|
+
* what `effectiveBaseUrl` later picks up, and `accessToken` is the endpoint's
|
|
1393
|
+
* key — a placeholder for the servers that ignore it.
|
|
1394
|
+
*/
|
|
1395
|
+
async setLocalEndpoint(endpointId, baseUrl, apiKey) {
|
|
1396
|
+
await this.setCredentials(`${LOCAL_AUTH_KEY_PREFIX}${endpointId}`, {
|
|
1397
|
+
accessToken: apiKey && apiKey.length > 0 ? apiKey : "local",
|
|
1398
|
+
refreshToken: "",
|
|
1399
|
+
expiresAt: Date.now() + LOCAL_CREDENTIAL_LIFETIME_MS,
|
|
1400
|
+
baseUrl
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
/** Remove one local endpoint's credential. No-op when it isn't stored. */
|
|
1404
|
+
async removeLocalEndpoint(endpointId) {
|
|
1405
|
+
await this.clearCredentials(`${LOCAL_AUTH_KEY_PREFIX}${endpointId}`);
|
|
1406
|
+
}
|
|
1295
1407
|
/**
|
|
1296
1408
|
* True if the active credential for `provider` is a static API key with no
|
|
1297
1409
|
* refresh mechanism. For `moonshot` this is only true when the Kimi OAuth
|
|
@@ -1306,6 +1418,16 @@ var AuthStorage = class {
|
|
|
1306
1418
|
}
|
|
1307
1419
|
return STATIC_API_KEY_PROVIDERS.has(provider);
|
|
1308
1420
|
}
|
|
1421
|
+
/**
|
|
1422
|
+
* The base URL on the credential that is active right now, if any.
|
|
1423
|
+
* Synchronous — call only after load()/resolveCredentials() populated the
|
|
1424
|
+
* snapshot. For `moonshot` this is the Kimi For Coding URL whenever the
|
|
1425
|
+
* OAuth entry is the one resolveCredentials would serve (i.e. not currently
|
|
1426
|
+
* usage-exhausted with an API key configured).
|
|
1427
|
+
*/
|
|
1428
|
+
getStoredBaseUrl(provider) {
|
|
1429
|
+
return activeBaseUrlEntry(this.data, provider)?.baseUrl;
|
|
1430
|
+
}
|
|
1309
1431
|
async load() {
|
|
1310
1432
|
await withFileLock(this.filePath, async () => {
|
|
1311
1433
|
try {
|
|
@@ -1337,27 +1459,46 @@ var AuthStorage = class {
|
|
|
1337
1459
|
/**
|
|
1338
1460
|
* Force a re-read from disk, discarding the in-memory cache. Needed when
|
|
1339
1461
|
* another process mutates the auth file out-of-band — e.g. the desktop app
|
|
1340
|
-
* writes API keys natively
|
|
1341
|
-
* this instance, so a long-lived daemon's cache would otherwise stay stale and
|
|
1342
|
-
* never see a newly added provider key.
|
|
1462
|
+
* writes API keys natively without going through this instance.
|
|
1343
1463
|
*/
|
|
1344
1464
|
async reload() {
|
|
1345
1465
|
this.loaded = false;
|
|
1346
1466
|
await this.load();
|
|
1347
1467
|
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Apply one provider-scoped mutation to the latest on-disk snapshot.
|
|
1470
|
+
* AuthStorage instances live in every app session/process, so writing this
|
|
1471
|
+
* instance's cached snapshot can erase credentials another instance just
|
|
1472
|
+
* added. The file lock only serializes writers; the re-read prevents stale
|
|
1473
|
+
* full-file overwrites.
|
|
1474
|
+
*/
|
|
1475
|
+
async mutateLatest(mutator) {
|
|
1476
|
+
await this.ensureLoaded();
|
|
1477
|
+
await withFileLock(this.filePath, async () => {
|
|
1478
|
+
const latest = await readAuthData(this.filePath);
|
|
1479
|
+
mutator(latest);
|
|
1480
|
+
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1481
|
+
this.data = latest;
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
async reloadLatest() {
|
|
1485
|
+
await withFileLock(this.filePath, async () => {
|
|
1486
|
+
this.data = await readAuthData(this.filePath);
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1348
1489
|
async getCredentials(provider) {
|
|
1349
1490
|
await this.ensureLoaded();
|
|
1350
1491
|
return this.data[provider];
|
|
1351
1492
|
}
|
|
1352
1493
|
async setCredentials(provider, creds) {
|
|
1353
|
-
await this.
|
|
1354
|
-
|
|
1355
|
-
|
|
1494
|
+
await this.mutateLatest((latest) => {
|
|
1495
|
+
latest[provider] = creds;
|
|
1496
|
+
});
|
|
1356
1497
|
}
|
|
1357
1498
|
async clearCredentials(provider) {
|
|
1358
|
-
await this.
|
|
1359
|
-
|
|
1360
|
-
|
|
1499
|
+
await this.mutateLatest((latest) => {
|
|
1500
|
+
delete latest[provider];
|
|
1501
|
+
});
|
|
1361
1502
|
}
|
|
1362
1503
|
/**
|
|
1363
1504
|
* Mark the credential stored under `storageKey` as usage-exhausted until
|
|
@@ -1371,12 +1512,15 @@ var AuthStorage = class {
|
|
|
1371
1512
|
* nothing is stored under `storageKey`.
|
|
1372
1513
|
*/
|
|
1373
1514
|
async markUsageExhausted(storageKey, resetsAt) {
|
|
1374
|
-
await this.ensureLoaded();
|
|
1375
|
-
const creds = this.data[storageKey];
|
|
1376
|
-
if (!creds) return;
|
|
1377
1515
|
const until = resetsAt !== void 0 && resetsAt * 1e3 > Date.now() ? resetsAt * 1e3 : Date.now() + USAGE_EXHAUSTED_DEFAULT_MS;
|
|
1378
|
-
|
|
1379
|
-
await this.
|
|
1516
|
+
let marked = false;
|
|
1517
|
+
await this.mutateLatest((latest) => {
|
|
1518
|
+
const creds = latest[storageKey];
|
|
1519
|
+
if (!creds) return;
|
|
1520
|
+
creds.usageExhaustedUntil = until;
|
|
1521
|
+
marked = true;
|
|
1522
|
+
});
|
|
1523
|
+
if (!marked) return;
|
|
1380
1524
|
log(
|
|
1381
1525
|
"WARN",
|
|
1382
1526
|
"auth",
|
|
@@ -1384,8 +1528,11 @@ var AuthStorage = class {
|
|
|
1384
1528
|
);
|
|
1385
1529
|
}
|
|
1386
1530
|
async clearAll() {
|
|
1387
|
-
this.
|
|
1388
|
-
await this.
|
|
1531
|
+
await this.ensureLoaded();
|
|
1532
|
+
await withFileLock(this.filePath, async () => {
|
|
1533
|
+
this.data = {};
|
|
1534
|
+
await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
|
|
1535
|
+
});
|
|
1389
1536
|
}
|
|
1390
1537
|
/**
|
|
1391
1538
|
* Returns valid credentials, auto-refreshing if expired.
|
|
@@ -1395,6 +1542,10 @@ var AuthStorage = class {
|
|
|
1395
1542
|
*/
|
|
1396
1543
|
async resolveCredentials(provider, opts) {
|
|
1397
1544
|
await this.ensureLoaded();
|
|
1545
|
+
const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : provider === "moonshot" ? [MOONSHOT_OAUTH_KEY, "moonshot"] : [provider];
|
|
1546
|
+
if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
|
|
1547
|
+
await this.reloadLatest();
|
|
1548
|
+
}
|
|
1398
1549
|
if (opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider)) {
|
|
1399
1550
|
for (const key of opts.storageKeys) {
|
|
1400
1551
|
const creds2 = this.data[key];
|
|
@@ -1435,47 +1586,50 @@ var AuthStorage = class {
|
|
|
1435
1586
|
if (STATIC_API_KEY_PROVIDERS.has(provider)) {
|
|
1436
1587
|
return creds;
|
|
1437
1588
|
}
|
|
1438
|
-
if (!opts?.forceRefresh && Date.now() < creds.expiresAt -
|
|
1589
|
+
if (!opts?.forceRefresh && Date.now() < creds.expiresAt - refreshThresholdMs(creds)) {
|
|
1439
1590
|
return creds;
|
|
1440
1591
|
}
|
|
1441
1592
|
const existing = this.refreshLocks.get(provider);
|
|
1442
1593
|
if (existing) return existing;
|
|
1443
1594
|
const refreshPromise = withFileLock(this.filePath, async () => {
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1595
|
+
const latest = await readAuthData(this.filePath);
|
|
1596
|
+
const latestCreds = latest[provider];
|
|
1597
|
+
if (!latestCreds) {
|
|
1598
|
+
this.data = latest;
|
|
1599
|
+
throw new NotLoggedInError(provider);
|
|
1600
|
+
}
|
|
1601
|
+
const credentialWasReplaced = latestCreds.accessToken !== creds.accessToken || latestCreds.refreshToken !== creds.refreshToken || latestCreds.expiresAt !== creds.expiresAt;
|
|
1602
|
+
if (credentialWasReplaced || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - refreshThresholdMs(latestCreds)) {
|
|
1603
|
+
this.data = latest;
|
|
1604
|
+
return latestCreds;
|
|
1453
1605
|
}
|
|
1454
1606
|
const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
|
|
1455
1607
|
let refreshed;
|
|
1456
1608
|
try {
|
|
1457
|
-
refreshed = await refreshFn(
|
|
1609
|
+
refreshed = await refreshFn(latestCreds.refreshToken);
|
|
1458
1610
|
} catch (err) {
|
|
1459
1611
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1460
1612
|
const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh/i.test(msg) || /unauthorized/i.test(msg);
|
|
1461
1613
|
if (isAuthFailure) {
|
|
1462
|
-
delete
|
|
1463
|
-
|
|
1614
|
+
delete latest[provider];
|
|
1615
|
+
this.data = latest;
|
|
1616
|
+
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1464
1617
|
throw new NotLoggedInError(provider);
|
|
1465
1618
|
}
|
|
1466
1619
|
throw err;
|
|
1467
1620
|
}
|
|
1468
|
-
if (!refreshed.accountId &&
|
|
1469
|
-
refreshed.accountId =
|
|
1621
|
+
if (!refreshed.accountId && latestCreds.accountId) {
|
|
1622
|
+
refreshed.accountId = latestCreds.accountId;
|
|
1470
1623
|
}
|
|
1471
|
-
if (!refreshed.projectId &&
|
|
1472
|
-
refreshed.projectId =
|
|
1624
|
+
if (!refreshed.projectId && latestCreds.projectId) {
|
|
1625
|
+
refreshed.projectId = latestCreds.projectId;
|
|
1473
1626
|
}
|
|
1474
|
-
if (!refreshed.baseUrl &&
|
|
1475
|
-
refreshed.baseUrl =
|
|
1627
|
+
if (!refreshed.baseUrl && latestCreds.baseUrl) {
|
|
1628
|
+
refreshed.baseUrl = latestCreds.baseUrl;
|
|
1476
1629
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1630
|
+
latest[provider] = refreshed;
|
|
1631
|
+
this.data = latest;
|
|
1632
|
+
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1479
1633
|
return refreshed;
|
|
1480
1634
|
});
|
|
1481
1635
|
this.refreshLocks.set(provider, refreshPromise);
|
|
@@ -1493,12 +1647,16 @@ var AuthStorage = class {
|
|
|
1493
1647
|
const creds = await this.resolveCredentials(provider);
|
|
1494
1648
|
return creds.accessToken;
|
|
1495
1649
|
}
|
|
1496
|
-
async save() {
|
|
1497
|
-
await withFileLock(this.filePath, async () => {
|
|
1498
|
-
await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
|
|
1499
|
-
});
|
|
1500
|
-
}
|
|
1501
1650
|
};
|
|
1651
|
+
async function readAuthData(filePath) {
|
|
1652
|
+
try {
|
|
1653
|
+
const content = await import_promises4.default.readFile(filePath, "utf-8");
|
|
1654
|
+
return JSON.parse(content);
|
|
1655
|
+
} catch (error) {
|
|
1656
|
+
if (error.code === "ENOENT") return {};
|
|
1657
|
+
throw error;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1502
1660
|
async function atomicWriteFile(filePath, content) {
|
|
1503
1661
|
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${import_node_crypto6.default.randomUUID().slice(0, 8)}.tmp`;
|
|
1504
1662
|
try {
|
|
@@ -1553,8 +1711,12 @@ var MODELS = [
|
|
|
1553
1711
|
// maxThinkingLevel: "max",
|
|
1554
1712
|
// },
|
|
1555
1713
|
{
|
|
1556
|
-
|
|
1557
|
-
|
|
1714
|
+
// Released 2026-07-24 — "For complex agentic coding and enterprise work".
|
|
1715
|
+
// Near-Fable capability at half the price ($5/$25 vs $10/$50). Adaptive
|
|
1716
|
+
// thinking with the full effort ladder (low→max, xhigh included); dateless
|
|
1717
|
+
// ID is the canonical pinned snapshot (post-4.6 naming scheme).
|
|
1718
|
+
id: "claude-opus-5",
|
|
1719
|
+
name: "Claude Opus 5",
|
|
1558
1720
|
provider: "anthropic",
|
|
1559
1721
|
contextWindow: 1e6,
|
|
1560
1722
|
maxOutputTokens: 128e3,
|
|
@@ -1591,7 +1753,8 @@ var MODELS = [
|
|
|
1591
1753
|
// ── OpenAI (Codex) ─────────────────────────────────────
|
|
1592
1754
|
// GPT-5.6 family — three agentic coding tiers launched July 2026. The public
|
|
1593
1755
|
// Responses API advertises a 1.05M context window; OpenAI's Codex product
|
|
1594
|
-
// catalog advertises
|
|
1756
|
+
// catalog advertises 272K on the ChatGPT OAuth route (corrected from the
|
|
1757
|
+
// initially advertised 372K — openai/codex PR #33972, Jul 18 2026 hotfix). All three take
|
|
1595
1758
|
// text+image input, freeform apply_patch, text+image web search, and parallel
|
|
1596
1759
|
// tool calls.
|
|
1597
1760
|
{
|
|
@@ -1603,7 +1766,7 @@ var MODELS = [
|
|
|
1603
1766
|
name: "GPT-5.6 Sol",
|
|
1604
1767
|
provider: "openai",
|
|
1605
1768
|
contextWindow: 105e4,
|
|
1606
|
-
codexContextWindow:
|
|
1769
|
+
codexContextWindow: 272e3,
|
|
1607
1770
|
maxOutputTokens: 128e3,
|
|
1608
1771
|
supportsThinking: true,
|
|
1609
1772
|
supportsImages: true,
|
|
@@ -1618,7 +1781,7 @@ var MODELS = [
|
|
|
1618
1781
|
name: "GPT-5.6 Terra",
|
|
1619
1782
|
provider: "openai",
|
|
1620
1783
|
contextWindow: 105e4,
|
|
1621
|
-
codexContextWindow:
|
|
1784
|
+
codexContextWindow: 272e3,
|
|
1622
1785
|
maxOutputTokens: 128e3,
|
|
1623
1786
|
supportsThinking: true,
|
|
1624
1787
|
supportsImages: true,
|
|
@@ -1633,7 +1796,7 @@ var MODELS = [
|
|
|
1633
1796
|
name: "GPT-5.6 Luna",
|
|
1634
1797
|
provider: "openai",
|
|
1635
1798
|
contextWindow: 105e4,
|
|
1636
|
-
codexContextWindow:
|
|
1799
|
+
codexContextWindow: 272e3,
|
|
1637
1800
|
maxOutputTokens: 128e3,
|
|
1638
1801
|
supportsThinking: true,
|
|
1639
1802
|
supportsImages: true,
|
|
@@ -1752,8 +1915,9 @@ var MODELS = [
|
|
|
1752
1915
|
},
|
|
1753
1916
|
// ── Moonshot (Kimi) ────────────────────────────────────
|
|
1754
1917
|
// K3 is Kimi's 2.8T-parameter flagship for long-horizon coding, knowledge
|
|
1755
|
-
// work, and deep reasoning.
|
|
1756
|
-
//
|
|
1918
|
+
// work, and deep reasoning. Its effort ladder is server-declared as
|
|
1919
|
+
// low/high/max on both the public API (default max) and the Kimi For Coding
|
|
1920
|
+
// OAuth endpoint (default high); thinking can also be fully disabled.
|
|
1757
1921
|
{
|
|
1758
1922
|
id: "kimi-k3",
|
|
1759
1923
|
name: "Kimi K3",
|
|
@@ -1938,14 +2102,30 @@ var MODELS = [
|
|
|
1938
2102
|
maxThinkingLevel: "high"
|
|
1939
2103
|
}
|
|
1940
2104
|
];
|
|
2105
|
+
var runtimeModels = /* @__PURE__ */ new Map();
|
|
2106
|
+
function registerRuntimeModels(models) {
|
|
2107
|
+
for (const model of models) runtimeModels.set(model.id, model);
|
|
2108
|
+
}
|
|
2109
|
+
function clearRuntimeModels(predicate) {
|
|
2110
|
+
if (!predicate) {
|
|
2111
|
+
runtimeModels.clear();
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
for (const [id, model] of runtimeModels) {
|
|
2115
|
+
if (predicate(model)) runtimeModels.delete(id);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
function getAllModels() {
|
|
2119
|
+
return [...MODELS, ...runtimeModels.values()];
|
|
2120
|
+
}
|
|
1941
2121
|
function getModel(id) {
|
|
1942
|
-
return MODELS.find((m) => m.id === id);
|
|
2122
|
+
return MODELS.find((m) => m.id === id) ?? runtimeModels.get(id);
|
|
1943
2123
|
}
|
|
1944
2124
|
function getModelsForProvider(provider) {
|
|
1945
|
-
return
|
|
2125
|
+
return getAllModels().filter((m) => m.provider === provider);
|
|
1946
2126
|
}
|
|
1947
2127
|
function getAuthStorageKeys(provider, modelId) {
|
|
1948
|
-
const model =
|
|
2128
|
+
const model = getAllModels().find((m) => m.id === modelId && m.provider === provider);
|
|
1949
2129
|
return model?.authStorageKeys ?? [provider];
|
|
1950
2130
|
}
|
|
1951
2131
|
function getAuthStorageKey(provider, modelId) {
|
|
@@ -1968,8 +2148,23 @@ function getDefaultModel(provider) {
|
|
|
1968
2148
|
if (provider === "openrouter") return MODELS.find((m) => m.id === "qwen/qwen3.6-plus");
|
|
1969
2149
|
if (provider === "sakana") return MODELS.find((m) => m.id === "fugu");
|
|
1970
2150
|
if (provider === "xai") return MODELS.find((m) => m.id === "grok-4.5");
|
|
2151
|
+
if (provider === "local") {
|
|
2152
|
+
return getModelsForProvider("local")[0] ?? PLACEHOLDER_LOCAL_MODEL;
|
|
2153
|
+
}
|
|
1971
2154
|
return MODELS.find((m) => m.id === "claude-sonnet-5");
|
|
1972
2155
|
}
|
|
2156
|
+
var PLACEHOLDER_LOCAL_MODEL = {
|
|
2157
|
+
id: "local/none/none",
|
|
2158
|
+
name: "No local model discovered",
|
|
2159
|
+
provider: "local",
|
|
2160
|
+
contextWindow: 8192,
|
|
2161
|
+
maxOutputTokens: 2048,
|
|
2162
|
+
supportsThinking: false,
|
|
2163
|
+
supportsImages: false,
|
|
2164
|
+
supportsVideo: false,
|
|
2165
|
+
costTier: "low",
|
|
2166
|
+
maxThinkingLevel: "high"
|
|
2167
|
+
};
|
|
1973
2168
|
function usesOpenAICodexTransport(options) {
|
|
1974
2169
|
return options?.provider === "openai" && Boolean(options.accountId);
|
|
1975
2170
|
}
|
|
@@ -1987,6 +2182,11 @@ function getContextWindow(modelId, options) {
|
|
|
1987
2182
|
function getMaxThinkingLevel(modelId) {
|
|
1988
2183
|
return getModel(modelId)?.maxThinkingLevel ?? "high";
|
|
1989
2184
|
}
|
|
2185
|
+
function getDefaultThinkingLevel(modelId, options) {
|
|
2186
|
+
const model = getModel(modelId);
|
|
2187
|
+
if (model?.id === "kimi-k3" && isKimiCodingEndpoint(options?.baseUrl)) return "high";
|
|
2188
|
+
return model?.maxThinkingLevel ?? "high";
|
|
2189
|
+
}
|
|
1990
2190
|
function getSummaryModel(provider, currentModelId) {
|
|
1991
2191
|
if (provider === "anthropic") {
|
|
1992
2192
|
return MODELS.find((m) => m.id === "claude-sonnet-5");
|
|
@@ -2014,7 +2214,7 @@ var OPENAI_GPT_56_THINKING_LEVELS = [
|
|
|
2014
2214
|
];
|
|
2015
2215
|
var SAKANA_THINKING_LEVELS = ["high", "xhigh"];
|
|
2016
2216
|
var XAI_THINKING_LEVELS = ["low", "medium", "high"];
|
|
2017
|
-
var
|
|
2217
|
+
var ANTHROPIC_XHIGH_THINKING_LEVELS = [
|
|
2018
2218
|
"low",
|
|
2019
2219
|
"medium",
|
|
2020
2220
|
"high",
|
|
@@ -2027,6 +2227,8 @@ var ANTHROPIC_ADAPTIVE_THINKING_LEVELS = [
|
|
|
2027
2227
|
"high",
|
|
2028
2228
|
"max"
|
|
2029
2229
|
];
|
|
2230
|
+
var MOONSHOT_K3_THINKING_LEVELS = ["low", "high", "max"];
|
|
2231
|
+
var LOCAL_THINKING_LEVELS = ["low", "medium", "high", "max"];
|
|
2030
2232
|
function isOpenAIGptModel(provider, model) {
|
|
2031
2233
|
return provider === "openai" && model.startsWith("gpt-");
|
|
2032
2234
|
}
|
|
@@ -2036,16 +2238,25 @@ function isSakanaModel(provider) {
|
|
|
2036
2238
|
function isXaiModel(provider) {
|
|
2037
2239
|
return provider === "xai";
|
|
2038
2240
|
}
|
|
2039
|
-
function
|
|
2040
|
-
return provider === "
|
|
2241
|
+
function isMoonshotK3Model(provider, model) {
|
|
2242
|
+
return provider === "moonshot" && model === "kimi-k3";
|
|
2243
|
+
}
|
|
2244
|
+
function isAnthropicXhighModel(provider, model) {
|
|
2245
|
+
return provider === "anthropic" && /opus-5|opus-4-8|opus-4-7/.test(model);
|
|
2041
2246
|
}
|
|
2042
2247
|
function isAnthropicAdaptiveModel(provider, model) {
|
|
2043
|
-
return provider === "anthropic" && /opus-4-8|opus-4-7|opus-4-6|sonnet-5|fable-5|mythos-5/.test(model);
|
|
2248
|
+
return provider === "anthropic" && /opus-5|opus-4-8|opus-4-7|opus-4-6|sonnet-5|fable-5|mythos-5/.test(model);
|
|
2044
2249
|
}
|
|
2045
2250
|
function getSupportedThinkingLevels(provider, model) {
|
|
2251
|
+
if (provider === "local") {
|
|
2252
|
+
const info = getModel(model);
|
|
2253
|
+
if (!info?.supportsThinking) return [];
|
|
2254
|
+
const maxIndex2 = LOCAL_THINKING_LEVELS.indexOf(info.maxThinkingLevel);
|
|
2255
|
+
return maxIndex2 === -1 ? LOCAL_THINKING_LEVELS.slice(0, 3) : LOCAL_THINKING_LEVELS.slice(0, maxIndex2 + 1);
|
|
2256
|
+
}
|
|
2046
2257
|
const maxLevel = getMaxThinkingLevel(model);
|
|
2047
2258
|
if (isAnthropicAdaptiveModel(provider, model)) {
|
|
2048
|
-
const levels2 =
|
|
2259
|
+
const levels2 = isAnthropicXhighModel(provider, model) ? ANTHROPIC_XHIGH_THINKING_LEVELS : ANTHROPIC_ADAPTIVE_THINKING_LEVELS;
|
|
2049
2260
|
const maxIndex2 = levels2.indexOf(maxLevel);
|
|
2050
2261
|
if (maxIndex2 === -1) return ["low", "medium", "high"];
|
|
2051
2262
|
return levels2.slice(0, maxIndex2 + 1);
|
|
@@ -2060,6 +2271,7 @@ function getSupportedThinkingLevels(provider, model) {
|
|
|
2060
2271
|
if (maxIndex2 === -1) return XAI_THINKING_LEVELS;
|
|
2061
2272
|
return XAI_THINKING_LEVELS.slice(0, maxIndex2 + 1);
|
|
2062
2273
|
}
|
|
2274
|
+
if (isMoonshotK3Model(provider, model)) return MOONSHOT_K3_THINKING_LEVELS;
|
|
2063
2275
|
if (!isOpenAIGptModel(provider, model)) return [maxLevel];
|
|
2064
2276
|
const levels = model.startsWith("gpt-5.6-") ? OPENAI_GPT_56_THINKING_LEVELS : OPENAI_GPT_THINKING_LEVELS;
|
|
2065
2277
|
const maxIndex = levels.indexOf(maxLevel);
|
|
@@ -2071,7 +2283,11 @@ function isThinkingLevelSupported(provider, model, level) {
|
|
|
2071
2283
|
}
|
|
2072
2284
|
function getNextThinkingLevel(provider, model, current) {
|
|
2073
2285
|
const supportedLevels = getSupportedThinkingLevels(provider, model);
|
|
2074
|
-
const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider) || isXaiModel(provider)
|
|
2286
|
+
const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider) || isXaiModel(provider) || isMoonshotK3Model(provider, model) || // Local servers take a real effort level, not just on/off: Ollama accepts
|
|
2287
|
+
// low/medium/high on `reasoning_effort` (verified against 0.32) and the
|
|
2288
|
+
// other OpenAI-compatible servers use the same three. A model that can't
|
|
2289
|
+
// reason at all already has no supported levels, so it never gets here.
|
|
2290
|
+
provider === "local";
|
|
2075
2291
|
if (!shouldCycleLevels) {
|
|
2076
2292
|
return current ? void 0 : supportedLevels[0];
|
|
2077
2293
|
}
|
|
@@ -2081,11 +2297,259 @@ function getNextThinkingLevel(provider, model, current) {
|
|
|
2081
2297
|
return supportedLevels[index + 1];
|
|
2082
2298
|
}
|
|
2083
2299
|
|
|
2300
|
+
// src/local-models.ts
|
|
2301
|
+
var DEFAULT_LOCAL_ENDPOINTS = [
|
|
2302
|
+
{ id: "ollama", label: "Ollama", baseUrl: "http://127.0.0.1:11434/v1", kind: "ollama" },
|
|
2303
|
+
{ id: "lmstudio", label: "LM Studio", baseUrl: "http://127.0.0.1:1234/v1", kind: "lmstudio" },
|
|
2304
|
+
{ id: "llamacpp", label: "llama.cpp", baseUrl: "http://127.0.0.1:8080/v1", kind: "llamacpp" },
|
|
2305
|
+
{ id: "vllm", label: "vLLM", baseUrl: "http://127.0.0.1:8000/v1", kind: "vllm" }
|
|
2306
|
+
];
|
|
2307
|
+
var FALLBACK_CONTEXT_WINDOW = 8192;
|
|
2308
|
+
var LOCAL_API_KEY_PLACEHOLDER = "local";
|
|
2309
|
+
var DEFAULT_PROBE_TIMEOUT_MS = 1200;
|
|
2310
|
+
var ENRICH_CONCURRENCY = 6;
|
|
2311
|
+
var CACHE_TTL_MS2 = 3e4;
|
|
2312
|
+
var NON_CHAT_ID_PATTERN = /(?:^|[-_/])(?:embed|embedding|rerank|reranker|bge|nomic-embed)/i;
|
|
2313
|
+
var LOCAL_ID_PREFIX = "local/";
|
|
2314
|
+
function formatLocalModelId(endpointId, rawId) {
|
|
2315
|
+
return `${LOCAL_ID_PREFIX}${endpointId}/${rawId}`;
|
|
2316
|
+
}
|
|
2317
|
+
function parseLocalModelId(id) {
|
|
2318
|
+
if (!id.startsWith(LOCAL_ID_PREFIX)) return void 0;
|
|
2319
|
+
const rest = id.slice(LOCAL_ID_PREFIX.length);
|
|
2320
|
+
const slash = rest.indexOf("/");
|
|
2321
|
+
if (slash <= 0 || slash === rest.length - 1) return void 0;
|
|
2322
|
+
return { endpointId: rest.slice(0, slash), rawId: rest.slice(slash + 1) };
|
|
2323
|
+
}
|
|
2324
|
+
function isLocalModelId(id) {
|
|
2325
|
+
return parseLocalModelId(id) !== void 0;
|
|
2326
|
+
}
|
|
2327
|
+
function localAuthStorageKey(endpointId) {
|
|
2328
|
+
return `local:${endpointId}`;
|
|
2329
|
+
}
|
|
2330
|
+
function endpointRoot(baseUrl) {
|
|
2331
|
+
return baseUrl.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
2332
|
+
}
|
|
2333
|
+
function authHeaders(endpoint) {
|
|
2334
|
+
return {
|
|
2335
|
+
Authorization: `Bearer ${endpoint.apiKey ?? LOCAL_API_KEY_PLACEHOLDER}`,
|
|
2336
|
+
Accept: "application/json"
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
async function fetchJson(url, endpoint, options) {
|
|
2340
|
+
try {
|
|
2341
|
+
const res = await fetchJsonOrThrow(url, endpoint, options);
|
|
2342
|
+
return res;
|
|
2343
|
+
} catch {
|
|
2344
|
+
return void 0;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
async function fetchJsonOrThrow(url, endpoint, { timeoutMs, signal, method = "GET", body }) {
|
|
2348
|
+
const controller = new AbortController();
|
|
2349
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2350
|
+
const onAbort = () => controller.abort();
|
|
2351
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2352
|
+
try {
|
|
2353
|
+
const res = await fetch(url, {
|
|
2354
|
+
method,
|
|
2355
|
+
signal: controller.signal,
|
|
2356
|
+
headers: body ? { ...authHeaders(endpoint), "Content-Type": "application/json" } : authHeaders(endpoint),
|
|
2357
|
+
...body ? { body: JSON.stringify(body) } : {}
|
|
2358
|
+
});
|
|
2359
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2360
|
+
return await res.json();
|
|
2361
|
+
} finally {
|
|
2362
|
+
clearTimeout(timer);
|
|
2363
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
function unreachableReason(endpoint, err) {
|
|
2367
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2368
|
+
if (/abort/i.test(message)) return `No response from ${endpoint.baseUrl} (timed out)`;
|
|
2369
|
+
if (/HTTP 401|HTTP 403/.test(message)) {
|
|
2370
|
+
return `${endpoint.baseUrl} rejected the API key (HTTP ${message.includes("401") ? 401 : 403})`;
|
|
2371
|
+
}
|
|
2372
|
+
if (/HTTP \d+/.test(message)) return `${endpoint.baseUrl} returned ${message}`;
|
|
2373
|
+
return `Not running at ${endpoint.baseUrl}`;
|
|
2374
|
+
}
|
|
2375
|
+
async function probeEndpoint(endpoint, { timeoutMs = DEFAULT_PROBE_TIMEOUT_MS, signal } = {}) {
|
|
2376
|
+
const listUrl = `${endpoint.baseUrl.replace(/\/+$/, "")}/models`;
|
|
2377
|
+
let list;
|
|
2378
|
+
try {
|
|
2379
|
+
list = await fetchJsonOrThrow(listUrl, endpoint, { timeoutMs, signal });
|
|
2380
|
+
} catch (err) {
|
|
2381
|
+
return { endpoint, reachable: false, reason: unreachableReason(endpoint, err), models: [] };
|
|
2382
|
+
}
|
|
2383
|
+
const entries = (list.data ?? []).filter(
|
|
2384
|
+
(entry) => typeof entry.id === "string" && entry.id.length > 0 && !NON_CHAT_ID_PATTERN.test(entry.id)
|
|
2385
|
+
);
|
|
2386
|
+
const models = await enrich(endpoint, entries, { timeoutMs, signal });
|
|
2387
|
+
log("INFO", "local-models", `Probed ${endpoint.label}`, {
|
|
2388
|
+
baseUrl: endpoint.baseUrl,
|
|
2389
|
+
models: String(models.length)
|
|
2390
|
+
});
|
|
2391
|
+
return { endpoint, reachable: true, models };
|
|
2392
|
+
}
|
|
2393
|
+
async function enrich(endpoint, entries, options) {
|
|
2394
|
+
if (endpoint.kind === "lmstudio") return enrichLmStudio(endpoint, entries, options);
|
|
2395
|
+
if (endpoint.kind === "ollama") return enrichOllama(endpoint, entries, options);
|
|
2396
|
+
if (endpoint.kind === "llamacpp") return enrichLlamaCpp(endpoint, entries, options);
|
|
2397
|
+
return entries.map((entry) => genericModel(endpoint, entry));
|
|
2398
|
+
}
|
|
2399
|
+
function genericModel(endpoint, entry) {
|
|
2400
|
+
const declared = typeof entry.max_model_len === "number" ? entry.max_model_len : void 0;
|
|
2401
|
+
return {
|
|
2402
|
+
rawId: entry.id,
|
|
2403
|
+
endpointId: endpoint.id,
|
|
2404
|
+
contextWindow: declared ?? FALLBACK_CONTEXT_WINDOW,
|
|
2405
|
+
contextWindowKnown: declared !== void 0,
|
|
2406
|
+
supportsTools: true,
|
|
2407
|
+
supportsImages: false,
|
|
2408
|
+
supportsThinking: false
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
async function enrichOllama(endpoint, entries, options) {
|
|
2412
|
+
const showUrl = `${endpointRoot(endpoint.baseUrl)}/api/show`;
|
|
2413
|
+
const enriched = await mapLimited(entries, ENRICH_CONCURRENCY, async (entry) => {
|
|
2414
|
+
const show = await fetchJson(showUrl, endpoint, {
|
|
2415
|
+
...options,
|
|
2416
|
+
method: "POST",
|
|
2417
|
+
body: { model: entry.id }
|
|
2418
|
+
});
|
|
2419
|
+
if (!show) return genericModel(endpoint, entry);
|
|
2420
|
+
const caps = show.capabilities ?? [];
|
|
2421
|
+
if (caps.includes("embedding") && !caps.includes("completion")) return void 0;
|
|
2422
|
+
const ctx = ollamaContextLength(show.model_info);
|
|
2423
|
+
return {
|
|
2424
|
+
rawId: entry.id,
|
|
2425
|
+
endpointId: endpoint.id,
|
|
2426
|
+
contextWindow: ctx ?? FALLBACK_CONTEXT_WINDOW,
|
|
2427
|
+
contextWindowKnown: ctx !== void 0,
|
|
2428
|
+
// Ollama reports capabilities honestly, so trust it here rather than
|
|
2429
|
+
// using the optimistic generic default.
|
|
2430
|
+
supportsTools: caps.includes("tools"),
|
|
2431
|
+
supportsImages: caps.includes("vision"),
|
|
2432
|
+
supportsThinking: caps.includes("thinking")
|
|
2433
|
+
};
|
|
2434
|
+
});
|
|
2435
|
+
return enriched.filter((model) => model !== void 0);
|
|
2436
|
+
}
|
|
2437
|
+
function ollamaContextLength(info) {
|
|
2438
|
+
if (!info) return void 0;
|
|
2439
|
+
for (const [key, value] of Object.entries(info)) {
|
|
2440
|
+
if (key.endsWith(".context_length") && typeof value === "number" && value > 0) return value;
|
|
2441
|
+
}
|
|
2442
|
+
return void 0;
|
|
2443
|
+
}
|
|
2444
|
+
async function enrichLmStudio(endpoint, entries, options) {
|
|
2445
|
+
const detail = await fetchJson(
|
|
2446
|
+
`${endpointRoot(endpoint.baseUrl)}/api/v0/models`,
|
|
2447
|
+
endpoint,
|
|
2448
|
+
options
|
|
2449
|
+
);
|
|
2450
|
+
if (!detail?.data) return entries.map((entry) => genericModel(endpoint, entry));
|
|
2451
|
+
const byId = new Map(detail.data.filter((m) => m.id).map((m) => [m.id, m]));
|
|
2452
|
+
const models = [];
|
|
2453
|
+
for (const entry of entries) {
|
|
2454
|
+
const info = byId.get(entry.id);
|
|
2455
|
+
if (info && info.type !== "llm" && info.type !== "vlm") continue;
|
|
2456
|
+
const ctx = info?.max_context_length;
|
|
2457
|
+
models.push({
|
|
2458
|
+
rawId: entry.id,
|
|
2459
|
+
endpointId: endpoint.id,
|
|
2460
|
+
contextWindow: typeof ctx === "number" && ctx > 0 ? ctx : FALLBACK_CONTEXT_WINDOW,
|
|
2461
|
+
contextWindowKnown: typeof ctx === "number" && ctx > 0,
|
|
2462
|
+
// LM Studio doesn't report tool support; it gates per-model at request time.
|
|
2463
|
+
supportsTools: true,
|
|
2464
|
+
supportsImages: info?.type === "vlm",
|
|
2465
|
+
supportsThinking: false,
|
|
2466
|
+
loaded: info?.state === "loaded"
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
return models;
|
|
2470
|
+
}
|
|
2471
|
+
async function enrichLlamaCpp(endpoint, entries, options) {
|
|
2472
|
+
const props = await fetchJson(
|
|
2473
|
+
`${endpointRoot(endpoint.baseUrl)}/props`,
|
|
2474
|
+
endpoint,
|
|
2475
|
+
options
|
|
2476
|
+
);
|
|
2477
|
+
const nCtx = props?.default_generation_settings?.n_ctx;
|
|
2478
|
+
const known = typeof nCtx === "number" && nCtx > 0;
|
|
2479
|
+
return entries.map((entry) => ({
|
|
2480
|
+
...genericModel(endpoint, entry),
|
|
2481
|
+
contextWindow: known ? nCtx : FALLBACK_CONTEXT_WINDOW,
|
|
2482
|
+
contextWindowKnown: known
|
|
2483
|
+
}));
|
|
2484
|
+
}
|
|
2485
|
+
async function mapLimited(items, limit, fn) {
|
|
2486
|
+
const results = new Array(items.length);
|
|
2487
|
+
let next = 0;
|
|
2488
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
2489
|
+
while (next < items.length) {
|
|
2490
|
+
const index = next++;
|
|
2491
|
+
results[index] = await fn(items[index]);
|
|
2492
|
+
}
|
|
2493
|
+
});
|
|
2494
|
+
await Promise.all(workers);
|
|
2495
|
+
return results;
|
|
2496
|
+
}
|
|
2497
|
+
function maxThinkingLevelFor(endpoint) {
|
|
2498
|
+
return endpoint.kind === "ollama" ? "max" : "high";
|
|
2499
|
+
}
|
|
2500
|
+
function toModelInfo(model, endpoint) {
|
|
2501
|
+
return {
|
|
2502
|
+
id: formatLocalModelId(model.endpointId, model.rawId),
|
|
2503
|
+
name: `${model.rawId} (${endpoint.label})`,
|
|
2504
|
+
provider: "local",
|
|
2505
|
+
contextWindow: model.contextWindow,
|
|
2506
|
+
// Leave real headroom for the prompt on small local windows.
|
|
2507
|
+
maxOutputTokens: Math.max(512, Math.min(4096, Math.floor(model.contextWindow / 4))),
|
|
2508
|
+
supportsThinking: model.supportsThinking,
|
|
2509
|
+
supportsImages: model.supportsImages,
|
|
2510
|
+
supportsVideo: false,
|
|
2511
|
+
costTier: "low",
|
|
2512
|
+
maxThinkingLevel: maxThinkingLevelFor(endpoint),
|
|
2513
|
+
authStorageKeys: [localAuthStorageKey(model.endpointId)]
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
var cache;
|
|
2517
|
+
function cacheKey(endpoints) {
|
|
2518
|
+
return endpoints.map((e) => `${e.id}@${e.baseUrl}`).join("|");
|
|
2519
|
+
}
|
|
2520
|
+
async function discoverLocalModels(endpoints = DEFAULT_LOCAL_ENDPOINTS, options = {}) {
|
|
2521
|
+
const key = cacheKey(endpoints);
|
|
2522
|
+
if (!options.force && cache && cache.key === key && Date.now() - cache.at < CACHE_TTL_MS2) {
|
|
2523
|
+
return cache.result;
|
|
2524
|
+
}
|
|
2525
|
+
const probes = await Promise.all(endpoints.map((endpoint) => probeEndpoint(endpoint, options)));
|
|
2526
|
+
const models = probes.flatMap(
|
|
2527
|
+
(probe) => probe.models.map((model) => toModelInfo(model, probe.endpoint))
|
|
2528
|
+
);
|
|
2529
|
+
const result = { probes, models };
|
|
2530
|
+
cache = { key, at: Date.now(), result };
|
|
2531
|
+
return result;
|
|
2532
|
+
}
|
|
2533
|
+
function clearLocalDiscoveryCache() {
|
|
2534
|
+
cache = void 0;
|
|
2535
|
+
}
|
|
2536
|
+
function findProbedModel(probes, modelId) {
|
|
2537
|
+
const parsed = parseLocalModelId(modelId);
|
|
2538
|
+
if (!parsed) return void 0;
|
|
2539
|
+
for (const probe of probes) {
|
|
2540
|
+
if (probe.endpoint.id !== parsed.endpointId) continue;
|
|
2541
|
+
const model = probe.models.find((m) => m.rawId === parsed.rawId);
|
|
2542
|
+
if (model) return { model, endpoint: probe.endpoint };
|
|
2543
|
+
}
|
|
2544
|
+
return void 0;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2084
2547
|
// src/provider-usage.ts
|
|
2085
2548
|
var SubscriptionUsageError = class extends Error {
|
|
2086
|
-
constructor(message, status) {
|
|
2549
|
+
constructor(message, status, retryAfterMs2) {
|
|
2087
2550
|
super(message);
|
|
2088
2551
|
this.status = status;
|
|
2552
|
+
this.retryAfterMs = retryAfterMs2;
|
|
2089
2553
|
this.name = "SubscriptionUsageError";
|
|
2090
2554
|
}
|
|
2091
2555
|
};
|
|
@@ -2135,12 +2599,20 @@ function normalizedCodexWindow(window, fallbackKind, now) {
|
|
|
2135
2599
|
resetsAt: codexResetAt(window, now)
|
|
2136
2600
|
};
|
|
2137
2601
|
}
|
|
2138
|
-
|
|
2602
|
+
function retryAfterMs(response, now) {
|
|
2603
|
+
const value = response.headers.get("retry-after")?.trim();
|
|
2604
|
+
if (!value) return void 0;
|
|
2605
|
+
if (/^\d+(?:\.\d+)?$/.test(value)) return Math.ceil(Number(value) * 1e3);
|
|
2606
|
+
const retryAt = Date.parse(value);
|
|
2607
|
+
return Number.isFinite(retryAt) ? Math.max(0, retryAt - now) : void 0;
|
|
2608
|
+
}
|
|
2609
|
+
async function readUsageResponse(response, now) {
|
|
2139
2610
|
const text = await response.text();
|
|
2140
2611
|
if (!response.ok) {
|
|
2141
2612
|
throw new SubscriptionUsageError(
|
|
2142
2613
|
`Subscription usage request failed with HTTP ${response.status}`,
|
|
2143
|
-
response.status
|
|
2614
|
+
response.status,
|
|
2615
|
+
retryAfterMs(response, now)
|
|
2144
2616
|
);
|
|
2145
2617
|
}
|
|
2146
2618
|
try {
|
|
@@ -2161,7 +2633,7 @@ async function fetchAnthropicUsage(credentials, fetchFn, signal, now) {
|
|
|
2161
2633
|
"User-Agent": "ezcoder"
|
|
2162
2634
|
}
|
|
2163
2635
|
});
|
|
2164
|
-
const data = await readUsageResponse(response);
|
|
2636
|
+
const data = await readUsageResponse(response, now());
|
|
2165
2637
|
const windows = [];
|
|
2166
2638
|
const currentPercent = clampPercent(data.five_hour?.utilization);
|
|
2167
2639
|
if (currentPercent !== void 0) {
|
|
@@ -2197,7 +2669,7 @@ async function fetchCodexUsage(credentials, fetchFn, signal, now) {
|
|
|
2197
2669
|
};
|
|
2198
2670
|
if (credentials.accountId) headers["ChatGPT-Account-Id"] = credentials.accountId;
|
|
2199
2671
|
const response = await fetchFn(CODEX_USAGE_URL, { method: "GET", signal, headers });
|
|
2200
|
-
const data = await readUsageResponse(response);
|
|
2672
|
+
const data = await readUsageResponse(response, now());
|
|
2201
2673
|
const windows = [
|
|
2202
2674
|
normalizedCodexWindow(data.rate_limit?.primary_window, "current", now()),
|
|
2203
2675
|
normalizedCodexWindow(data.rate_limit?.secondary_window, "weekly", now())
|
|
@@ -2213,12 +2685,97 @@ async function fetchCodexUsage(credentials, fetchFn, signal, now) {
|
|
|
2213
2685
|
fetchedAt: now()
|
|
2214
2686
|
};
|
|
2215
2687
|
}
|
|
2688
|
+
function kimiNumber(value) {
|
|
2689
|
+
if (typeof value === "string" && value.trim()) {
|
|
2690
|
+
const parsed = Number(value);
|
|
2691
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2692
|
+
}
|
|
2693
|
+
return finiteNumber(value);
|
|
2694
|
+
}
|
|
2695
|
+
function kimiUsagePercent(detail) {
|
|
2696
|
+
const used = kimiNumber(detail?.used);
|
|
2697
|
+
const limit = kimiNumber(detail?.limit);
|
|
2698
|
+
if (used === void 0 || limit === void 0 || limit <= 0) return void 0;
|
|
2699
|
+
return Math.min(100, Math.max(0, used / limit * 100));
|
|
2700
|
+
}
|
|
2701
|
+
function kimiWindowMinutes(window) {
|
|
2702
|
+
const duration = kimiNumber(window?.duration);
|
|
2703
|
+
if (duration === void 0 || duration <= 0) return void 0;
|
|
2704
|
+
switch (window?.timeUnit) {
|
|
2705
|
+
case "TIME_UNIT_SECOND":
|
|
2706
|
+
return duration / 60;
|
|
2707
|
+
case "TIME_UNIT_MINUTE":
|
|
2708
|
+
return duration;
|
|
2709
|
+
case "TIME_UNIT_HOUR":
|
|
2710
|
+
return duration * 60;
|
|
2711
|
+
case "TIME_UNIT_DAY":
|
|
2712
|
+
return duration * 24 * 60;
|
|
2713
|
+
default:
|
|
2714
|
+
return void 0;
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
async function fetchKimiUsage(credentials, fetchFn, signal, now) {
|
|
2718
|
+
const response = await fetchFn(`${kimiCodeBaseUrl()}/usages`, {
|
|
2719
|
+
method: "GET",
|
|
2720
|
+
signal,
|
|
2721
|
+
headers: {
|
|
2722
|
+
Authorization: "Bearer " + credentials.accessToken,
|
|
2723
|
+
Accept: "application/json",
|
|
2724
|
+
// The managed coding endpoint gates on the kimi-code-cli client identity,
|
|
2725
|
+
// same as model requests.
|
|
2726
|
+
...kimiCodingHeaders()
|
|
2727
|
+
}
|
|
2728
|
+
});
|
|
2729
|
+
const data = await readUsageResponse(response, now());
|
|
2730
|
+
const windows = [];
|
|
2731
|
+
const weeklyPercent = kimiUsagePercent(data.usage);
|
|
2732
|
+
if (weeklyPercent !== void 0) {
|
|
2733
|
+
windows.push({
|
|
2734
|
+
kind: "weekly",
|
|
2735
|
+
label: "Weekly",
|
|
2736
|
+
usedPercent: weeklyPercent,
|
|
2737
|
+
resetsAt: isoTimestamp(data.usage?.resetTime)
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
for (const limit of data.limits ?? []) {
|
|
2741
|
+
const minutes = kimiWindowMinutes(limit?.window);
|
|
2742
|
+
const percent = kimiUsagePercent(limit?.detail);
|
|
2743
|
+
if (minutes === void 0 || percent === void 0) continue;
|
|
2744
|
+
if (minutes >= 6 * 24 * 60) {
|
|
2745
|
+
if (!windows.some((window) => window.kind === "weekly")) {
|
|
2746
|
+
windows.push({
|
|
2747
|
+
kind: "weekly",
|
|
2748
|
+
label: "Weekly",
|
|
2749
|
+
usedPercent: percent,
|
|
2750
|
+
resetsAt: isoTimestamp(limit?.detail?.resetTime)
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
continue;
|
|
2754
|
+
}
|
|
2755
|
+
windows.push({
|
|
2756
|
+
kind: "current",
|
|
2757
|
+
label: `${Math.max(1, Math.round(minutes / 60))}-hour`,
|
|
2758
|
+
usedPercent: percent,
|
|
2759
|
+
resetsAt: isoTimestamp(limit?.detail?.resetTime)
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2762
|
+
windows.sort((left, right) => {
|
|
2763
|
+
if (left.kind === right.kind) return 0;
|
|
2764
|
+
return left.kind === "current" ? -1 : 1;
|
|
2765
|
+
});
|
|
2766
|
+
return {
|
|
2767
|
+
provider: "moonshot",
|
|
2768
|
+
displayName: "Kimi",
|
|
2769
|
+
windows,
|
|
2770
|
+
fetchedAt: now()
|
|
2771
|
+
};
|
|
2772
|
+
}
|
|
2216
2773
|
async function fetchSubscriptionUsage(provider, credentials, options = {}) {
|
|
2217
2774
|
const fetchFn = options.fetchFn ?? fetch;
|
|
2218
2775
|
const now = options.now ?? Date.now;
|
|
2219
2776
|
const signal = AbortSignal.timeout(options.timeoutMs ?? 8e3);
|
|
2220
2777
|
try {
|
|
2221
|
-
return provider === "anthropic" ? await fetchAnthropicUsage(credentials, fetchFn, signal, now) : await fetchCodexUsage(credentials, fetchFn, signal, now);
|
|
2778
|
+
return provider === "anthropic" ? await fetchAnthropicUsage(credentials, fetchFn, signal, now) : provider === "openai" ? await fetchCodexUsage(credentials, fetchFn, signal, now) : await fetchKimiUsage(credentials, fetchFn, signal, now);
|
|
2222
2779
|
} catch (error) {
|
|
2223
2780
|
if (error instanceof SubscriptionUsageError) throw error;
|
|
2224
2781
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2535,7 +3092,7 @@ async function transcribeVoice(fileUrl) {
|
|
|
2535
3092
|
|
|
2536
3093
|
// src/auto-update.ts
|
|
2537
3094
|
var import_node_child_process2 = require("child_process");
|
|
2538
|
-
var
|
|
3095
|
+
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
2539
3096
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
2540
3097
|
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
2541
3098
|
var FETCH_TIMEOUT_MS2 = 1e4;
|
|
@@ -2569,7 +3126,7 @@ function createAutoUpdater(config) {
|
|
|
2569
3126
|
}
|
|
2570
3127
|
function readState() {
|
|
2571
3128
|
try {
|
|
2572
|
-
const raw =
|
|
3129
|
+
const raw = import_node_fs4.default.readFileSync(stateFilePath(), "utf-8");
|
|
2573
3130
|
return JSON.parse(raw);
|
|
2574
3131
|
} catch {
|
|
2575
3132
|
return null;
|
|
@@ -2578,8 +3135,8 @@ function createAutoUpdater(config) {
|
|
|
2578
3135
|
function writeState(state) {
|
|
2579
3136
|
try {
|
|
2580
3137
|
const filePath = stateFilePath();
|
|
2581
|
-
|
|
2582
|
-
|
|
3138
|
+
import_node_fs4.default.mkdirSync(import_node_path5.default.dirname(filePath), { recursive: true, mode: 448 });
|
|
3139
|
+
import_node_fs4.default.writeFileSync(filePath, JSON.stringify(state));
|
|
2583
3140
|
} catch {
|
|
2584
3141
|
}
|
|
2585
3142
|
}
|
|
@@ -2704,19 +3261,30 @@ function createAutoUpdater(config) {
|
|
|
2704
3261
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2705
3262
|
0 && (module.exports = {
|
|
2706
3263
|
AuthStorage,
|
|
3264
|
+
DEFAULT_LOCAL_ENDPOINTS,
|
|
2707
3265
|
DEFAULT_MAX_VIDEO_BYTES,
|
|
3266
|
+
FALLBACK_CONTEXT_WINDOW,
|
|
3267
|
+
LOCAL_API_KEY_PLACEHOLDER,
|
|
3268
|
+
LOCAL_AUTH_KEY_PREFIX,
|
|
2708
3269
|
MODELS,
|
|
2709
3270
|
MOONSHOT_OAUTH_KEY,
|
|
2710
3271
|
NotLoggedInError,
|
|
2711
3272
|
SubscriptionUsageError,
|
|
2712
3273
|
TelegramBot,
|
|
2713
3274
|
XIAOMI_CREDITS_KEY,
|
|
3275
|
+
clearLocalDiscoveryCache,
|
|
3276
|
+
clearRuntimeModels,
|
|
2714
3277
|
closeLogger,
|
|
2715
3278
|
createAutoUpdater,
|
|
2716
3279
|
decodeOggOpus,
|
|
3280
|
+
discoverLocalModels,
|
|
2717
3281
|
downmixToMono,
|
|
3282
|
+
endpointRoot,
|
|
2718
3283
|
fetchSubscriptionUsage,
|
|
3284
|
+
findProbedModel,
|
|
3285
|
+
formatLocalModelId,
|
|
2719
3286
|
generatePKCE,
|
|
3287
|
+
getAllModels,
|
|
2720
3288
|
getAppPaths,
|
|
2721
3289
|
getAuthStorageKey,
|
|
2722
3290
|
getAuthStorageKeys,
|
|
@@ -2724,6 +3292,7 @@ function createAutoUpdater(config) {
|
|
|
2724
3292
|
getClaudeCodeVersion,
|
|
2725
3293
|
getContextWindow,
|
|
2726
3294
|
getDefaultModel,
|
|
3295
|
+
getDefaultThinkingLevel,
|
|
2727
3296
|
getFastModel,
|
|
2728
3297
|
getMaxThinkingLevel,
|
|
2729
3298
|
getModel,
|
|
@@ -2735,24 +3304,31 @@ function createAutoUpdater(config) {
|
|
|
2735
3304
|
getToolResultCharLimit,
|
|
2736
3305
|
getVideoByteLimit,
|
|
2737
3306
|
isKimiCodingEndpoint,
|
|
3307
|
+
isLocalModelId,
|
|
2738
3308
|
isLoggerOpen,
|
|
2739
3309
|
isModelLoaded,
|
|
2740
3310
|
isThinkingLevelSupported,
|
|
2741
3311
|
kimiCodeBaseUrl,
|
|
2742
3312
|
kimiCodingHeaders,
|
|
3313
|
+
localAuthStorageKey,
|
|
2743
3314
|
log,
|
|
2744
3315
|
loginAnthropic,
|
|
2745
3316
|
loginGemini,
|
|
2746
3317
|
loginKimi,
|
|
2747
3318
|
loginOpenAI,
|
|
2748
3319
|
openLog,
|
|
3320
|
+
parseLocalModelId,
|
|
3321
|
+
probeEndpoint,
|
|
3322
|
+
readStoredBaseUrlSync,
|
|
2749
3323
|
refreshAnthropicToken,
|
|
2750
3324
|
refreshGeminiToken,
|
|
2751
3325
|
refreshKimiToken,
|
|
2752
3326
|
refreshOpenAIToken,
|
|
2753
3327
|
registerLogCleanup,
|
|
3328
|
+
registerRuntimeModels,
|
|
2754
3329
|
resample,
|
|
2755
3330
|
setProgressCallback,
|
|
3331
|
+
toModelInfo,
|
|
2756
3332
|
transcribeVoice,
|
|
2757
3333
|
usesOpenAICodexTransport,
|
|
2758
3334
|
withFileLock
|