@prestyj/core 5.7.0 → 5.9.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-RQADK6HE.js → chunk-JXPW4JKV.js} +482 -288
- package/dist/chunk-JXPW4JKV.js.map +1 -0
- package/dist/index.cjs +672 -365
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -8
- package/dist/index.d.ts +48 -8
- package/dist/index.js +122 -8
- package/dist/index.js.map +1 -1
- package/dist/model-registry.cjs +91 -24
- package/dist/model-registry.cjs.map +1 -1
- package/dist/model-registry.d.cts +24 -2
- package/dist/model-registry.d.ts +24 -2
- package/dist/model-registry.js +5 -1
- package/package.json +2 -2
- package/dist/chunk-RQADK6HE.js.map +0 -1
|
@@ -2,8 +2,226 @@ import {
|
|
|
2
2
|
getAppPaths
|
|
3
3
|
} from "./chunk-WCIV25KV.js";
|
|
4
4
|
|
|
5
|
+
// src/oauth/kimi.ts
|
|
6
|
+
import { execFileSync } from "child_process";
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { arch, hostname, release, type } from "os";
|
|
10
|
+
import path from "path";
|
|
11
|
+
var CLIENT_ID = "17e5f671-d194-4dfb-9706-5516cb48c098";
|
|
12
|
+
var DEFAULT_OAUTH_HOST = "https://auth.kimi.com";
|
|
13
|
+
var DEFAULT_CODING_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
14
|
+
var KIMI_PLATFORM = "kimi_code_cli";
|
|
15
|
+
var DEFAULT_KIMI_VERSION = "1.0.11";
|
|
16
|
+
var DEVICE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
17
|
+
function oauthHost() {
|
|
18
|
+
const host = process.env.KIMI_CODE_OAUTH_HOST ?? process.env.KIMI_OAUTH_HOST ?? DEFAULT_OAUTH_HOST;
|
|
19
|
+
return host.replace(/\/+$/, "");
|
|
20
|
+
}
|
|
21
|
+
function kimiCodeBaseUrl() {
|
|
22
|
+
return (process.env.KIMI_CODE_BASE_URL ?? DEFAULT_CODING_BASE_URL).replace(/\/+$/, "");
|
|
23
|
+
}
|
|
24
|
+
function kimiVersion() {
|
|
25
|
+
const v = process.env.KIMI_CODE_VERSION ?? DEFAULT_KIMI_VERSION;
|
|
26
|
+
return asciiHeader(v, DEFAULT_KIMI_VERSION);
|
|
27
|
+
}
|
|
28
|
+
function asciiHeader(value, fallback = "unknown") {
|
|
29
|
+
const cleaned = value.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
30
|
+
return cleaned.length > 0 ? cleaned : fallback;
|
|
31
|
+
}
|
|
32
|
+
function macOsProductVersion() {
|
|
33
|
+
try {
|
|
34
|
+
const version = execFileSync("/usr/bin/sw_vers", ["-productVersion"], {
|
|
35
|
+
encoding: "utf-8",
|
|
36
|
+
timeout: 1e3
|
|
37
|
+
}).trim();
|
|
38
|
+
return version.length > 0 ? version : void 0;
|
|
39
|
+
} catch {
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function deviceModel() {
|
|
44
|
+
const os = type();
|
|
45
|
+
const version = release();
|
|
46
|
+
const osArch = arch();
|
|
47
|
+
if (os === "Darwin") return `macOS ${macOsProductVersion() ?? version} ${osArch}`;
|
|
48
|
+
if (os === "Windows_NT") return `Windows ${version} ${osArch}`;
|
|
49
|
+
return `${os} ${version} ${osArch}`.trim();
|
|
50
|
+
}
|
|
51
|
+
function deviceId() {
|
|
52
|
+
const idPath = path.join(getAppPaths().agentDir, "kimi_device_id");
|
|
53
|
+
if (existsSync(idPath)) {
|
|
54
|
+
try {
|
|
55
|
+
const text = readFileSync(idPath, "utf-8").trim();
|
|
56
|
+
if (text.length > 0) return text;
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const id = randomUUID();
|
|
61
|
+
try {
|
|
62
|
+
mkdirSync(getAppPaths().agentDir, { recursive: true, mode: 448 });
|
|
63
|
+
writeFileSync(idPath, id, { encoding: "utf-8", mode: 384 });
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
return id;
|
|
67
|
+
}
|
|
68
|
+
function deviceHeaders() {
|
|
69
|
+
return {
|
|
70
|
+
"X-Msh-Platform": KIMI_PLATFORM,
|
|
71
|
+
"X-Msh-Version": kimiVersion(),
|
|
72
|
+
"X-Msh-Device-Name": asciiHeader(hostname()),
|
|
73
|
+
"X-Msh-Device-Model": asciiHeader(deviceModel()),
|
|
74
|
+
"X-Msh-Os-Version": asciiHeader(release()),
|
|
75
|
+
"X-Msh-Device-Id": deviceId()
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function kimiCodingHeaders() {
|
|
79
|
+
return {
|
|
80
|
+
"User-Agent": `kimi-code-cli/${kimiVersion()}`,
|
|
81
|
+
...deviceHeaders()
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function isKimiCodingEndpoint(baseUrl) {
|
|
85
|
+
if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
|
|
86
|
+
const normalized = baseUrl.replace(/\/+$/, "");
|
|
87
|
+
return normalized === kimiCodeBaseUrl() || /(^|\.)kimi\.com/i.test(normalized);
|
|
88
|
+
}
|
|
89
|
+
async function postForm(endpoint, params) {
|
|
90
|
+
const response = await fetch(`${oauthHost()}${endpoint}`, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: {
|
|
93
|
+
...deviceHeaders(),
|
|
94
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
95
|
+
Accept: "application/json"
|
|
96
|
+
},
|
|
97
|
+
body: new URLSearchParams(params).toString()
|
|
98
|
+
});
|
|
99
|
+
let data = {};
|
|
100
|
+
try {
|
|
101
|
+
const parsed = await response.json();
|
|
102
|
+
if (parsed && typeof parsed === "object") data = parsed;
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
return { status: response.status, data };
|
|
106
|
+
}
|
|
107
|
+
function errorDetail(data) {
|
|
108
|
+
const desc = data.error_description ?? data.message ?? data.error;
|
|
109
|
+
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
110
|
+
}
|
|
111
|
+
function credsFromTokenResponse(data, opts) {
|
|
112
|
+
const accessToken = data.access_token;
|
|
113
|
+
const responseRefreshToken = data.refresh_token;
|
|
114
|
+
const expiresIn = Number(data.expires_in);
|
|
115
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
116
|
+
throw new Error("Kimi OAuth response missing access_token.");
|
|
117
|
+
}
|
|
118
|
+
const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
|
|
119
|
+
if (refreshToken.length === 0) {
|
|
120
|
+
throw new Error("Kimi OAuth response missing refresh_token.");
|
|
121
|
+
}
|
|
122
|
+
if (!Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
123
|
+
throw new Error("Kimi OAuth response missing or invalid expires_in.");
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
accessToken,
|
|
127
|
+
refreshToken,
|
|
128
|
+
expiresAt: Date.now() + expiresIn * 1e3,
|
|
129
|
+
baseUrl: kimiCodeBaseUrl()
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async function requestDeviceAuthorization() {
|
|
133
|
+
const { status, data } = await postForm("/api/oauth/device_authorization", {
|
|
134
|
+
client_id: CLIENT_ID
|
|
135
|
+
});
|
|
136
|
+
if (status !== 200) {
|
|
137
|
+
throw new Error(`Kimi device authorization failed (${status}): ${errorDetail(data)}`);
|
|
138
|
+
}
|
|
139
|
+
const userCode = data.user_code;
|
|
140
|
+
const deviceCode = data.device_code;
|
|
141
|
+
const verificationUriComplete = data.verification_uri_complete;
|
|
142
|
+
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
143
|
+
throw new Error("Kimi device authorization response missing user_code/device_code.");
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
userCode,
|
|
147
|
+
deviceCode,
|
|
148
|
+
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
149
|
+
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
150
|
+
interval: Number(data.interval ?? 5) || 5
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function pollDeviceToken(deviceCode) {
|
|
154
|
+
const { status, data } = await postForm("/api/oauth/token", {
|
|
155
|
+
client_id: CLIENT_ID,
|
|
156
|
+
device_code: deviceCode,
|
|
157
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
158
|
+
});
|
|
159
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
160
|
+
return { kind: "success", creds: credsFromTokenResponse(data) };
|
|
161
|
+
}
|
|
162
|
+
if (status >= 500) {
|
|
163
|
+
throw new Error(`Kimi token polling server error (${status}): ${errorDetail(data)}`);
|
|
164
|
+
}
|
|
165
|
+
const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
|
|
166
|
+
switch (errorCode) {
|
|
167
|
+
case "authorization_pending":
|
|
168
|
+
return { kind: "pending" };
|
|
169
|
+
case "slow_down":
|
|
170
|
+
return { kind: "slow_down" };
|
|
171
|
+
case "expired_token":
|
|
172
|
+
return { kind: "expired" };
|
|
173
|
+
case "access_denied":
|
|
174
|
+
return { kind: "denied" };
|
|
175
|
+
default:
|
|
176
|
+
throw new Error(`Kimi token polling failed (${status}): ${errorDetail(data)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function sleep(ms) {
|
|
180
|
+
return new Promise((resolve) => {
|
|
181
|
+
setTimeout(resolve, ms);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
async function loginKimi(callbacks) {
|
|
185
|
+
const auth = await requestDeviceAuthorization();
|
|
186
|
+
callbacks.onStatus(
|
|
187
|
+
`Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
|
|
188
|
+
);
|
|
189
|
+
callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
|
|
190
|
+
callbacks.onStatus("Waiting for you to authorize in the browser...");
|
|
191
|
+
const deadline = Date.now() + DEVICE_TIMEOUT_MS;
|
|
192
|
+
let interval = Math.max(auth.interval, 1);
|
|
193
|
+
while (Date.now() < deadline) {
|
|
194
|
+
await sleep(interval * 1e3);
|
|
195
|
+
const result = await pollDeviceToken(auth.deviceCode);
|
|
196
|
+
if (result.kind === "success") return result.creds;
|
|
197
|
+
if (result.kind === "denied") {
|
|
198
|
+
throw new Error("Kimi authorization was denied.");
|
|
199
|
+
}
|
|
200
|
+
if (result.kind === "expired") {
|
|
201
|
+
throw new Error("Kimi device code expired. Please run login again.");
|
|
202
|
+
}
|
|
203
|
+
if (result.kind === "slow_down") {
|
|
204
|
+
interval += 5;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
throw new Error("Kimi login timed out. Please run login again.");
|
|
208
|
+
}
|
|
209
|
+
async function refreshKimiToken(refreshToken) {
|
|
210
|
+
const { status, data } = await postForm("/api/oauth/token", {
|
|
211
|
+
client_id: CLIENT_ID,
|
|
212
|
+
grant_type: "refresh_token",
|
|
213
|
+
refresh_token: refreshToken
|
|
214
|
+
});
|
|
215
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
216
|
+
return credsFromTokenResponse(data, { fallbackRefreshToken: refreshToken });
|
|
217
|
+
}
|
|
218
|
+
const errorCode = typeof data.error === "string" ? data.error : "";
|
|
219
|
+
throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
5
222
|
// src/auth-storage.ts
|
|
6
223
|
import fs4 from "fs/promises";
|
|
224
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
7
225
|
import crypto5 from "crypto";
|
|
8
226
|
|
|
9
227
|
// src/oauth/anthropic.ts
|
|
@@ -29,15 +247,17 @@ async function generatePKCE() {
|
|
|
29
247
|
|
|
30
248
|
// src/claude-code-version.ts
|
|
31
249
|
import fs2 from "fs/promises";
|
|
32
|
-
import
|
|
250
|
+
import path3 from "path";
|
|
33
251
|
|
|
34
252
|
// src/logger.ts
|
|
35
253
|
import fs from "fs";
|
|
36
|
-
import
|
|
254
|
+
import path2 from "path";
|
|
37
255
|
import { randomBytes } from "crypto";
|
|
38
256
|
import { environmentSecrets, redactText, redactValue } from "@prestyj/ai";
|
|
39
257
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
40
258
|
var fd = null;
|
|
259
|
+
var bytesWritten = 0;
|
|
260
|
+
var capped = false;
|
|
41
261
|
var sessionId = "";
|
|
42
262
|
var appName = "app";
|
|
43
263
|
var cleanups = [];
|
|
@@ -56,22 +276,37 @@ function rotateIfNeeded(filePath) {
|
|
|
56
276
|
}
|
|
57
277
|
}
|
|
58
278
|
function openLog(filePath, name) {
|
|
59
|
-
if (fd !== null) return false;
|
|
279
|
+
if (fd !== null || capped) return false;
|
|
60
280
|
appName = name;
|
|
61
281
|
exactSecrets = environmentSecrets(process.env);
|
|
62
282
|
try {
|
|
63
|
-
fs.mkdirSync(
|
|
283
|
+
fs.mkdirSync(path2.dirname(filePath), { recursive: true, mode: 448 });
|
|
64
284
|
} catch {
|
|
65
285
|
}
|
|
66
286
|
rotateIfNeeded(filePath);
|
|
67
287
|
try {
|
|
68
288
|
fd = fs.openSync(filePath, "a");
|
|
69
289
|
} catch {
|
|
290
|
+
fd = null;
|
|
291
|
+
bytesWritten = 0;
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
bytesWritten = fs.fstatSync(fd).size;
|
|
296
|
+
} catch {
|
|
297
|
+
try {
|
|
298
|
+
fs.closeSync(fd);
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
fd = null;
|
|
302
|
+
bytesWritten = 0;
|
|
70
303
|
return false;
|
|
71
304
|
}
|
|
72
305
|
sessionId = randomBytes(4).toString("hex");
|
|
73
306
|
try {
|
|
74
|
-
|
|
307
|
+
if (bytesWritten < MAX_BYTES) {
|
|
308
|
+
bytesWritten += fs.writeSync(fd, "\n");
|
|
309
|
+
}
|
|
75
310
|
} catch {
|
|
76
311
|
}
|
|
77
312
|
return true;
|
|
@@ -97,8 +332,23 @@ function log(level, category, message, data) {
|
|
|
97
332
|
if (pairs) line += ` ${pairs}`;
|
|
98
333
|
}
|
|
99
334
|
line += "\n";
|
|
335
|
+
const lineBytes = Buffer.byteLength(line);
|
|
336
|
+
if (bytesWritten + lineBytes > MAX_BYTES) {
|
|
337
|
+
const capLine = `[${ts}] [sid=${sessionId}] [WARN] [logger] Log cap reached; file logging disabled until restart
|
|
338
|
+
`;
|
|
339
|
+
try {
|
|
340
|
+
if (bytesWritten + Buffer.byteLength(capLine) <= MAX_BYTES) {
|
|
341
|
+
bytesWritten += fs.writeSync(fd, capLine);
|
|
342
|
+
}
|
|
343
|
+
fs.closeSync(fd);
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
fd = null;
|
|
347
|
+
capped = true;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
100
350
|
try {
|
|
101
|
-
fs.writeSync(fd, line);
|
|
351
|
+
bytesWritten += fs.writeSync(fd, line);
|
|
102
352
|
} catch {
|
|
103
353
|
}
|
|
104
354
|
}
|
|
@@ -106,13 +356,16 @@ function registerLogCleanup(fn) {
|
|
|
106
356
|
cleanups.push(fn);
|
|
107
357
|
}
|
|
108
358
|
function closeLogger(opts) {
|
|
109
|
-
if (fd
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
359
|
+
if (fd !== null) {
|
|
360
|
+
if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
|
|
361
|
+
try {
|
|
362
|
+
if (fd !== null) fs.closeSync(fd);
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
114
365
|
}
|
|
115
366
|
fd = null;
|
|
367
|
+
bytesWritten = 0;
|
|
368
|
+
capped = false;
|
|
116
369
|
exactSecrets = [];
|
|
117
370
|
for (const unsub of cleanups) unsub();
|
|
118
371
|
cleanups = [];
|
|
@@ -126,7 +379,7 @@ var FALLBACK_VERSION = "2.1.88";
|
|
|
126
379
|
var memoryCache = null;
|
|
127
380
|
var inflight = null;
|
|
128
381
|
function cachePath() {
|
|
129
|
-
return
|
|
382
|
+
return path3.join(getAppPaths().agentDir, "claude-code-version.json");
|
|
130
383
|
}
|
|
131
384
|
async function readDiskCache() {
|
|
132
385
|
try {
|
|
@@ -208,7 +461,7 @@ async function getClaudeCliUserAgent() {
|
|
|
208
461
|
}
|
|
209
462
|
|
|
210
463
|
// src/oauth/anthropic.ts
|
|
211
|
-
var
|
|
464
|
+
var CLIENT_ID2 = atob("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
|
|
212
465
|
var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
213
466
|
var TOKEN_URLS = [
|
|
214
467
|
"https://platform.claude.com/v1/oauth/token",
|
|
@@ -255,7 +508,7 @@ async function loginAnthropic(callbacks) {
|
|
|
255
508
|
const state = crypto2.randomBytes(16).toString("hex");
|
|
256
509
|
const params = new URLSearchParams({
|
|
257
510
|
code: "true",
|
|
258
|
-
client_id:
|
|
511
|
+
client_id: CLIENT_ID2,
|
|
259
512
|
response_type: "code",
|
|
260
513
|
redirect_uri: REDIRECT_URI,
|
|
261
514
|
scope: SCOPES,
|
|
@@ -276,7 +529,7 @@ async function exchangeAnthropicCode(code, state, verifier) {
|
|
|
276
529
|
const data = await postTokenRequest(
|
|
277
530
|
{
|
|
278
531
|
grant_type: "authorization_code",
|
|
279
|
-
client_id:
|
|
532
|
+
client_id: CLIENT_ID2,
|
|
280
533
|
code,
|
|
281
534
|
state,
|
|
282
535
|
redirect_uri: REDIRECT_URI,
|
|
@@ -290,7 +543,7 @@ async function refreshAnthropicToken(refreshToken) {
|
|
|
290
543
|
const data = await postTokenRequest(
|
|
291
544
|
{
|
|
292
545
|
grant_type: "refresh_token",
|
|
293
|
-
client_id:
|
|
546
|
+
client_id: CLIENT_ID2,
|
|
294
547
|
refresh_token: refreshToken
|
|
295
548
|
},
|
|
296
549
|
"token refresh"
|
|
@@ -301,7 +554,7 @@ async function refreshAnthropicToken(refreshToken) {
|
|
|
301
554
|
// src/oauth/openai.ts
|
|
302
555
|
import http from "http";
|
|
303
556
|
import crypto3 from "crypto";
|
|
304
|
-
var
|
|
557
|
+
var CLIENT_ID3 = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
305
558
|
var AUTHORIZE_URL2 = "https://auth.openai.com/oauth/authorize";
|
|
306
559
|
var TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
307
560
|
var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
|
|
@@ -312,7 +565,7 @@ async function loginOpenAI(callbacks) {
|
|
|
312
565
|
const state = crypto3.randomBytes(16).toString("hex");
|
|
313
566
|
const url = new URL(AUTHORIZE_URL2);
|
|
314
567
|
url.searchParams.set("response_type", "code");
|
|
315
|
-
url.searchParams.set("client_id",
|
|
568
|
+
url.searchParams.set("client_id", CLIENT_ID3);
|
|
316
569
|
url.searchParams.set("redirect_uri", REDIRECT_URI2);
|
|
317
570
|
url.searchParams.set("scope", SCOPE);
|
|
318
571
|
url.searchParams.set("code_challenge", challenge);
|
|
@@ -433,7 +686,7 @@ async function exchangeOpenAICode(code, verifier) {
|
|
|
433
686
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
434
687
|
body: new URLSearchParams({
|
|
435
688
|
grant_type: "authorization_code",
|
|
436
|
-
client_id:
|
|
689
|
+
client_id: CLIENT_ID3,
|
|
437
690
|
code,
|
|
438
691
|
redirect_uri: REDIRECT_URI2,
|
|
439
692
|
code_verifier: verifier
|
|
@@ -457,7 +710,7 @@ async function refreshOpenAIToken(refreshToken) {
|
|
|
457
710
|
body: new URLSearchParams({
|
|
458
711
|
grant_type: "refresh_token",
|
|
459
712
|
refresh_token: refreshToken,
|
|
460
|
-
client_id:
|
|
713
|
+
client_id: CLIENT_ID3
|
|
461
714
|
})
|
|
462
715
|
});
|
|
463
716
|
if (!response.ok) {
|
|
@@ -840,223 +1093,6 @@ function codeAssistHeaders(accessToken) {
|
|
|
840
1093
|
};
|
|
841
1094
|
}
|
|
842
1095
|
|
|
843
|
-
// src/oauth/kimi.ts
|
|
844
|
-
import { execFileSync } from "child_process";
|
|
845
|
-
import { randomUUID } from "crypto";
|
|
846
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
847
|
-
import { arch, hostname, release, type } from "os";
|
|
848
|
-
import path3 from "path";
|
|
849
|
-
var CLIENT_ID3 = "17e5f671-d194-4dfb-9706-5516cb48c098";
|
|
850
|
-
var DEFAULT_OAUTH_HOST = "https://auth.kimi.com";
|
|
851
|
-
var DEFAULT_CODING_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
852
|
-
var KIMI_PLATFORM = "kimi_code_cli";
|
|
853
|
-
var DEFAULT_KIMI_VERSION = "1.0.11";
|
|
854
|
-
var DEVICE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
855
|
-
function oauthHost() {
|
|
856
|
-
const host = process.env.KIMI_CODE_OAUTH_HOST ?? process.env.KIMI_OAUTH_HOST ?? DEFAULT_OAUTH_HOST;
|
|
857
|
-
return host.replace(/\/+$/, "");
|
|
858
|
-
}
|
|
859
|
-
function kimiCodeBaseUrl() {
|
|
860
|
-
return (process.env.KIMI_CODE_BASE_URL ?? DEFAULT_CODING_BASE_URL).replace(/\/+$/, "");
|
|
861
|
-
}
|
|
862
|
-
function kimiVersion() {
|
|
863
|
-
const v = process.env.KIMI_CODE_VERSION ?? DEFAULT_KIMI_VERSION;
|
|
864
|
-
return asciiHeader(v, DEFAULT_KIMI_VERSION);
|
|
865
|
-
}
|
|
866
|
-
function asciiHeader(value, fallback = "unknown") {
|
|
867
|
-
const cleaned = value.replace(/[^\u0020-\u007E]/g, "").trim();
|
|
868
|
-
return cleaned.length > 0 ? cleaned : fallback;
|
|
869
|
-
}
|
|
870
|
-
function macOsProductVersion() {
|
|
871
|
-
try {
|
|
872
|
-
const version = execFileSync("/usr/bin/sw_vers", ["-productVersion"], {
|
|
873
|
-
encoding: "utf-8",
|
|
874
|
-
timeout: 1e3
|
|
875
|
-
}).trim();
|
|
876
|
-
return version.length > 0 ? version : void 0;
|
|
877
|
-
} catch {
|
|
878
|
-
return void 0;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
function deviceModel() {
|
|
882
|
-
const os = type();
|
|
883
|
-
const version = release();
|
|
884
|
-
const osArch = arch();
|
|
885
|
-
if (os === "Darwin") return `macOS ${macOsProductVersion() ?? version} ${osArch}`;
|
|
886
|
-
if (os === "Windows_NT") return `Windows ${version} ${osArch}`;
|
|
887
|
-
return `${os} ${version} ${osArch}`.trim();
|
|
888
|
-
}
|
|
889
|
-
function deviceId() {
|
|
890
|
-
const idPath = path3.join(getAppPaths().agentDir, "kimi_device_id");
|
|
891
|
-
if (existsSync(idPath)) {
|
|
892
|
-
try {
|
|
893
|
-
const text = readFileSync(idPath, "utf-8").trim();
|
|
894
|
-
if (text.length > 0) return text;
|
|
895
|
-
} catch {
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
const id = randomUUID();
|
|
899
|
-
try {
|
|
900
|
-
mkdirSync(getAppPaths().agentDir, { recursive: true, mode: 448 });
|
|
901
|
-
writeFileSync(idPath, id, { encoding: "utf-8", mode: 384 });
|
|
902
|
-
} catch {
|
|
903
|
-
}
|
|
904
|
-
return id;
|
|
905
|
-
}
|
|
906
|
-
function deviceHeaders() {
|
|
907
|
-
return {
|
|
908
|
-
"X-Msh-Platform": KIMI_PLATFORM,
|
|
909
|
-
"X-Msh-Version": kimiVersion(),
|
|
910
|
-
"X-Msh-Device-Name": asciiHeader(hostname()),
|
|
911
|
-
"X-Msh-Device-Model": asciiHeader(deviceModel()),
|
|
912
|
-
"X-Msh-Os-Version": asciiHeader(release()),
|
|
913
|
-
"X-Msh-Device-Id": deviceId()
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
function kimiCodingHeaders() {
|
|
917
|
-
return {
|
|
918
|
-
"User-Agent": `kimi-code-cli/${kimiVersion()}`,
|
|
919
|
-
...deviceHeaders()
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
function isKimiCodingEndpoint(baseUrl) {
|
|
923
|
-
if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
|
|
924
|
-
const normalized = baseUrl.replace(/\/+$/, "");
|
|
925
|
-
return normalized === kimiCodeBaseUrl() || /(^|\.)kimi\.com/i.test(normalized);
|
|
926
|
-
}
|
|
927
|
-
async function postForm(endpoint, params) {
|
|
928
|
-
const response = await fetch(`${oauthHost()}${endpoint}`, {
|
|
929
|
-
method: "POST",
|
|
930
|
-
headers: {
|
|
931
|
-
...deviceHeaders(),
|
|
932
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
933
|
-
Accept: "application/json"
|
|
934
|
-
},
|
|
935
|
-
body: new URLSearchParams(params).toString()
|
|
936
|
-
});
|
|
937
|
-
let data = {};
|
|
938
|
-
try {
|
|
939
|
-
const parsed = await response.json();
|
|
940
|
-
if (parsed && typeof parsed === "object") data = parsed;
|
|
941
|
-
} catch {
|
|
942
|
-
}
|
|
943
|
-
return { status: response.status, data };
|
|
944
|
-
}
|
|
945
|
-
function errorDetail(data) {
|
|
946
|
-
const desc = data.error_description ?? data.message ?? data.error;
|
|
947
|
-
return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
|
|
948
|
-
}
|
|
949
|
-
function credsFromTokenResponse(data, opts) {
|
|
950
|
-
const accessToken = data.access_token;
|
|
951
|
-
const responseRefreshToken = data.refresh_token;
|
|
952
|
-
const expiresIn = Number(data.expires_in);
|
|
953
|
-
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
954
|
-
throw new Error("Kimi OAuth response missing access_token.");
|
|
955
|
-
}
|
|
956
|
-
const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
|
|
957
|
-
if (refreshToken.length === 0) {
|
|
958
|
-
throw new Error("Kimi OAuth response missing refresh_token.");
|
|
959
|
-
}
|
|
960
|
-
if (!Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
961
|
-
throw new Error("Kimi OAuth response missing or invalid expires_in.");
|
|
962
|
-
}
|
|
963
|
-
return {
|
|
964
|
-
accessToken,
|
|
965
|
-
refreshToken,
|
|
966
|
-
expiresAt: Date.now() + expiresIn * 1e3,
|
|
967
|
-
baseUrl: kimiCodeBaseUrl()
|
|
968
|
-
};
|
|
969
|
-
}
|
|
970
|
-
async function requestDeviceAuthorization() {
|
|
971
|
-
const { status, data } = await postForm("/api/oauth/device_authorization", {
|
|
972
|
-
client_id: CLIENT_ID3
|
|
973
|
-
});
|
|
974
|
-
if (status !== 200) {
|
|
975
|
-
throw new Error(`Kimi device authorization failed (${status}): ${errorDetail(data)}`);
|
|
976
|
-
}
|
|
977
|
-
const userCode = data.user_code;
|
|
978
|
-
const deviceCode = data.device_code;
|
|
979
|
-
const verificationUriComplete = data.verification_uri_complete;
|
|
980
|
-
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
981
|
-
throw new Error("Kimi device authorization response missing user_code/device_code.");
|
|
982
|
-
}
|
|
983
|
-
return {
|
|
984
|
-
userCode,
|
|
985
|
-
deviceCode,
|
|
986
|
-
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
987
|
-
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
988
|
-
interval: Number(data.interval ?? 5) || 5
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
async function pollDeviceToken(deviceCode) {
|
|
992
|
-
const { status, data } = await postForm("/api/oauth/token", {
|
|
993
|
-
client_id: CLIENT_ID3,
|
|
994
|
-
device_code: deviceCode,
|
|
995
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
996
|
-
});
|
|
997
|
-
if (status === 200 && typeof data.access_token === "string") {
|
|
998
|
-
return { kind: "success", creds: credsFromTokenResponse(data) };
|
|
999
|
-
}
|
|
1000
|
-
if (status >= 500) {
|
|
1001
|
-
throw new Error(`Kimi token polling server error (${status}): ${errorDetail(data)}`);
|
|
1002
|
-
}
|
|
1003
|
-
const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
|
|
1004
|
-
switch (errorCode) {
|
|
1005
|
-
case "authorization_pending":
|
|
1006
|
-
return { kind: "pending" };
|
|
1007
|
-
case "slow_down":
|
|
1008
|
-
return { kind: "slow_down" };
|
|
1009
|
-
case "expired_token":
|
|
1010
|
-
return { kind: "expired" };
|
|
1011
|
-
case "access_denied":
|
|
1012
|
-
return { kind: "denied" };
|
|
1013
|
-
default:
|
|
1014
|
-
throw new Error(`Kimi token polling failed (${status}): ${errorDetail(data)}`);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
function sleep(ms) {
|
|
1018
|
-
return new Promise((resolve) => {
|
|
1019
|
-
setTimeout(resolve, ms);
|
|
1020
|
-
});
|
|
1021
|
-
}
|
|
1022
|
-
async function loginKimi(callbacks) {
|
|
1023
|
-
const auth = await requestDeviceAuthorization();
|
|
1024
|
-
callbacks.onStatus(
|
|
1025
|
-
`Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
|
|
1026
|
-
);
|
|
1027
|
-
callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
|
|
1028
|
-
callbacks.onStatus("Waiting for you to authorize in the browser...");
|
|
1029
|
-
const deadline = Date.now() + DEVICE_TIMEOUT_MS;
|
|
1030
|
-
let interval = Math.max(auth.interval, 1);
|
|
1031
|
-
while (Date.now() < deadline) {
|
|
1032
|
-
await sleep(interval * 1e3);
|
|
1033
|
-
const result = await pollDeviceToken(auth.deviceCode);
|
|
1034
|
-
if (result.kind === "success") return result.creds;
|
|
1035
|
-
if (result.kind === "denied") {
|
|
1036
|
-
throw new Error("Kimi authorization was denied.");
|
|
1037
|
-
}
|
|
1038
|
-
if (result.kind === "expired") {
|
|
1039
|
-
throw new Error("Kimi device code expired. Please run login again.");
|
|
1040
|
-
}
|
|
1041
|
-
if (result.kind === "slow_down") {
|
|
1042
|
-
interval += 5;
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
throw new Error("Kimi login timed out. Please run login again.");
|
|
1046
|
-
}
|
|
1047
|
-
async function refreshKimiToken(refreshToken) {
|
|
1048
|
-
const { status, data } = await postForm("/api/oauth/token", {
|
|
1049
|
-
client_id: CLIENT_ID3,
|
|
1050
|
-
grant_type: "refresh_token",
|
|
1051
|
-
refresh_token: refreshToken
|
|
1052
|
-
});
|
|
1053
|
-
if (status === 200 && typeof data.access_token === "string") {
|
|
1054
|
-
return credsFromTokenResponse(data, { fallbackRefreshToken: refreshToken });
|
|
1055
|
-
}
|
|
1056
|
-
const errorCode = typeof data.error === "string" ? data.error : "";
|
|
1057
|
-
throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
1096
|
// src/file-lock.ts
|
|
1061
1097
|
import fs3 from "fs/promises";
|
|
1062
1098
|
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
@@ -1122,7 +1158,28 @@ function isAlive(pid) {
|
|
|
1122
1158
|
// src/auth-storage.ts
|
|
1123
1159
|
var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
|
|
1124
1160
|
var XIAOMI_CREDITS_KEY = "xiaomi-credits";
|
|
1161
|
+
function activeBaseUrlEntry(data, provider) {
|
|
1162
|
+
if (provider === "moonshot") {
|
|
1163
|
+
const oauth = data[MOONSHOT_OAUTH_KEY];
|
|
1164
|
+
if (oauth) {
|
|
1165
|
+
const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
|
|
1166
|
+
if (Date.now() < exhaustedUntil && data["moonshot"]) return data["moonshot"];
|
|
1167
|
+
return oauth;
|
|
1168
|
+
}
|
|
1169
|
+
return data["moonshot"];
|
|
1170
|
+
}
|
|
1171
|
+
return data[provider];
|
|
1172
|
+
}
|
|
1173
|
+
function readStoredBaseUrlSync(authFile, provider) {
|
|
1174
|
+
try {
|
|
1175
|
+
const data = JSON.parse(readFileSync2(authFile, "utf-8"));
|
|
1176
|
+
return activeBaseUrlEntry(data, provider)?.baseUrl;
|
|
1177
|
+
} catch {
|
|
1178
|
+
return void 0;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1125
1181
|
var REFRESH_SKEW_MS = 6e4;
|
|
1182
|
+
var USAGE_EXHAUSTED_DEFAULT_MS = 15 * 60 * 1e3;
|
|
1126
1183
|
var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
|
|
1127
1184
|
"glm",
|
|
1128
1185
|
"moonshot",
|
|
@@ -1130,7 +1187,8 @@ var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
|
|
|
1130
1187
|
"minimax",
|
|
1131
1188
|
"deepseek",
|
|
1132
1189
|
"openrouter",
|
|
1133
|
-
"sakana"
|
|
1190
|
+
"sakana",
|
|
1191
|
+
"xai"
|
|
1134
1192
|
]);
|
|
1135
1193
|
var AuthStorage = class {
|
|
1136
1194
|
data = {};
|
|
@@ -1189,10 +1247,22 @@ var AuthStorage = class {
|
|
|
1189
1247
|
async isStaticApiKey(provider) {
|
|
1190
1248
|
await this.ensureLoaded();
|
|
1191
1249
|
if (provider === "moonshot" && this.data[MOONSHOT_OAUTH_KEY]) {
|
|
1192
|
-
|
|
1250
|
+
const exhaustedUntil = this.data[MOONSHOT_OAUTH_KEY].usageExhaustedUntil ?? 0;
|
|
1251
|
+
const apiKeyActive = Date.now() < exhaustedUntil && Boolean(this.data["moonshot"]);
|
|
1252
|
+
if (!apiKeyActive) return false;
|
|
1193
1253
|
}
|
|
1194
1254
|
return STATIC_API_KEY_PROVIDERS.has(provider);
|
|
1195
1255
|
}
|
|
1256
|
+
/**
|
|
1257
|
+
* The base URL on the credential that is active right now, if any.
|
|
1258
|
+
* Synchronous — call only after load()/resolveCredentials() populated the
|
|
1259
|
+
* snapshot. For `moonshot` this is the Kimi For Coding URL whenever the
|
|
1260
|
+
* OAuth entry is the one resolveCredentials would serve (i.e. not currently
|
|
1261
|
+
* usage-exhausted with an API key configured).
|
|
1262
|
+
*/
|
|
1263
|
+
getStoredBaseUrl(provider) {
|
|
1264
|
+
return activeBaseUrlEntry(this.data, provider)?.baseUrl;
|
|
1265
|
+
}
|
|
1196
1266
|
async load() {
|
|
1197
1267
|
await withFileLock(this.filePath, async () => {
|
|
1198
1268
|
try {
|
|
@@ -1224,31 +1294,80 @@ var AuthStorage = class {
|
|
|
1224
1294
|
/**
|
|
1225
1295
|
* Force a re-read from disk, discarding the in-memory cache. Needed when
|
|
1226
1296
|
* another process mutates the auth file out-of-band — e.g. the desktop app
|
|
1227
|
-
* writes API keys natively
|
|
1228
|
-
* this instance, so a long-lived daemon's cache would otherwise stay stale and
|
|
1229
|
-
* never see a newly added provider key.
|
|
1297
|
+
* writes API keys natively without going through this instance.
|
|
1230
1298
|
*/
|
|
1231
1299
|
async reload() {
|
|
1232
1300
|
this.loaded = false;
|
|
1233
1301
|
await this.load();
|
|
1234
1302
|
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Apply one provider-scoped mutation to the latest on-disk snapshot.
|
|
1305
|
+
* AuthStorage instances live in every app session/process, so writing this
|
|
1306
|
+
* instance's cached snapshot can erase credentials another instance just
|
|
1307
|
+
* added. The file lock only serializes writers; the re-read prevents stale
|
|
1308
|
+
* full-file overwrites.
|
|
1309
|
+
*/
|
|
1310
|
+
async mutateLatest(mutator) {
|
|
1311
|
+
await this.ensureLoaded();
|
|
1312
|
+
await withFileLock(this.filePath, async () => {
|
|
1313
|
+
const latest = await readAuthData(this.filePath);
|
|
1314
|
+
mutator(latest);
|
|
1315
|
+
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1316
|
+
this.data = latest;
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
async reloadLatest() {
|
|
1320
|
+
await withFileLock(this.filePath, async () => {
|
|
1321
|
+
this.data = await readAuthData(this.filePath);
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1235
1324
|
async getCredentials(provider) {
|
|
1236
1325
|
await this.ensureLoaded();
|
|
1237
1326
|
return this.data[provider];
|
|
1238
1327
|
}
|
|
1239
1328
|
async setCredentials(provider, creds) {
|
|
1240
|
-
await this.
|
|
1241
|
-
|
|
1242
|
-
|
|
1329
|
+
await this.mutateLatest((latest) => {
|
|
1330
|
+
latest[provider] = creds;
|
|
1331
|
+
});
|
|
1243
1332
|
}
|
|
1244
1333
|
async clearCredentials(provider) {
|
|
1245
|
-
await this.
|
|
1246
|
-
|
|
1247
|
-
|
|
1334
|
+
await this.mutateLatest((latest) => {
|
|
1335
|
+
delete latest[provider];
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Mark the credential stored under `storageKey` as usage-exhausted until
|
|
1340
|
+
* `resetsAt` (unix SECONDS, from the provider's rate-limit response) or a
|
|
1341
|
+
* 15-minute default when no reset time is known. While the mark is in the
|
|
1342
|
+
* future, `resolveCredentials("moonshot")` serves the Moonshot API key
|
|
1343
|
+
* instead of the Kimi OAuth credential (when both are configured) — OAuth
|
|
1344
|
+
* stays the preferred credential and is retried automatically once the mark
|
|
1345
|
+
* lapses. Persisted to auth.json so a restart (or another ezcoder-app window)
|
|
1346
|
+
* doesn't burn a request rediscovering the same exhausted window. No-op if
|
|
1347
|
+
* nothing is stored under `storageKey`.
|
|
1348
|
+
*/
|
|
1349
|
+
async markUsageExhausted(storageKey, resetsAt) {
|
|
1350
|
+
const until = resetsAt !== void 0 && resetsAt * 1e3 > Date.now() ? resetsAt * 1e3 : Date.now() + USAGE_EXHAUSTED_DEFAULT_MS;
|
|
1351
|
+
let marked = false;
|
|
1352
|
+
await this.mutateLatest((latest) => {
|
|
1353
|
+
const creds = latest[storageKey];
|
|
1354
|
+
if (!creds) return;
|
|
1355
|
+
creds.usageExhaustedUntil = until;
|
|
1356
|
+
marked = true;
|
|
1357
|
+
});
|
|
1358
|
+
if (!marked) return;
|
|
1359
|
+
log(
|
|
1360
|
+
"WARN",
|
|
1361
|
+
"auth",
|
|
1362
|
+
`Marked ${storageKey} usage-exhausted until ${new Date(until).toISOString()}`
|
|
1363
|
+
);
|
|
1248
1364
|
}
|
|
1249
1365
|
async clearAll() {
|
|
1250
|
-
this.
|
|
1251
|
-
await this.
|
|
1366
|
+
await this.ensureLoaded();
|
|
1367
|
+
await withFileLock(this.filePath, async () => {
|
|
1368
|
+
this.data = {};
|
|
1369
|
+
await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
|
|
1370
|
+
});
|
|
1252
1371
|
}
|
|
1253
1372
|
/**
|
|
1254
1373
|
* Returns valid credentials, auto-refreshing if expired.
|
|
@@ -1258,6 +1377,10 @@ var AuthStorage = class {
|
|
|
1258
1377
|
*/
|
|
1259
1378
|
async resolveCredentials(provider, opts) {
|
|
1260
1379
|
await this.ensureLoaded();
|
|
1380
|
+
const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : provider === "moonshot" ? [MOONSHOT_OAUTH_KEY, "moonshot"] : [provider];
|
|
1381
|
+
if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
|
|
1382
|
+
await this.reloadLatest();
|
|
1383
|
+
}
|
|
1261
1384
|
if (opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider)) {
|
|
1262
1385
|
for (const key of opts.storageKeys) {
|
|
1263
1386
|
const creds2 = this.data[key];
|
|
@@ -1266,8 +1389,19 @@ var AuthStorage = class {
|
|
|
1266
1389
|
throw new NotLoggedInError(provider);
|
|
1267
1390
|
}
|
|
1268
1391
|
if (provider === "moonshot" && this.data[MOONSHOT_OAUTH_KEY]) {
|
|
1392
|
+
const exhaustedUntil = this.data[MOONSHOT_OAUTH_KEY].usageExhaustedUntil ?? 0;
|
|
1393
|
+
if (Date.now() < exhaustedUntil && this.data["moonshot"]) {
|
|
1394
|
+
log(
|
|
1395
|
+
"WARN",
|
|
1396
|
+
"auth",
|
|
1397
|
+
`Kimi OAuth usage window is exhausted \u2014 using the Moonshot API key until ${new Date(exhaustedUntil).toISOString()} (OAuth resumes automatically).`
|
|
1398
|
+
);
|
|
1399
|
+
return this.data["moonshot"];
|
|
1400
|
+
}
|
|
1269
1401
|
try {
|
|
1270
|
-
return await this.resolveCredentials(MOONSHOT_OAUTH_KEY,
|
|
1402
|
+
return await this.resolveCredentials(MOONSHOT_OAUTH_KEY, {
|
|
1403
|
+
...opts?.forceRefresh ? { forceRefresh: true } : {}
|
|
1404
|
+
});
|
|
1271
1405
|
} catch (err) {
|
|
1272
1406
|
if (err instanceof NotLoggedInError && this.data["moonshot"]) {
|
|
1273
1407
|
log(
|
|
@@ -1293,41 +1427,44 @@ var AuthStorage = class {
|
|
|
1293
1427
|
const existing = this.refreshLocks.get(provider);
|
|
1294
1428
|
if (existing) return existing;
|
|
1295
1429
|
const refreshPromise = withFileLock(this.filePath, async () => {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1430
|
+
const latest = await readAuthData(this.filePath);
|
|
1431
|
+
const latestCreds = latest[provider];
|
|
1432
|
+
if (!latestCreds) {
|
|
1433
|
+
this.data = latest;
|
|
1434
|
+
throw new NotLoggedInError(provider);
|
|
1435
|
+
}
|
|
1436
|
+
const credentialWasReplaced = latestCreds.accessToken !== creds.accessToken || latestCreds.refreshToken !== creds.refreshToken || latestCreds.expiresAt !== creds.expiresAt;
|
|
1437
|
+
if (credentialWasReplaced || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - REFRESH_SKEW_MS) {
|
|
1438
|
+
this.data = latest;
|
|
1439
|
+
return latestCreds;
|
|
1305
1440
|
}
|
|
1306
1441
|
const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
|
|
1307
1442
|
let refreshed;
|
|
1308
1443
|
try {
|
|
1309
|
-
refreshed = await refreshFn(
|
|
1444
|
+
refreshed = await refreshFn(latestCreds.refreshToken);
|
|
1310
1445
|
} catch (err) {
|
|
1311
1446
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1312
1447
|
const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh/i.test(msg) || /unauthorized/i.test(msg);
|
|
1313
1448
|
if (isAuthFailure) {
|
|
1314
|
-
delete
|
|
1315
|
-
|
|
1449
|
+
delete latest[provider];
|
|
1450
|
+
this.data = latest;
|
|
1451
|
+
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1316
1452
|
throw new NotLoggedInError(provider);
|
|
1317
1453
|
}
|
|
1318
1454
|
throw err;
|
|
1319
1455
|
}
|
|
1320
|
-
if (!refreshed.accountId &&
|
|
1321
|
-
refreshed.accountId =
|
|
1456
|
+
if (!refreshed.accountId && latestCreds.accountId) {
|
|
1457
|
+
refreshed.accountId = latestCreds.accountId;
|
|
1322
1458
|
}
|
|
1323
|
-
if (!refreshed.projectId &&
|
|
1324
|
-
refreshed.projectId =
|
|
1459
|
+
if (!refreshed.projectId && latestCreds.projectId) {
|
|
1460
|
+
refreshed.projectId = latestCreds.projectId;
|
|
1325
1461
|
}
|
|
1326
|
-
if (!refreshed.baseUrl &&
|
|
1327
|
-
refreshed.baseUrl =
|
|
1462
|
+
if (!refreshed.baseUrl && latestCreds.baseUrl) {
|
|
1463
|
+
refreshed.baseUrl = latestCreds.baseUrl;
|
|
1328
1464
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1465
|
+
latest[provider] = refreshed;
|
|
1466
|
+
this.data = latest;
|
|
1467
|
+
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1331
1468
|
return refreshed;
|
|
1332
1469
|
});
|
|
1333
1470
|
this.refreshLocks.set(provider, refreshPromise);
|
|
@@ -1345,12 +1482,16 @@ var AuthStorage = class {
|
|
|
1345
1482
|
const creds = await this.resolveCredentials(provider);
|
|
1346
1483
|
return creds.accessToken;
|
|
1347
1484
|
}
|
|
1348
|
-
async save() {
|
|
1349
|
-
await withFileLock(this.filePath, async () => {
|
|
1350
|
-
await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
1485
|
};
|
|
1486
|
+
async function readAuthData(filePath) {
|
|
1487
|
+
try {
|
|
1488
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
1489
|
+
return JSON.parse(content);
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
if (error.code === "ENOENT") return {};
|
|
1492
|
+
throw error;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1354
1495
|
async function atomicWriteFile(filePath, content) {
|
|
1355
1496
|
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${crypto5.randomUUID().slice(0, 8)}.tmp`;
|
|
1356
1497
|
try {
|
|
@@ -1443,7 +1584,8 @@ var MODELS = [
|
|
|
1443
1584
|
// ── OpenAI (Codex) ─────────────────────────────────────
|
|
1444
1585
|
// GPT-5.6 family — three agentic coding tiers launched July 2026. The public
|
|
1445
1586
|
// Responses API advertises a 1.05M context window; OpenAI's Codex product
|
|
1446
|
-
// catalog advertises
|
|
1587
|
+
// catalog advertises 272K on the ChatGPT OAuth route (corrected from the
|
|
1588
|
+
// initially advertised 372K — openai/codex PR #33972, Jul 18 2026 hotfix). All three take
|
|
1447
1589
|
// text+image input, freeform apply_patch, text+image web search, and parallel
|
|
1448
1590
|
// tool calls.
|
|
1449
1591
|
{
|
|
@@ -1455,7 +1597,7 @@ var MODELS = [
|
|
|
1455
1597
|
name: "GPT-5.6 Sol",
|
|
1456
1598
|
provider: "openai",
|
|
1457
1599
|
contextWindow: 105e4,
|
|
1458
|
-
codexContextWindow:
|
|
1600
|
+
codexContextWindow: 272e3,
|
|
1459
1601
|
maxOutputTokens: 128e3,
|
|
1460
1602
|
supportsThinking: true,
|
|
1461
1603
|
supportsImages: true,
|
|
@@ -1470,7 +1612,7 @@ var MODELS = [
|
|
|
1470
1612
|
name: "GPT-5.6 Terra",
|
|
1471
1613
|
provider: "openai",
|
|
1472
1614
|
contextWindow: 105e4,
|
|
1473
|
-
codexContextWindow:
|
|
1615
|
+
codexContextWindow: 272e3,
|
|
1474
1616
|
maxOutputTokens: 128e3,
|
|
1475
1617
|
supportsThinking: true,
|
|
1476
1618
|
supportsImages: true,
|
|
@@ -1485,7 +1627,7 @@ var MODELS = [
|
|
|
1485
1627
|
name: "GPT-5.6 Luna",
|
|
1486
1628
|
provider: "openai",
|
|
1487
1629
|
contextWindow: 105e4,
|
|
1488
|
-
codexContextWindow:
|
|
1630
|
+
codexContextWindow: 272e3,
|
|
1489
1631
|
maxOutputTokens: 128e3,
|
|
1490
1632
|
supportsThinking: true,
|
|
1491
1633
|
supportsImages: true,
|
|
@@ -1536,6 +1678,26 @@ var MODELS = [
|
|
|
1536
1678
|
costTier: "high",
|
|
1537
1679
|
maxThinkingLevel: "xhigh"
|
|
1538
1680
|
},
|
|
1681
|
+
// ── xAI (Grok) ─────────────────────────────────────────
|
|
1682
|
+
// Grok 4.5 (released 2026-07-08) is xAI's flagship for coding, agentic
|
|
1683
|
+
// tasks, and knowledge work — 500K context, text+image input, configurable
|
|
1684
|
+
// `reasoning_effort` (low/medium/high, server default high; reasoning can't
|
|
1685
|
+
// be fully disabled). Served over the OpenAI-compatible API at
|
|
1686
|
+
// https://api.x.ai/v1 (API key from console.x.ai). xAI hasn't published an
|
|
1687
|
+
// official max-output cap for 4.5; 131K matches the Grok Responses ceiling
|
|
1688
|
+
// third-party integrations use.
|
|
1689
|
+
{
|
|
1690
|
+
id: "grok-4.5",
|
|
1691
|
+
name: "Grok 4.5",
|
|
1692
|
+
provider: "xai",
|
|
1693
|
+
contextWindow: 5e5,
|
|
1694
|
+
maxOutputTokens: 131072,
|
|
1695
|
+
supportsThinking: true,
|
|
1696
|
+
supportsImages: true,
|
|
1697
|
+
supportsVideo: false,
|
|
1698
|
+
costTier: "medium",
|
|
1699
|
+
maxThinkingLevel: "high"
|
|
1700
|
+
},
|
|
1539
1701
|
// ── Gemini ─────────────────────────────────────────────
|
|
1540
1702
|
{
|
|
1541
1703
|
id: "gemini-3.1-flash-lite",
|
|
@@ -1583,9 +1745,29 @@ var MODELS = [
|
|
|
1583
1745
|
maxThinkingLevel: "high"
|
|
1584
1746
|
},
|
|
1585
1747
|
// ── Moonshot (Kimi) ────────────────────────────────────
|
|
1748
|
+
// K3 is Kimi's 2.8T-parameter flagship for long-horizon coding, knowledge
|
|
1749
|
+
// work, and deep reasoning. Its effort ladder is server-declared as
|
|
1750
|
+
// low/high/max on both the public API (default max) and the Kimi For Coding
|
|
1751
|
+
// OAuth endpoint (default high); thinking can also be fully disabled.
|
|
1752
|
+
{
|
|
1753
|
+
id: "kimi-k3",
|
|
1754
|
+
name: "Kimi K3",
|
|
1755
|
+
provider: "moonshot",
|
|
1756
|
+
contextWindow: 1048576,
|
|
1757
|
+
// The API can be raised as high as the full context window, but 131K is the
|
|
1758
|
+
// documented default and keeps room for input in AgentSession's fixed cap.
|
|
1759
|
+
maxOutputTokens: 131072,
|
|
1760
|
+
supportsThinking: true,
|
|
1761
|
+
supportsImages: true,
|
|
1762
|
+
supportsVideo: true,
|
|
1763
|
+
maxVideoBytes: 100 * 1024 * 1024,
|
|
1764
|
+
costTier: "high",
|
|
1765
|
+
maxThinkingLevel: "max"
|
|
1766
|
+
},
|
|
1767
|
+
// Retain the cheaper dedicated coding model as an explicit alternative.
|
|
1586
1768
|
{
|
|
1587
1769
|
id: "kimi-k2.7-code",
|
|
1588
|
-
name: "Kimi K2.7",
|
|
1770
|
+
name: "Kimi K2.7 Code",
|
|
1589
1771
|
provider: "moonshot",
|
|
1590
1772
|
contextWindow: 262144,
|
|
1591
1773
|
maxOutputTokens: 262144,
|
|
@@ -1775,16 +1957,20 @@ function getDefaultModel(provider) {
|
|
|
1775
1957
|
if (provider === "openai") return MODELS.find((m) => m.id === "gpt-5.6-sol");
|
|
1776
1958
|
if (provider === "gemini") return MODELS.find((m) => m.id === "gemini-3.1-flash-lite");
|
|
1777
1959
|
if (provider === "glm") return MODELS.find((m) => m.id === "glm-5.2");
|
|
1778
|
-
if (provider === "moonshot") return MODELS.find((m) => m.id === "kimi-
|
|
1960
|
+
if (provider === "moonshot") return MODELS.find((m) => m.id === "kimi-k3");
|
|
1779
1961
|
if (provider === "minimax") return MODELS.find((m) => m.id === "MiniMax-M3");
|
|
1780
1962
|
if (provider === "deepseek") return MODELS.find((m) => m.id === "deepseek-v4-pro");
|
|
1781
1963
|
if (provider === "openrouter") return MODELS.find((m) => m.id === "qwen/qwen3.6-plus");
|
|
1782
1964
|
if (provider === "sakana") return MODELS.find((m) => m.id === "fugu");
|
|
1965
|
+
if (provider === "xai") return MODELS.find((m) => m.id === "grok-4.5");
|
|
1783
1966
|
return MODELS.find((m) => m.id === "claude-sonnet-5");
|
|
1784
1967
|
}
|
|
1785
1968
|
function usesOpenAICodexTransport(options) {
|
|
1786
1969
|
return options?.provider === "openai" && Boolean(options.accountId);
|
|
1787
1970
|
}
|
|
1971
|
+
function getToolResultCharLimit(_modelId, options) {
|
|
1972
|
+
return usesOpenAICodexTransport(options) ? 1e4 * 4 : void 0;
|
|
1973
|
+
}
|
|
1788
1974
|
function getContextWindow(modelId, options) {
|
|
1789
1975
|
const model = getModel(modelId);
|
|
1790
1976
|
if (!model) return 2e5;
|
|
@@ -1796,6 +1982,11 @@ function getContextWindow(modelId, options) {
|
|
|
1796
1982
|
function getMaxThinkingLevel(modelId) {
|
|
1797
1983
|
return getModel(modelId)?.maxThinkingLevel ?? "high";
|
|
1798
1984
|
}
|
|
1985
|
+
function getDefaultThinkingLevel(modelId, options) {
|
|
1986
|
+
const model = getModel(modelId);
|
|
1987
|
+
if (model?.id === "kimi-k3" && isKimiCodingEndpoint(options?.baseUrl)) return "high";
|
|
1988
|
+
return model?.maxThinkingLevel ?? "high";
|
|
1989
|
+
}
|
|
1799
1990
|
function getSummaryModel(provider, currentModelId) {
|
|
1800
1991
|
if (provider === "anthropic") {
|
|
1801
1992
|
return MODELS.find((m) => m.id === "claude-sonnet-5");
|
|
@@ -1812,6 +2003,11 @@ function getFastModel(provider, currentModelId) {
|
|
|
1812
2003
|
}
|
|
1813
2004
|
|
|
1814
2005
|
export {
|
|
2006
|
+
kimiCodeBaseUrl,
|
|
2007
|
+
kimiCodingHeaders,
|
|
2008
|
+
isKimiCodingEndpoint,
|
|
2009
|
+
loginKimi,
|
|
2010
|
+
refreshKimiToken,
|
|
1815
2011
|
generatePKCE,
|
|
1816
2012
|
openLog,
|
|
1817
2013
|
getSessionId,
|
|
@@ -1827,14 +2023,10 @@ export {
|
|
|
1827
2023
|
refreshOpenAIToken,
|
|
1828
2024
|
loginGemini,
|
|
1829
2025
|
refreshGeminiToken,
|
|
1830
|
-
kimiCodeBaseUrl,
|
|
1831
|
-
kimiCodingHeaders,
|
|
1832
|
-
isKimiCodingEndpoint,
|
|
1833
|
-
loginKimi,
|
|
1834
|
-
refreshKimiToken,
|
|
1835
2026
|
withFileLock,
|
|
1836
2027
|
MOONSHOT_OAUTH_KEY,
|
|
1837
2028
|
XIAOMI_CREDITS_KEY,
|
|
2029
|
+
readStoredBaseUrlSync,
|
|
1838
2030
|
AuthStorage,
|
|
1839
2031
|
NotLoggedInError,
|
|
1840
2032
|
MODELS,
|
|
@@ -1846,9 +2038,11 @@ export {
|
|
|
1846
2038
|
getVideoByteLimit,
|
|
1847
2039
|
getDefaultModel,
|
|
1848
2040
|
usesOpenAICodexTransport,
|
|
2041
|
+
getToolResultCharLimit,
|
|
1849
2042
|
getContextWindow,
|
|
1850
2043
|
getMaxThinkingLevel,
|
|
2044
|
+
getDefaultThinkingLevel,
|
|
1851
2045
|
getSummaryModel,
|
|
1852
2046
|
getFastModel
|
|
1853
2047
|
};
|
|
1854
|
-
//# sourceMappingURL=chunk-
|
|
2048
|
+
//# sourceMappingURL=chunk-JXPW4JKV.js.map
|