@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
|
@@ -2,8 +2,227 @@ 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
|
+
expiresIn,
|
|
130
|
+
baseUrl: kimiCodeBaseUrl()
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function requestDeviceAuthorization() {
|
|
134
|
+
const { status, data } = await postForm("/api/oauth/device_authorization", {
|
|
135
|
+
client_id: CLIENT_ID
|
|
136
|
+
});
|
|
137
|
+
if (status !== 200) {
|
|
138
|
+
throw new Error(`Kimi device authorization failed (${status}): ${errorDetail(data)}`);
|
|
139
|
+
}
|
|
140
|
+
const userCode = data.user_code;
|
|
141
|
+
const deviceCode = data.device_code;
|
|
142
|
+
const verificationUriComplete = data.verification_uri_complete;
|
|
143
|
+
if (typeof userCode !== "string" || typeof deviceCode !== "string") {
|
|
144
|
+
throw new Error("Kimi device authorization response missing user_code/device_code.");
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
userCode,
|
|
148
|
+
deviceCode,
|
|
149
|
+
verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
|
|
150
|
+
verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
|
|
151
|
+
interval: Number(data.interval ?? 5) || 5
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async function pollDeviceToken(deviceCode) {
|
|
155
|
+
const { status, data } = await postForm("/api/oauth/token", {
|
|
156
|
+
client_id: CLIENT_ID,
|
|
157
|
+
device_code: deviceCode,
|
|
158
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
159
|
+
});
|
|
160
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
161
|
+
return { kind: "success", creds: credsFromTokenResponse(data) };
|
|
162
|
+
}
|
|
163
|
+
if (status >= 500) {
|
|
164
|
+
throw new Error(`Kimi token polling server error (${status}): ${errorDetail(data)}`);
|
|
165
|
+
}
|
|
166
|
+
const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
|
|
167
|
+
switch (errorCode) {
|
|
168
|
+
case "authorization_pending":
|
|
169
|
+
return { kind: "pending" };
|
|
170
|
+
case "slow_down":
|
|
171
|
+
return { kind: "slow_down" };
|
|
172
|
+
case "expired_token":
|
|
173
|
+
return { kind: "expired" };
|
|
174
|
+
case "access_denied":
|
|
175
|
+
return { kind: "denied" };
|
|
176
|
+
default:
|
|
177
|
+
throw new Error(`Kimi token polling failed (${status}): ${errorDetail(data)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function sleep(ms) {
|
|
181
|
+
return new Promise((resolve) => {
|
|
182
|
+
setTimeout(resolve, ms);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async function loginKimi(callbacks) {
|
|
186
|
+
const auth = await requestDeviceAuthorization();
|
|
187
|
+
callbacks.onStatus(
|
|
188
|
+
`Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
|
|
189
|
+
);
|
|
190
|
+
callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
|
|
191
|
+
callbacks.onStatus("Waiting for you to authorize in the browser...");
|
|
192
|
+
const deadline = Date.now() + DEVICE_TIMEOUT_MS;
|
|
193
|
+
let interval = Math.max(auth.interval, 1);
|
|
194
|
+
while (Date.now() < deadline) {
|
|
195
|
+
await sleep(interval * 1e3);
|
|
196
|
+
const result = await pollDeviceToken(auth.deviceCode);
|
|
197
|
+
if (result.kind === "success") return result.creds;
|
|
198
|
+
if (result.kind === "denied") {
|
|
199
|
+
throw new Error("Kimi authorization was denied.");
|
|
200
|
+
}
|
|
201
|
+
if (result.kind === "expired") {
|
|
202
|
+
throw new Error("Kimi device code expired. Please run login again.");
|
|
203
|
+
}
|
|
204
|
+
if (result.kind === "slow_down") {
|
|
205
|
+
interval += 5;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
throw new Error("Kimi login timed out. Please run login again.");
|
|
209
|
+
}
|
|
210
|
+
async function refreshKimiToken(refreshToken) {
|
|
211
|
+
const { status, data } = await postForm("/api/oauth/token", {
|
|
212
|
+
client_id: CLIENT_ID,
|
|
213
|
+
grant_type: "refresh_token",
|
|
214
|
+
refresh_token: refreshToken
|
|
215
|
+
});
|
|
216
|
+
if (status === 200 && typeof data.access_token === "string") {
|
|
217
|
+
return credsFromTokenResponse(data, { fallbackRefreshToken: refreshToken });
|
|
218
|
+
}
|
|
219
|
+
const errorCode = typeof data.error === "string" ? data.error : "";
|
|
220
|
+
throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
5
223
|
// src/auth-storage.ts
|
|
6
224
|
import fs4 from "fs/promises";
|
|
225
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
7
226
|
import crypto5 from "crypto";
|
|
8
227
|
|
|
9
228
|
// src/oauth/anthropic.ts
|
|
@@ -29,15 +248,17 @@ async function generatePKCE() {
|
|
|
29
248
|
|
|
30
249
|
// src/claude-code-version.ts
|
|
31
250
|
import fs2 from "fs/promises";
|
|
32
|
-
import
|
|
251
|
+
import path3 from "path";
|
|
33
252
|
|
|
34
253
|
// src/logger.ts
|
|
35
254
|
import fs from "fs";
|
|
36
|
-
import
|
|
255
|
+
import path2 from "path";
|
|
37
256
|
import { randomBytes } from "crypto";
|
|
38
257
|
import { environmentSecrets, redactText, redactValue } from "@prestyj/ai";
|
|
39
258
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
40
259
|
var fd = null;
|
|
260
|
+
var bytesWritten = 0;
|
|
261
|
+
var capped = false;
|
|
41
262
|
var sessionId = "";
|
|
42
263
|
var appName = "app";
|
|
43
264
|
var cleanups = [];
|
|
@@ -56,22 +277,37 @@ function rotateIfNeeded(filePath) {
|
|
|
56
277
|
}
|
|
57
278
|
}
|
|
58
279
|
function openLog(filePath, name) {
|
|
59
|
-
if (fd !== null) return false;
|
|
280
|
+
if (fd !== null || capped) return false;
|
|
60
281
|
appName = name;
|
|
61
282
|
exactSecrets = environmentSecrets(process.env);
|
|
62
283
|
try {
|
|
63
|
-
fs.mkdirSync(
|
|
284
|
+
fs.mkdirSync(path2.dirname(filePath), { recursive: true, mode: 448 });
|
|
64
285
|
} catch {
|
|
65
286
|
}
|
|
66
287
|
rotateIfNeeded(filePath);
|
|
67
288
|
try {
|
|
68
289
|
fd = fs.openSync(filePath, "a");
|
|
69
290
|
} catch {
|
|
291
|
+
fd = null;
|
|
292
|
+
bytesWritten = 0;
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
bytesWritten = fs.fstatSync(fd).size;
|
|
297
|
+
} catch {
|
|
298
|
+
try {
|
|
299
|
+
fs.closeSync(fd);
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
fd = null;
|
|
303
|
+
bytesWritten = 0;
|
|
70
304
|
return false;
|
|
71
305
|
}
|
|
72
306
|
sessionId = randomBytes(4).toString("hex");
|
|
73
307
|
try {
|
|
74
|
-
|
|
308
|
+
if (bytesWritten < MAX_BYTES) {
|
|
309
|
+
bytesWritten += fs.writeSync(fd, "\n");
|
|
310
|
+
}
|
|
75
311
|
} catch {
|
|
76
312
|
}
|
|
77
313
|
return true;
|
|
@@ -97,8 +333,23 @@ function log(level, category, message, data) {
|
|
|
97
333
|
if (pairs) line += ` ${pairs}`;
|
|
98
334
|
}
|
|
99
335
|
line += "\n";
|
|
336
|
+
const lineBytes = Buffer.byteLength(line);
|
|
337
|
+
if (bytesWritten + lineBytes > MAX_BYTES) {
|
|
338
|
+
const capLine = `[${ts}] [sid=${sessionId}] [WARN] [logger] Log cap reached; file logging disabled until restart
|
|
339
|
+
`;
|
|
340
|
+
try {
|
|
341
|
+
if (bytesWritten + Buffer.byteLength(capLine) <= MAX_BYTES) {
|
|
342
|
+
bytesWritten += fs.writeSync(fd, capLine);
|
|
343
|
+
}
|
|
344
|
+
fs.closeSync(fd);
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
fd = null;
|
|
348
|
+
capped = true;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
100
351
|
try {
|
|
101
|
-
fs.writeSync(fd, line);
|
|
352
|
+
bytesWritten += fs.writeSync(fd, line);
|
|
102
353
|
} catch {
|
|
103
354
|
}
|
|
104
355
|
}
|
|
@@ -106,13 +357,16 @@ function registerLogCleanup(fn) {
|
|
|
106
357
|
cleanups.push(fn);
|
|
107
358
|
}
|
|
108
359
|
function closeLogger(opts) {
|
|
109
|
-
if (fd
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
360
|
+
if (fd !== null) {
|
|
361
|
+
if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
|
|
362
|
+
try {
|
|
363
|
+
if (fd !== null) fs.closeSync(fd);
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
114
366
|
}
|
|
115
367
|
fd = null;
|
|
368
|
+
bytesWritten = 0;
|
|
369
|
+
capped = false;
|
|
116
370
|
exactSecrets = [];
|
|
117
371
|
for (const unsub of cleanups) unsub();
|
|
118
372
|
cleanups = [];
|
|
@@ -126,7 +380,7 @@ var FALLBACK_VERSION = "2.1.88";
|
|
|
126
380
|
var memoryCache = null;
|
|
127
381
|
var inflight = null;
|
|
128
382
|
function cachePath() {
|
|
129
|
-
return
|
|
383
|
+
return path3.join(getAppPaths().agentDir, "claude-code-version.json");
|
|
130
384
|
}
|
|
131
385
|
async function readDiskCache() {
|
|
132
386
|
try {
|
|
@@ -208,7 +462,7 @@ async function getClaudeCliUserAgent() {
|
|
|
208
462
|
}
|
|
209
463
|
|
|
210
464
|
// src/oauth/anthropic.ts
|
|
211
|
-
var
|
|
465
|
+
var CLIENT_ID2 = atob("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
|
|
212
466
|
var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
213
467
|
var TOKEN_URLS = [
|
|
214
468
|
"https://platform.claude.com/v1/oauth/token",
|
|
@@ -255,7 +509,7 @@ async function loginAnthropic(callbacks) {
|
|
|
255
509
|
const state = crypto2.randomBytes(16).toString("hex");
|
|
256
510
|
const params = new URLSearchParams({
|
|
257
511
|
code: "true",
|
|
258
|
-
client_id:
|
|
512
|
+
client_id: CLIENT_ID2,
|
|
259
513
|
response_type: "code",
|
|
260
514
|
redirect_uri: REDIRECT_URI,
|
|
261
515
|
scope: SCOPES,
|
|
@@ -276,7 +530,7 @@ async function exchangeAnthropicCode(code, state, verifier) {
|
|
|
276
530
|
const data = await postTokenRequest(
|
|
277
531
|
{
|
|
278
532
|
grant_type: "authorization_code",
|
|
279
|
-
client_id:
|
|
533
|
+
client_id: CLIENT_ID2,
|
|
280
534
|
code,
|
|
281
535
|
state,
|
|
282
536
|
redirect_uri: REDIRECT_URI,
|
|
@@ -290,7 +544,7 @@ async function refreshAnthropicToken(refreshToken) {
|
|
|
290
544
|
const data = await postTokenRequest(
|
|
291
545
|
{
|
|
292
546
|
grant_type: "refresh_token",
|
|
293
|
-
client_id:
|
|
547
|
+
client_id: CLIENT_ID2,
|
|
294
548
|
refresh_token: refreshToken
|
|
295
549
|
},
|
|
296
550
|
"token refresh"
|
|
@@ -301,7 +555,7 @@ async function refreshAnthropicToken(refreshToken) {
|
|
|
301
555
|
// src/oauth/openai.ts
|
|
302
556
|
import http from "http";
|
|
303
557
|
import crypto3 from "crypto";
|
|
304
|
-
var
|
|
558
|
+
var CLIENT_ID3 = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
305
559
|
var AUTHORIZE_URL2 = "https://auth.openai.com/oauth/authorize";
|
|
306
560
|
var TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
307
561
|
var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
|
|
@@ -312,7 +566,7 @@ async function loginOpenAI(callbacks) {
|
|
|
312
566
|
const state = crypto3.randomBytes(16).toString("hex");
|
|
313
567
|
const url = new URL(AUTHORIZE_URL2);
|
|
314
568
|
url.searchParams.set("response_type", "code");
|
|
315
|
-
url.searchParams.set("client_id",
|
|
569
|
+
url.searchParams.set("client_id", CLIENT_ID3);
|
|
316
570
|
url.searchParams.set("redirect_uri", REDIRECT_URI2);
|
|
317
571
|
url.searchParams.set("scope", SCOPE);
|
|
318
572
|
url.searchParams.set("code_challenge", challenge);
|
|
@@ -433,7 +687,7 @@ async function exchangeOpenAICode(code, verifier) {
|
|
|
433
687
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
434
688
|
body: new URLSearchParams({
|
|
435
689
|
grant_type: "authorization_code",
|
|
436
|
-
client_id:
|
|
690
|
+
client_id: CLIENT_ID3,
|
|
437
691
|
code,
|
|
438
692
|
redirect_uri: REDIRECT_URI2,
|
|
439
693
|
code_verifier: verifier
|
|
@@ -457,7 +711,7 @@ async function refreshOpenAIToken(refreshToken) {
|
|
|
457
711
|
body: new URLSearchParams({
|
|
458
712
|
grant_type: "refresh_token",
|
|
459
713
|
refresh_token: refreshToken,
|
|
460
|
-
client_id:
|
|
714
|
+
client_id: CLIENT_ID3
|
|
461
715
|
})
|
|
462
716
|
});
|
|
463
717
|
if (!response.ok) {
|
|
@@ -840,223 +1094,6 @@ function codeAssistHeaders(accessToken) {
|
|
|
840
1094
|
};
|
|
841
1095
|
}
|
|
842
1096
|
|
|
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
1097
|
// src/file-lock.ts
|
|
1061
1098
|
import fs3 from "fs/promises";
|
|
1062
1099
|
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
@@ -1122,7 +1159,34 @@ function isAlive(pid) {
|
|
|
1122
1159
|
// src/auth-storage.ts
|
|
1123
1160
|
var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
|
|
1124
1161
|
var XIAOMI_CREDITS_KEY = "xiaomi-credits";
|
|
1125
|
-
var
|
|
1162
|
+
var LOCAL_AUTH_KEY_PREFIX = "local:";
|
|
1163
|
+
var LOCAL_CREDENTIAL_LIFETIME_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
|
|
1164
|
+
function activeBaseUrlEntry(data, provider) {
|
|
1165
|
+
if (provider === "moonshot") {
|
|
1166
|
+
const oauth = data[MOONSHOT_OAUTH_KEY];
|
|
1167
|
+
if (oauth) {
|
|
1168
|
+
const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
|
|
1169
|
+
if (Date.now() < exhaustedUntil && data["moonshot"]) return data["moonshot"];
|
|
1170
|
+
return oauth;
|
|
1171
|
+
}
|
|
1172
|
+
return data["moonshot"];
|
|
1173
|
+
}
|
|
1174
|
+
return data[provider];
|
|
1175
|
+
}
|
|
1176
|
+
function readStoredBaseUrlSync(authFile, provider) {
|
|
1177
|
+
try {
|
|
1178
|
+
const data = JSON.parse(readFileSync2(authFile, "utf-8"));
|
|
1179
|
+
return activeBaseUrlEntry(data, provider)?.baseUrl;
|
|
1180
|
+
} catch {
|
|
1181
|
+
return void 0;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
var MIN_REFRESH_THRESHOLD_MS = 3e5;
|
|
1185
|
+
var REFRESH_THRESHOLD_RATIO = 0.5;
|
|
1186
|
+
function refreshThresholdMs(creds) {
|
|
1187
|
+
const lifetimeMs = (creds.expiresIn ?? 0) * 1e3;
|
|
1188
|
+
return Math.max(MIN_REFRESH_THRESHOLD_MS, lifetimeMs * REFRESH_THRESHOLD_RATIO);
|
|
1189
|
+
}
|
|
1126
1190
|
var USAGE_EXHAUSTED_DEFAULT_MS = 15 * 60 * 1e3;
|
|
1127
1191
|
var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
|
|
1128
1192
|
"glm",
|
|
@@ -1132,7 +1196,9 @@ var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
|
|
|
1132
1196
|
"deepseek",
|
|
1133
1197
|
"openrouter",
|
|
1134
1198
|
"sakana",
|
|
1135
|
-
"xai"
|
|
1199
|
+
"xai",
|
|
1200
|
+
// Local endpoints: a fixed (usually placeholder) key, never refreshable.
|
|
1201
|
+
"local"
|
|
1136
1202
|
]);
|
|
1137
1203
|
var AuthStorage = class {
|
|
1138
1204
|
data = {};
|
|
@@ -1181,8 +1247,33 @@ var AuthStorage = class {
|
|
|
1181
1247
|
if (provider === "xiaomi") {
|
|
1182
1248
|
return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
|
|
1183
1249
|
}
|
|
1250
|
+
if (provider === "local") {
|
|
1251
|
+
return Object.keys(this.data).some((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX));
|
|
1252
|
+
}
|
|
1184
1253
|
return Boolean(this.data[provider]);
|
|
1185
1254
|
}
|
|
1255
|
+
/** Endpoint ids that currently have a `local:<id>` credential stored. */
|
|
1256
|
+
async listLocalEndpointIds() {
|
|
1257
|
+
await this.ensureLoaded();
|
|
1258
|
+
return Object.keys(this.data).filter((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX)).map((key) => key.slice(LOCAL_AUTH_KEY_PREFIX.length));
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Write (or refresh) the credential for one local endpoint. The `baseUrl` is
|
|
1262
|
+
* what `effectiveBaseUrl` later picks up, and `accessToken` is the endpoint's
|
|
1263
|
+
* key — a placeholder for the servers that ignore it.
|
|
1264
|
+
*/
|
|
1265
|
+
async setLocalEndpoint(endpointId, baseUrl, apiKey) {
|
|
1266
|
+
await this.setCredentials(`${LOCAL_AUTH_KEY_PREFIX}${endpointId}`, {
|
|
1267
|
+
accessToken: apiKey && apiKey.length > 0 ? apiKey : "local",
|
|
1268
|
+
refreshToken: "",
|
|
1269
|
+
expiresAt: Date.now() + LOCAL_CREDENTIAL_LIFETIME_MS,
|
|
1270
|
+
baseUrl
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
/** Remove one local endpoint's credential. No-op when it isn't stored. */
|
|
1274
|
+
async removeLocalEndpoint(endpointId) {
|
|
1275
|
+
await this.clearCredentials(`${LOCAL_AUTH_KEY_PREFIX}${endpointId}`);
|
|
1276
|
+
}
|
|
1186
1277
|
/**
|
|
1187
1278
|
* True if the active credential for `provider` is a static API key with no
|
|
1188
1279
|
* refresh mechanism. For `moonshot` this is only true when the Kimi OAuth
|
|
@@ -1197,6 +1288,16 @@ var AuthStorage = class {
|
|
|
1197
1288
|
}
|
|
1198
1289
|
return STATIC_API_KEY_PROVIDERS.has(provider);
|
|
1199
1290
|
}
|
|
1291
|
+
/**
|
|
1292
|
+
* The base URL on the credential that is active right now, if any.
|
|
1293
|
+
* Synchronous — call only after load()/resolveCredentials() populated the
|
|
1294
|
+
* snapshot. For `moonshot` this is the Kimi For Coding URL whenever the
|
|
1295
|
+
* OAuth entry is the one resolveCredentials would serve (i.e. not currently
|
|
1296
|
+
* usage-exhausted with an API key configured).
|
|
1297
|
+
*/
|
|
1298
|
+
getStoredBaseUrl(provider) {
|
|
1299
|
+
return activeBaseUrlEntry(this.data, provider)?.baseUrl;
|
|
1300
|
+
}
|
|
1200
1301
|
async load() {
|
|
1201
1302
|
await withFileLock(this.filePath, async () => {
|
|
1202
1303
|
try {
|
|
@@ -1228,27 +1329,46 @@ var AuthStorage = class {
|
|
|
1228
1329
|
/**
|
|
1229
1330
|
* Force a re-read from disk, discarding the in-memory cache. Needed when
|
|
1230
1331
|
* another process mutates the auth file out-of-band — e.g. the desktop app
|
|
1231
|
-
* writes API keys natively
|
|
1232
|
-
* this instance, so a long-lived daemon's cache would otherwise stay stale and
|
|
1233
|
-
* never see a newly added provider key.
|
|
1332
|
+
* writes API keys natively without going through this instance.
|
|
1234
1333
|
*/
|
|
1235
1334
|
async reload() {
|
|
1236
1335
|
this.loaded = false;
|
|
1237
1336
|
await this.load();
|
|
1238
1337
|
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Apply one provider-scoped mutation to the latest on-disk snapshot.
|
|
1340
|
+
* AuthStorage instances live in every app session/process, so writing this
|
|
1341
|
+
* instance's cached snapshot can erase credentials another instance just
|
|
1342
|
+
* added. The file lock only serializes writers; the re-read prevents stale
|
|
1343
|
+
* full-file overwrites.
|
|
1344
|
+
*/
|
|
1345
|
+
async mutateLatest(mutator) {
|
|
1346
|
+
await this.ensureLoaded();
|
|
1347
|
+
await withFileLock(this.filePath, async () => {
|
|
1348
|
+
const latest = await readAuthData(this.filePath);
|
|
1349
|
+
mutator(latest);
|
|
1350
|
+
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1351
|
+
this.data = latest;
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
async reloadLatest() {
|
|
1355
|
+
await withFileLock(this.filePath, async () => {
|
|
1356
|
+
this.data = await readAuthData(this.filePath);
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1239
1359
|
async getCredentials(provider) {
|
|
1240
1360
|
await this.ensureLoaded();
|
|
1241
1361
|
return this.data[provider];
|
|
1242
1362
|
}
|
|
1243
1363
|
async setCredentials(provider, creds) {
|
|
1244
|
-
await this.
|
|
1245
|
-
|
|
1246
|
-
|
|
1364
|
+
await this.mutateLatest((latest) => {
|
|
1365
|
+
latest[provider] = creds;
|
|
1366
|
+
});
|
|
1247
1367
|
}
|
|
1248
1368
|
async clearCredentials(provider) {
|
|
1249
|
-
await this.
|
|
1250
|
-
|
|
1251
|
-
|
|
1369
|
+
await this.mutateLatest((latest) => {
|
|
1370
|
+
delete latest[provider];
|
|
1371
|
+
});
|
|
1252
1372
|
}
|
|
1253
1373
|
/**
|
|
1254
1374
|
* Mark the credential stored under `storageKey` as usage-exhausted until
|
|
@@ -1262,12 +1382,15 @@ var AuthStorage = class {
|
|
|
1262
1382
|
* nothing is stored under `storageKey`.
|
|
1263
1383
|
*/
|
|
1264
1384
|
async markUsageExhausted(storageKey, resetsAt) {
|
|
1265
|
-
await this.ensureLoaded();
|
|
1266
|
-
const creds = this.data[storageKey];
|
|
1267
|
-
if (!creds) return;
|
|
1268
1385
|
const until = resetsAt !== void 0 && resetsAt * 1e3 > Date.now() ? resetsAt * 1e3 : Date.now() + USAGE_EXHAUSTED_DEFAULT_MS;
|
|
1269
|
-
|
|
1270
|
-
await this.
|
|
1386
|
+
let marked = false;
|
|
1387
|
+
await this.mutateLatest((latest) => {
|
|
1388
|
+
const creds = latest[storageKey];
|
|
1389
|
+
if (!creds) return;
|
|
1390
|
+
creds.usageExhaustedUntil = until;
|
|
1391
|
+
marked = true;
|
|
1392
|
+
});
|
|
1393
|
+
if (!marked) return;
|
|
1271
1394
|
log(
|
|
1272
1395
|
"WARN",
|
|
1273
1396
|
"auth",
|
|
@@ -1275,8 +1398,11 @@ var AuthStorage = class {
|
|
|
1275
1398
|
);
|
|
1276
1399
|
}
|
|
1277
1400
|
async clearAll() {
|
|
1278
|
-
this.
|
|
1279
|
-
await this.
|
|
1401
|
+
await this.ensureLoaded();
|
|
1402
|
+
await withFileLock(this.filePath, async () => {
|
|
1403
|
+
this.data = {};
|
|
1404
|
+
await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
|
|
1405
|
+
});
|
|
1280
1406
|
}
|
|
1281
1407
|
/**
|
|
1282
1408
|
* Returns valid credentials, auto-refreshing if expired.
|
|
@@ -1286,6 +1412,10 @@ var AuthStorage = class {
|
|
|
1286
1412
|
*/
|
|
1287
1413
|
async resolveCredentials(provider, opts) {
|
|
1288
1414
|
await this.ensureLoaded();
|
|
1415
|
+
const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : provider === "moonshot" ? [MOONSHOT_OAUTH_KEY, "moonshot"] : [provider];
|
|
1416
|
+
if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
|
|
1417
|
+
await this.reloadLatest();
|
|
1418
|
+
}
|
|
1289
1419
|
if (opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider)) {
|
|
1290
1420
|
for (const key of opts.storageKeys) {
|
|
1291
1421
|
const creds2 = this.data[key];
|
|
@@ -1326,47 +1456,50 @@ var AuthStorage = class {
|
|
|
1326
1456
|
if (STATIC_API_KEY_PROVIDERS.has(provider)) {
|
|
1327
1457
|
return creds;
|
|
1328
1458
|
}
|
|
1329
|
-
if (!opts?.forceRefresh && Date.now() < creds.expiresAt -
|
|
1459
|
+
if (!opts?.forceRefresh && Date.now() < creds.expiresAt - refreshThresholdMs(creds)) {
|
|
1330
1460
|
return creds;
|
|
1331
1461
|
}
|
|
1332
1462
|
const existing = this.refreshLocks.get(provider);
|
|
1333
1463
|
if (existing) return existing;
|
|
1334
1464
|
const refreshPromise = withFileLock(this.filePath, async () => {
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1465
|
+
const latest = await readAuthData(this.filePath);
|
|
1466
|
+
const latestCreds = latest[provider];
|
|
1467
|
+
if (!latestCreds) {
|
|
1468
|
+
this.data = latest;
|
|
1469
|
+
throw new NotLoggedInError(provider);
|
|
1470
|
+
}
|
|
1471
|
+
const credentialWasReplaced = latestCreds.accessToken !== creds.accessToken || latestCreds.refreshToken !== creds.refreshToken || latestCreds.expiresAt !== creds.expiresAt;
|
|
1472
|
+
if (credentialWasReplaced || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - refreshThresholdMs(latestCreds)) {
|
|
1473
|
+
this.data = latest;
|
|
1474
|
+
return latestCreds;
|
|
1344
1475
|
}
|
|
1345
1476
|
const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
|
|
1346
1477
|
let refreshed;
|
|
1347
1478
|
try {
|
|
1348
|
-
refreshed = await refreshFn(
|
|
1479
|
+
refreshed = await refreshFn(latestCreds.refreshToken);
|
|
1349
1480
|
} catch (err) {
|
|
1350
1481
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1351
1482
|
const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh/i.test(msg) || /unauthorized/i.test(msg);
|
|
1352
1483
|
if (isAuthFailure) {
|
|
1353
|
-
delete
|
|
1354
|
-
|
|
1484
|
+
delete latest[provider];
|
|
1485
|
+
this.data = latest;
|
|
1486
|
+
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1355
1487
|
throw new NotLoggedInError(provider);
|
|
1356
1488
|
}
|
|
1357
1489
|
throw err;
|
|
1358
1490
|
}
|
|
1359
|
-
if (!refreshed.accountId &&
|
|
1360
|
-
refreshed.accountId =
|
|
1491
|
+
if (!refreshed.accountId && latestCreds.accountId) {
|
|
1492
|
+
refreshed.accountId = latestCreds.accountId;
|
|
1361
1493
|
}
|
|
1362
|
-
if (!refreshed.projectId &&
|
|
1363
|
-
refreshed.projectId =
|
|
1494
|
+
if (!refreshed.projectId && latestCreds.projectId) {
|
|
1495
|
+
refreshed.projectId = latestCreds.projectId;
|
|
1364
1496
|
}
|
|
1365
|
-
if (!refreshed.baseUrl &&
|
|
1366
|
-
refreshed.baseUrl =
|
|
1497
|
+
if (!refreshed.baseUrl && latestCreds.baseUrl) {
|
|
1498
|
+
refreshed.baseUrl = latestCreds.baseUrl;
|
|
1367
1499
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1500
|
+
latest[provider] = refreshed;
|
|
1501
|
+
this.data = latest;
|
|
1502
|
+
await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
|
|
1370
1503
|
return refreshed;
|
|
1371
1504
|
});
|
|
1372
1505
|
this.refreshLocks.set(provider, refreshPromise);
|
|
@@ -1384,12 +1517,16 @@ var AuthStorage = class {
|
|
|
1384
1517
|
const creds = await this.resolveCredentials(provider);
|
|
1385
1518
|
return creds.accessToken;
|
|
1386
1519
|
}
|
|
1387
|
-
async save() {
|
|
1388
|
-
await withFileLock(this.filePath, async () => {
|
|
1389
|
-
await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
|
|
1390
|
-
});
|
|
1391
|
-
}
|
|
1392
1520
|
};
|
|
1521
|
+
async function readAuthData(filePath) {
|
|
1522
|
+
try {
|
|
1523
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
1524
|
+
return JSON.parse(content);
|
|
1525
|
+
} catch (error) {
|
|
1526
|
+
if (error.code === "ENOENT") return {};
|
|
1527
|
+
throw error;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1393
1530
|
async function atomicWriteFile(filePath, content) {
|
|
1394
1531
|
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${crypto5.randomUUID().slice(0, 8)}.tmp`;
|
|
1395
1532
|
try {
|
|
@@ -1444,8 +1581,12 @@ var MODELS = [
|
|
|
1444
1581
|
// maxThinkingLevel: "max",
|
|
1445
1582
|
// },
|
|
1446
1583
|
{
|
|
1447
|
-
|
|
1448
|
-
|
|
1584
|
+
// Released 2026-07-24 — "For complex agentic coding and enterprise work".
|
|
1585
|
+
// Near-Fable capability at half the price ($5/$25 vs $10/$50). Adaptive
|
|
1586
|
+
// thinking with the full effort ladder (low→max, xhigh included); dateless
|
|
1587
|
+
// ID is the canonical pinned snapshot (post-4.6 naming scheme).
|
|
1588
|
+
id: "claude-opus-5",
|
|
1589
|
+
name: "Claude Opus 5",
|
|
1449
1590
|
provider: "anthropic",
|
|
1450
1591
|
contextWindow: 1e6,
|
|
1451
1592
|
maxOutputTokens: 128e3,
|
|
@@ -1482,7 +1623,8 @@ var MODELS = [
|
|
|
1482
1623
|
// ── OpenAI (Codex) ─────────────────────────────────────
|
|
1483
1624
|
// GPT-5.6 family — three agentic coding tiers launched July 2026. The public
|
|
1484
1625
|
// Responses API advertises a 1.05M context window; OpenAI's Codex product
|
|
1485
|
-
// catalog advertises
|
|
1626
|
+
// catalog advertises 272K on the ChatGPT OAuth route (corrected from the
|
|
1627
|
+
// initially advertised 372K — openai/codex PR #33972, Jul 18 2026 hotfix). All three take
|
|
1486
1628
|
// text+image input, freeform apply_patch, text+image web search, and parallel
|
|
1487
1629
|
// tool calls.
|
|
1488
1630
|
{
|
|
@@ -1494,7 +1636,7 @@ var MODELS = [
|
|
|
1494
1636
|
name: "GPT-5.6 Sol",
|
|
1495
1637
|
provider: "openai",
|
|
1496
1638
|
contextWindow: 105e4,
|
|
1497
|
-
codexContextWindow:
|
|
1639
|
+
codexContextWindow: 272e3,
|
|
1498
1640
|
maxOutputTokens: 128e3,
|
|
1499
1641
|
supportsThinking: true,
|
|
1500
1642
|
supportsImages: true,
|
|
@@ -1509,7 +1651,7 @@ var MODELS = [
|
|
|
1509
1651
|
name: "GPT-5.6 Terra",
|
|
1510
1652
|
provider: "openai",
|
|
1511
1653
|
contextWindow: 105e4,
|
|
1512
|
-
codexContextWindow:
|
|
1654
|
+
codexContextWindow: 272e3,
|
|
1513
1655
|
maxOutputTokens: 128e3,
|
|
1514
1656
|
supportsThinking: true,
|
|
1515
1657
|
supportsImages: true,
|
|
@@ -1524,7 +1666,7 @@ var MODELS = [
|
|
|
1524
1666
|
name: "GPT-5.6 Luna",
|
|
1525
1667
|
provider: "openai",
|
|
1526
1668
|
contextWindow: 105e4,
|
|
1527
|
-
codexContextWindow:
|
|
1669
|
+
codexContextWindow: 272e3,
|
|
1528
1670
|
maxOutputTokens: 128e3,
|
|
1529
1671
|
supportsThinking: true,
|
|
1530
1672
|
supportsImages: true,
|
|
@@ -1643,8 +1785,9 @@ var MODELS = [
|
|
|
1643
1785
|
},
|
|
1644
1786
|
// ── Moonshot (Kimi) ────────────────────────────────────
|
|
1645
1787
|
// K3 is Kimi's 2.8T-parameter flagship for long-horizon coding, knowledge
|
|
1646
|
-
// work, and deep reasoning.
|
|
1647
|
-
//
|
|
1788
|
+
// work, and deep reasoning. Its effort ladder is server-declared as
|
|
1789
|
+
// low/high/max on both the public API (default max) and the Kimi For Coding
|
|
1790
|
+
// OAuth endpoint (default high); thinking can also be fully disabled.
|
|
1648
1791
|
{
|
|
1649
1792
|
id: "kimi-k3",
|
|
1650
1793
|
name: "Kimi K3",
|
|
@@ -1829,14 +1972,30 @@ var MODELS = [
|
|
|
1829
1972
|
maxThinkingLevel: "high"
|
|
1830
1973
|
}
|
|
1831
1974
|
];
|
|
1975
|
+
var runtimeModels = /* @__PURE__ */ new Map();
|
|
1976
|
+
function registerRuntimeModels(models) {
|
|
1977
|
+
for (const model of models) runtimeModels.set(model.id, model);
|
|
1978
|
+
}
|
|
1979
|
+
function clearRuntimeModels(predicate) {
|
|
1980
|
+
if (!predicate) {
|
|
1981
|
+
runtimeModels.clear();
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
for (const [id, model] of runtimeModels) {
|
|
1985
|
+
if (predicate(model)) runtimeModels.delete(id);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
function getAllModels() {
|
|
1989
|
+
return [...MODELS, ...runtimeModels.values()];
|
|
1990
|
+
}
|
|
1832
1991
|
function getModel(id) {
|
|
1833
|
-
return MODELS.find((m) => m.id === id);
|
|
1992
|
+
return MODELS.find((m) => m.id === id) ?? runtimeModels.get(id);
|
|
1834
1993
|
}
|
|
1835
1994
|
function getModelsForProvider(provider) {
|
|
1836
|
-
return
|
|
1995
|
+
return getAllModels().filter((m) => m.provider === provider);
|
|
1837
1996
|
}
|
|
1838
1997
|
function getAuthStorageKeys(provider, modelId) {
|
|
1839
|
-
const model =
|
|
1998
|
+
const model = getAllModels().find((m) => m.id === modelId && m.provider === provider);
|
|
1840
1999
|
return model?.authStorageKeys ?? [provider];
|
|
1841
2000
|
}
|
|
1842
2001
|
function getAuthStorageKey(provider, modelId) {
|
|
@@ -1859,8 +2018,23 @@ function getDefaultModel(provider) {
|
|
|
1859
2018
|
if (provider === "openrouter") return MODELS.find((m) => m.id === "qwen/qwen3.6-plus");
|
|
1860
2019
|
if (provider === "sakana") return MODELS.find((m) => m.id === "fugu");
|
|
1861
2020
|
if (provider === "xai") return MODELS.find((m) => m.id === "grok-4.5");
|
|
2021
|
+
if (provider === "local") {
|
|
2022
|
+
return getModelsForProvider("local")[0] ?? PLACEHOLDER_LOCAL_MODEL;
|
|
2023
|
+
}
|
|
1862
2024
|
return MODELS.find((m) => m.id === "claude-sonnet-5");
|
|
1863
2025
|
}
|
|
2026
|
+
var PLACEHOLDER_LOCAL_MODEL = {
|
|
2027
|
+
id: "local/none/none",
|
|
2028
|
+
name: "No local model discovered",
|
|
2029
|
+
provider: "local",
|
|
2030
|
+
contextWindow: 8192,
|
|
2031
|
+
maxOutputTokens: 2048,
|
|
2032
|
+
supportsThinking: false,
|
|
2033
|
+
supportsImages: false,
|
|
2034
|
+
supportsVideo: false,
|
|
2035
|
+
costTier: "low",
|
|
2036
|
+
maxThinkingLevel: "high"
|
|
2037
|
+
};
|
|
1864
2038
|
function usesOpenAICodexTransport(options) {
|
|
1865
2039
|
return options?.provider === "openai" && Boolean(options.accountId);
|
|
1866
2040
|
}
|
|
@@ -1878,6 +2052,11 @@ function getContextWindow(modelId, options) {
|
|
|
1878
2052
|
function getMaxThinkingLevel(modelId) {
|
|
1879
2053
|
return getModel(modelId)?.maxThinkingLevel ?? "high";
|
|
1880
2054
|
}
|
|
2055
|
+
function getDefaultThinkingLevel(modelId, options) {
|
|
2056
|
+
const model = getModel(modelId);
|
|
2057
|
+
if (model?.id === "kimi-k3" && isKimiCodingEndpoint(options?.baseUrl)) return "high";
|
|
2058
|
+
return model?.maxThinkingLevel ?? "high";
|
|
2059
|
+
}
|
|
1881
2060
|
function getSummaryModel(provider, currentModelId) {
|
|
1882
2061
|
if (provider === "anthropic") {
|
|
1883
2062
|
return MODELS.find((m) => m.id === "claude-sonnet-5");
|
|
@@ -1894,6 +2073,11 @@ function getFastModel(provider, currentModelId) {
|
|
|
1894
2073
|
}
|
|
1895
2074
|
|
|
1896
2075
|
export {
|
|
2076
|
+
kimiCodeBaseUrl,
|
|
2077
|
+
kimiCodingHeaders,
|
|
2078
|
+
isKimiCodingEndpoint,
|
|
2079
|
+
loginKimi,
|
|
2080
|
+
refreshKimiToken,
|
|
1897
2081
|
generatePKCE,
|
|
1898
2082
|
openLog,
|
|
1899
2083
|
getSessionId,
|
|
@@ -1909,17 +2093,17 @@ export {
|
|
|
1909
2093
|
refreshOpenAIToken,
|
|
1910
2094
|
loginGemini,
|
|
1911
2095
|
refreshGeminiToken,
|
|
1912
|
-
kimiCodeBaseUrl,
|
|
1913
|
-
kimiCodingHeaders,
|
|
1914
|
-
isKimiCodingEndpoint,
|
|
1915
|
-
loginKimi,
|
|
1916
|
-
refreshKimiToken,
|
|
1917
2096
|
withFileLock,
|
|
1918
2097
|
MOONSHOT_OAUTH_KEY,
|
|
1919
2098
|
XIAOMI_CREDITS_KEY,
|
|
2099
|
+
LOCAL_AUTH_KEY_PREFIX,
|
|
2100
|
+
readStoredBaseUrlSync,
|
|
1920
2101
|
AuthStorage,
|
|
1921
2102
|
NotLoggedInError,
|
|
1922
2103
|
MODELS,
|
|
2104
|
+
registerRuntimeModels,
|
|
2105
|
+
clearRuntimeModels,
|
|
2106
|
+
getAllModels,
|
|
1923
2107
|
getModel,
|
|
1924
2108
|
getModelsForProvider,
|
|
1925
2109
|
getAuthStorageKeys,
|
|
@@ -1931,7 +2115,8 @@ export {
|
|
|
1931
2115
|
getToolResultCharLimit,
|
|
1932
2116
|
getContextWindow,
|
|
1933
2117
|
getMaxThinkingLevel,
|
|
2118
|
+
getDefaultThinkingLevel,
|
|
1934
2119
|
getSummaryModel,
|
|
1935
2120
|
getFastModel
|
|
1936
2121
|
};
|
|
1937
|
-
//# sourceMappingURL=chunk-
|
|
2122
|
+
//# sourceMappingURL=chunk-TPGCBL7I.js.map
|