@prestyj/core 5.8.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/index.cjs CHANGED
@@ -51,6 +51,7 @@ __export(index_exports, {
51
51
  getClaudeCodeVersion: () => getClaudeCodeVersion,
52
52
  getContextWindow: () => getContextWindow,
53
53
  getDefaultModel: () => getDefaultModel,
54
+ getDefaultThinkingLevel: () => getDefaultThinkingLevel,
54
55
  getFastModel: () => getFastModel,
55
56
  getMaxThinkingLevel: () => getMaxThinkingLevel,
56
57
  getModel: () => getModel,
@@ -73,6 +74,7 @@ __export(index_exports, {
73
74
  loginKimi: () => loginKimi,
74
75
  loginOpenAI: () => loginOpenAI,
75
76
  openLog: () => openLog,
77
+ readStoredBaseUrlSync: () => readStoredBaseUrlSync,
76
78
  refreshAnthropicToken: () => refreshAnthropicToken,
77
79
  refreshGeminiToken: () => refreshGeminiToken,
78
80
  refreshKimiToken: () => refreshKimiToken,
@@ -86,9 +88,12 @@ __export(index_exports, {
86
88
  });
87
89
  module.exports = __toCommonJS(index_exports);
88
90
 
89
- // src/auth-storage.ts
90
- var import_promises4 = __toESM(require("fs/promises"), 1);
91
- var import_node_crypto6 = __toESM(require("crypto"), 1);
91
+ // src/oauth/kimi.ts
92
+ var import_node_child_process = require("child_process");
93
+ var import_node_crypto = require("crypto");
94
+ var import_node_fs = require("fs");
95
+ var import_node_os2 = require("os");
96
+ var import_node_path2 = __toESM(require("path"), 1);
92
97
 
93
98
  // src/paths.ts
94
99
  var import_node_path = __toESM(require("path"), 1);
@@ -115,8 +120,225 @@ function getAppPaths() {
115
120
  };
116
121
  }
117
122
 
123
+ // src/oauth/kimi.ts
124
+ var CLIENT_ID = "17e5f671-d194-4dfb-9706-5516cb48c098";
125
+ var DEFAULT_OAUTH_HOST = "https://auth.kimi.com";
126
+ var DEFAULT_CODING_BASE_URL = "https://api.kimi.com/coding/v1";
127
+ var KIMI_PLATFORM = "kimi_code_cli";
128
+ var DEFAULT_KIMI_VERSION = "1.0.11";
129
+ var DEVICE_TIMEOUT_MS = 15 * 60 * 1e3;
130
+ function oauthHost() {
131
+ const host = process.env.KIMI_CODE_OAUTH_HOST ?? process.env.KIMI_OAUTH_HOST ?? DEFAULT_OAUTH_HOST;
132
+ return host.replace(/\/+$/, "");
133
+ }
134
+ function kimiCodeBaseUrl() {
135
+ return (process.env.KIMI_CODE_BASE_URL ?? DEFAULT_CODING_BASE_URL).replace(/\/+$/, "");
136
+ }
137
+ function kimiVersion() {
138
+ const v = process.env.KIMI_CODE_VERSION ?? DEFAULT_KIMI_VERSION;
139
+ return asciiHeader(v, DEFAULT_KIMI_VERSION);
140
+ }
141
+ function asciiHeader(value, fallback = "unknown") {
142
+ const cleaned = value.replace(/[^\u0020-\u007E]/g, "").trim();
143
+ return cleaned.length > 0 ? cleaned : fallback;
144
+ }
145
+ function macOsProductVersion() {
146
+ try {
147
+ const version = (0, import_node_child_process.execFileSync)("/usr/bin/sw_vers", ["-productVersion"], {
148
+ encoding: "utf-8",
149
+ timeout: 1e3
150
+ }).trim();
151
+ return version.length > 0 ? version : void 0;
152
+ } catch {
153
+ return void 0;
154
+ }
155
+ }
156
+ function deviceModel() {
157
+ const os2 = (0, import_node_os2.type)();
158
+ const version = (0, import_node_os2.release)();
159
+ const osArch = (0, import_node_os2.arch)();
160
+ if (os2 === "Darwin") return `macOS ${macOsProductVersion() ?? version} ${osArch}`;
161
+ if (os2 === "Windows_NT") return `Windows ${version} ${osArch}`;
162
+ return `${os2} ${version} ${osArch}`.trim();
163
+ }
164
+ function deviceId() {
165
+ const idPath = import_node_path2.default.join(getAppPaths().agentDir, "kimi_device_id");
166
+ if ((0, import_node_fs.existsSync)(idPath)) {
167
+ try {
168
+ const text = (0, import_node_fs.readFileSync)(idPath, "utf-8").trim();
169
+ if (text.length > 0) return text;
170
+ } catch {
171
+ }
172
+ }
173
+ const id = (0, import_node_crypto.randomUUID)();
174
+ try {
175
+ (0, import_node_fs.mkdirSync)(getAppPaths().agentDir, { recursive: true, mode: 448 });
176
+ (0, import_node_fs.writeFileSync)(idPath, id, { encoding: "utf-8", mode: 384 });
177
+ } catch {
178
+ }
179
+ return id;
180
+ }
181
+ function deviceHeaders() {
182
+ return {
183
+ "X-Msh-Platform": KIMI_PLATFORM,
184
+ "X-Msh-Version": kimiVersion(),
185
+ "X-Msh-Device-Name": asciiHeader((0, import_node_os2.hostname)()),
186
+ "X-Msh-Device-Model": asciiHeader(deviceModel()),
187
+ "X-Msh-Os-Version": asciiHeader((0, import_node_os2.release)()),
188
+ "X-Msh-Device-Id": deviceId()
189
+ };
190
+ }
191
+ function kimiCodingHeaders() {
192
+ return {
193
+ "User-Agent": `kimi-code-cli/${kimiVersion()}`,
194
+ ...deviceHeaders()
195
+ };
196
+ }
197
+ function isKimiCodingEndpoint(baseUrl) {
198
+ if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
199
+ const normalized = baseUrl.replace(/\/+$/, "");
200
+ return normalized === kimiCodeBaseUrl() || /(^|\.)kimi\.com/i.test(normalized);
201
+ }
202
+ async function postForm(endpoint, params) {
203
+ const response = await fetch(`${oauthHost()}${endpoint}`, {
204
+ method: "POST",
205
+ headers: {
206
+ ...deviceHeaders(),
207
+ "Content-Type": "application/x-www-form-urlencoded",
208
+ Accept: "application/json"
209
+ },
210
+ body: new URLSearchParams(params).toString()
211
+ });
212
+ let data = {};
213
+ try {
214
+ const parsed = await response.json();
215
+ if (parsed && typeof parsed === "object") data = parsed;
216
+ } catch {
217
+ }
218
+ return { status: response.status, data };
219
+ }
220
+ function errorDetail(data) {
221
+ const desc = data.error_description ?? data.message ?? data.error;
222
+ return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
223
+ }
224
+ function credsFromTokenResponse(data, opts) {
225
+ const accessToken = data.access_token;
226
+ const responseRefreshToken = data.refresh_token;
227
+ const expiresIn = Number(data.expires_in);
228
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
229
+ throw new Error("Kimi OAuth response missing access_token.");
230
+ }
231
+ const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
232
+ if (refreshToken.length === 0) {
233
+ throw new Error("Kimi OAuth response missing refresh_token.");
234
+ }
235
+ if (!Number.isFinite(expiresIn) || expiresIn <= 0) {
236
+ throw new Error("Kimi OAuth response missing or invalid expires_in.");
237
+ }
238
+ return {
239
+ accessToken,
240
+ refreshToken,
241
+ expiresAt: Date.now() + expiresIn * 1e3,
242
+ baseUrl: kimiCodeBaseUrl()
243
+ };
244
+ }
245
+ async function requestDeviceAuthorization() {
246
+ const { status, data } = await postForm("/api/oauth/device_authorization", {
247
+ client_id: CLIENT_ID
248
+ });
249
+ if (status !== 200) {
250
+ throw new Error(`Kimi device authorization failed (${status}): ${errorDetail(data)}`);
251
+ }
252
+ const userCode = data.user_code;
253
+ const deviceCode = data.device_code;
254
+ const verificationUriComplete = data.verification_uri_complete;
255
+ if (typeof userCode !== "string" || typeof deviceCode !== "string") {
256
+ throw new Error("Kimi device authorization response missing user_code/device_code.");
257
+ }
258
+ return {
259
+ userCode,
260
+ deviceCode,
261
+ verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
262
+ verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
263
+ interval: Number(data.interval ?? 5) || 5
264
+ };
265
+ }
266
+ async function pollDeviceToken(deviceCode) {
267
+ const { status, data } = await postForm("/api/oauth/token", {
268
+ client_id: CLIENT_ID,
269
+ device_code: deviceCode,
270
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
271
+ });
272
+ if (status === 200 && typeof data.access_token === "string") {
273
+ return { kind: "success", creds: credsFromTokenResponse(data) };
274
+ }
275
+ if (status >= 500) {
276
+ throw new Error(`Kimi token polling server error (${status}): ${errorDetail(data)}`);
277
+ }
278
+ const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
279
+ switch (errorCode) {
280
+ case "authorization_pending":
281
+ return { kind: "pending" };
282
+ case "slow_down":
283
+ return { kind: "slow_down" };
284
+ case "expired_token":
285
+ return { kind: "expired" };
286
+ case "access_denied":
287
+ return { kind: "denied" };
288
+ default:
289
+ throw new Error(`Kimi token polling failed (${status}): ${errorDetail(data)}`);
290
+ }
291
+ }
292
+ function sleep(ms) {
293
+ return new Promise((resolve) => {
294
+ setTimeout(resolve, ms);
295
+ });
296
+ }
297
+ async function loginKimi(callbacks) {
298
+ const auth = await requestDeviceAuthorization();
299
+ callbacks.onStatus(
300
+ `Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
301
+ );
302
+ callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
303
+ callbacks.onStatus("Waiting for you to authorize in the browser...");
304
+ const deadline = Date.now() + DEVICE_TIMEOUT_MS;
305
+ let interval = Math.max(auth.interval, 1);
306
+ while (Date.now() < deadline) {
307
+ await sleep(interval * 1e3);
308
+ const result = await pollDeviceToken(auth.deviceCode);
309
+ if (result.kind === "success") return result.creds;
310
+ if (result.kind === "denied") {
311
+ throw new Error("Kimi authorization was denied.");
312
+ }
313
+ if (result.kind === "expired") {
314
+ throw new Error("Kimi device code expired. Please run login again.");
315
+ }
316
+ if (result.kind === "slow_down") {
317
+ interval += 5;
318
+ }
319
+ }
320
+ throw new Error("Kimi login timed out. Please run login again.");
321
+ }
322
+ async function refreshKimiToken(refreshToken) {
323
+ const { status, data } = await postForm("/api/oauth/token", {
324
+ client_id: CLIENT_ID,
325
+ grant_type: "refresh_token",
326
+ refresh_token: refreshToken
327
+ });
328
+ if (status === 200 && typeof data.access_token === "string") {
329
+ return credsFromTokenResponse(data, { fallbackRefreshToken: refreshToken });
330
+ }
331
+ const errorCode = typeof data.error === "string" ? data.error : "";
332
+ throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
333
+ }
334
+
335
+ // src/auth-storage.ts
336
+ var import_promises4 = __toESM(require("fs/promises"), 1);
337
+ var import_node_fs3 = require("fs");
338
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
339
+
118
340
  // src/oauth/anthropic.ts
119
- var import_node_crypto2 = __toESM(require("crypto"), 1);
341
+ var import_node_crypto3 = __toESM(require("crypto"), 1);
120
342
 
121
343
  // src/oauth/pkce.ts
122
344
  function base64urlEncode(bytes) {
@@ -138,49 +360,66 @@ async function generatePKCE() {
138
360
 
139
361
  // src/claude-code-version.ts
140
362
  var import_promises = __toESM(require("fs/promises"), 1);
141
- var import_node_path3 = __toESM(require("path"), 1);
363
+ var import_node_path4 = __toESM(require("path"), 1);
142
364
 
143
365
  // src/logger.ts
144
- var import_node_fs = __toESM(require("fs"), 1);
145
- var import_node_path2 = __toESM(require("path"), 1);
146
- var import_node_crypto = require("crypto");
366
+ var import_node_fs2 = __toESM(require("fs"), 1);
367
+ var import_node_path3 = __toESM(require("path"), 1);
368
+ var import_node_crypto2 = require("crypto");
147
369
  var import_ai = require("@prestyj/ai");
148
370
  var MAX_BYTES = 10 * 1024 * 1024;
149
371
  var fd = null;
372
+ var bytesWritten = 0;
373
+ var capped = false;
150
374
  var sessionId = "";
151
375
  var appName = "app";
152
376
  var cleanups = [];
153
377
  var exactSecrets = [];
154
378
  function rotateIfNeeded(filePath) {
155
379
  try {
156
- const st = import_node_fs.default.statSync(filePath);
380
+ const st = import_node_fs2.default.statSync(filePath);
157
381
  if (st.size < MAX_BYTES) return;
158
382
  const rotated = `${filePath}.1`;
159
383
  try {
160
- import_node_fs.default.unlinkSync(rotated);
384
+ import_node_fs2.default.unlinkSync(rotated);
161
385
  } catch {
162
386
  }
163
- import_node_fs.default.renameSync(filePath, rotated);
387
+ import_node_fs2.default.renameSync(filePath, rotated);
164
388
  } catch {
165
389
  }
166
390
  }
167
391
  function openLog(filePath, name) {
168
- if (fd !== null) return false;
392
+ if (fd !== null || capped) return false;
169
393
  appName = name;
170
394
  exactSecrets = (0, import_ai.environmentSecrets)(process.env);
171
395
  try {
172
- import_node_fs.default.mkdirSync(import_node_path2.default.dirname(filePath), { recursive: true, mode: 448 });
396
+ import_node_fs2.default.mkdirSync(import_node_path3.default.dirname(filePath), { recursive: true, mode: 448 });
173
397
  } catch {
174
398
  }
175
399
  rotateIfNeeded(filePath);
176
400
  try {
177
- fd = import_node_fs.default.openSync(filePath, "a");
401
+ fd = import_node_fs2.default.openSync(filePath, "a");
178
402
  } catch {
403
+ fd = null;
404
+ bytesWritten = 0;
179
405
  return false;
180
406
  }
181
- sessionId = (0, import_node_crypto.randomBytes)(4).toString("hex");
182
407
  try {
183
- import_node_fs.default.writeSync(fd, "\n");
408
+ bytesWritten = import_node_fs2.default.fstatSync(fd).size;
409
+ } catch {
410
+ try {
411
+ import_node_fs2.default.closeSync(fd);
412
+ } catch {
413
+ }
414
+ fd = null;
415
+ bytesWritten = 0;
416
+ return false;
417
+ }
418
+ sessionId = (0, import_node_crypto2.randomBytes)(4).toString("hex");
419
+ try {
420
+ if (bytesWritten < MAX_BYTES) {
421
+ bytesWritten += import_node_fs2.default.writeSync(fd, "\n");
422
+ }
184
423
  } catch {
185
424
  }
186
425
  return true;
@@ -206,8 +445,23 @@ function log(level, category, message, data) {
206
445
  if (pairs) line += ` ${pairs}`;
207
446
  }
208
447
  line += "\n";
448
+ const lineBytes = Buffer.byteLength(line);
449
+ if (bytesWritten + lineBytes > MAX_BYTES) {
450
+ const capLine = `[${ts}] [sid=${sessionId}] [WARN] [logger] Log cap reached; file logging disabled until restart
451
+ `;
452
+ try {
453
+ if (bytesWritten + Buffer.byteLength(capLine) <= MAX_BYTES) {
454
+ bytesWritten += import_node_fs2.default.writeSync(fd, capLine);
455
+ }
456
+ import_node_fs2.default.closeSync(fd);
457
+ } catch {
458
+ }
459
+ fd = null;
460
+ capped = true;
461
+ return;
462
+ }
209
463
  try {
210
- import_node_fs.default.writeSync(fd, line);
464
+ bytesWritten += import_node_fs2.default.writeSync(fd, line);
211
465
  } catch {
212
466
  }
213
467
  }
@@ -215,13 +469,16 @@ function registerLogCleanup(fn) {
215
469
  cleanups.push(fn);
216
470
  }
217
471
  function closeLogger(opts) {
218
- if (fd === null) return;
219
- if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
220
- try {
221
- import_node_fs.default.closeSync(fd);
222
- } catch {
472
+ if (fd !== null) {
473
+ if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
474
+ try {
475
+ if (fd !== null) import_node_fs2.default.closeSync(fd);
476
+ } catch {
477
+ }
223
478
  }
224
479
  fd = null;
480
+ bytesWritten = 0;
481
+ capped = false;
225
482
  exactSecrets = [];
226
483
  for (const unsub of cleanups) unsub();
227
484
  cleanups = [];
@@ -235,7 +492,7 @@ var FALLBACK_VERSION = "2.1.88";
235
492
  var memoryCache = null;
236
493
  var inflight = null;
237
494
  function cachePath() {
238
- return import_node_path3.default.join(getAppPaths().agentDir, "claude-code-version.json");
495
+ return import_node_path4.default.join(getAppPaths().agentDir, "claude-code-version.json");
239
496
  }
240
497
  async function readDiskCache() {
241
498
  try {
@@ -317,7 +574,7 @@ async function getClaudeCliUserAgent() {
317
574
  }
318
575
 
319
576
  // src/oauth/anthropic.ts
320
- var CLIENT_ID = atob("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
577
+ var CLIENT_ID2 = atob("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
321
578
  var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
322
579
  var TOKEN_URLS = [
323
580
  "https://platform.claude.com/v1/oauth/token",
@@ -361,10 +618,10 @@ function toCredentials(data) {
361
618
  }
362
619
  async function loginAnthropic(callbacks) {
363
620
  const { verifier, challenge } = await generatePKCE();
364
- const state = import_node_crypto2.default.randomBytes(16).toString("hex");
621
+ const state = import_node_crypto3.default.randomBytes(16).toString("hex");
365
622
  const params = new URLSearchParams({
366
623
  code: "true",
367
- client_id: CLIENT_ID,
624
+ client_id: CLIENT_ID2,
368
625
  response_type: "code",
369
626
  redirect_uri: REDIRECT_URI,
370
627
  scope: SCOPES,
@@ -385,7 +642,7 @@ async function exchangeAnthropicCode(code, state, verifier) {
385
642
  const data = await postTokenRequest(
386
643
  {
387
644
  grant_type: "authorization_code",
388
- client_id: CLIENT_ID,
645
+ client_id: CLIENT_ID2,
389
646
  code,
390
647
  state,
391
648
  redirect_uri: REDIRECT_URI,
@@ -399,7 +656,7 @@ async function refreshAnthropicToken(refreshToken) {
399
656
  const data = await postTokenRequest(
400
657
  {
401
658
  grant_type: "refresh_token",
402
- client_id: CLIENT_ID,
659
+ client_id: CLIENT_ID2,
403
660
  refresh_token: refreshToken
404
661
  },
405
662
  "token refresh"
@@ -409,8 +666,8 @@ async function refreshAnthropicToken(refreshToken) {
409
666
 
410
667
  // src/oauth/openai.ts
411
668
  var import_node_http = __toESM(require("http"), 1);
412
- var import_node_crypto3 = __toESM(require("crypto"), 1);
413
- var CLIENT_ID2 = "app_EMoamEEZ73f0CkXaXp7hrann";
669
+ var import_node_crypto4 = __toESM(require("crypto"), 1);
670
+ var CLIENT_ID3 = "app_EMoamEEZ73f0CkXaXp7hrann";
414
671
  var AUTHORIZE_URL2 = "https://auth.openai.com/oauth/authorize";
415
672
  var TOKEN_URL = "https://auth.openai.com/oauth/token";
416
673
  var REDIRECT_URI2 = "http://localhost:1455/auth/callback";
@@ -418,10 +675,10 @@ var SCOPE = "openid profile email offline_access api.connectors.read api.connect
418
675
  var JWT_CLAIM_PATH = "https://api.openai.com/auth";
419
676
  async function loginOpenAI(callbacks) {
420
677
  const { verifier, challenge } = await generatePKCE();
421
- const state = import_node_crypto3.default.randomBytes(16).toString("hex");
678
+ const state = import_node_crypto4.default.randomBytes(16).toString("hex");
422
679
  const url = new URL(AUTHORIZE_URL2);
423
680
  url.searchParams.set("response_type", "code");
424
- url.searchParams.set("client_id", CLIENT_ID2);
681
+ url.searchParams.set("client_id", CLIENT_ID3);
425
682
  url.searchParams.set("redirect_uri", REDIRECT_URI2);
426
683
  url.searchParams.set("scope", SCOPE);
427
684
  url.searchParams.set("code_challenge", challenge);
@@ -542,7 +799,7 @@ async function exchangeOpenAICode(code, verifier) {
542
799
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
543
800
  body: new URLSearchParams({
544
801
  grant_type: "authorization_code",
545
- client_id: CLIENT_ID2,
802
+ client_id: CLIENT_ID3,
546
803
  code,
547
804
  redirect_uri: REDIRECT_URI2,
548
805
  code_verifier: verifier
@@ -566,7 +823,7 @@ async function refreshOpenAIToken(refreshToken) {
566
823
  body: new URLSearchParams({
567
824
  grant_type: "refresh_token",
568
825
  refresh_token: refreshToken,
569
- client_id: CLIENT_ID2
826
+ client_id: CLIENT_ID3
570
827
  })
571
828
  });
572
829
  if (!response.ok) {
@@ -588,7 +845,7 @@ async function refreshOpenAIToken(refreshToken) {
588
845
 
589
846
  // src/oauth/gemini.ts
590
847
  var import_node_http2 = __toESM(require("http"), 1);
591
- var import_node_crypto4 = __toESM(require("crypto"), 1);
848
+ var import_node_crypto5 = __toESM(require("crypto"), 1);
592
849
  var CLIENT_ID_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_ID";
593
850
  var CLIENT_SECRET_ENV = "GGCODER_GEMINI_OAUTH_CLIENT_SECRET";
594
851
  var DEFAULT_CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
@@ -622,7 +879,7 @@ var CodeAssistHttpError = class extends Error {
622
879
  async function loginGemini(callbacks) {
623
880
  const { clientId, clientSecret } = getGeminiOAuthClientCredentials();
624
881
  const { verifier, challenge } = await generatePKCE();
625
- const state = import_node_crypto4.default.randomBytes(32).toString("hex");
882
+ const state = import_node_crypto5.default.randomBytes(32).toString("hex");
626
883
  const redirectUri = await getLoopbackRedirectUri();
627
884
  const url = new URL(AUTHORIZE_URL3);
628
885
  url.searchParams.set("response_type", "code");
@@ -863,307 +1120,90 @@ async function loadCodeAssist(accessToken, envProject, metadata) {
863
1120
  }
864
1121
  }
865
1122
  function getValidationRequiredTier(response) {
866
- return response.ineligibleTiers?.find(
867
- (tier) => tier.reasonCode === VALIDATION_REQUIRED_REASON && typeof tier.validationUrl === "string"
868
- );
869
- }
870
- function getOnboardTier(response) {
871
- const defaultTier = response.allowedTiers?.find((tier) => tier.isDefault);
872
- return defaultTier ?? { id: USER_TIER_LEGACY, name: "" };
873
- }
874
- function throwProjectError(response) {
875
- const reasons = response.ineligibleTiers?.map((tier) => tier.reasonMessage ?? tier.tierName).filter((reason) => Boolean(reason));
876
- if (reasons && reasons.length > 0) {
877
- throw new Error(`Gemini Code Assist setup failed: ${reasons.join(", ")}`);
878
- }
879
- throw new Error(
880
- "Gemini requires a Google Cloud project for this account. Set GOOGLE_CLOUD_PROJECT and try again."
881
- );
882
- }
883
- async function codeAssistPost(accessToken, method, body) {
884
- let lastError;
885
- for (let attempt = 0; attempt <= CODE_ASSIST_POST_RETRIES; attempt++) {
886
- try {
887
- return await codeAssistRequest(getCodeAssistMethodUrl(method), accessToken, method, {
888
- method: "POST",
889
- body: JSON.stringify(body)
890
- });
891
- } catch (err) {
892
- if (!(err instanceof CodeAssistHttpError) || attempt === CODE_ASSIST_POST_RETRIES || !shouldRetryCodeAssistStatus(err.status)) {
893
- throw err;
894
- }
895
- lastError = err;
896
- }
897
- await new Promise((resolve) => setTimeout(resolve, CODE_ASSIST_POST_RETRY_DELAY_MS));
898
- }
899
- throw lastError ?? new Error(`Gemini Code Assist ${method} failed.`);
900
- }
901
- async function codeAssistGet(accessToken, operationName) {
902
- return codeAssistRequest(getCodeAssistOperationUrl(operationName), accessToken, "operation", {
903
- method: "GET"
904
- });
905
- }
906
- async function codeAssistRequest(url, accessToken, label, init) {
907
- const response = await fetch(url, {
908
- ...init,
909
- headers: codeAssistHeaders(accessToken)
910
- });
911
- if (!response.ok) {
912
- const text = await response.text();
913
- throw new CodeAssistHttpError(label, response.status, text);
914
- }
915
- return await response.json();
916
- }
917
- function getCodeAssistBaseUrl() {
918
- const endpoint = process.env.CODE_ASSIST_ENDPOINT ?? CODE_ASSIST_BASE_URL;
919
- const version = process.env.CODE_ASSIST_API_VERSION || CODE_ASSIST_API_VERSION;
920
- return `${endpoint}/${version}`;
921
- }
922
- function getCodeAssistMethodUrl(method) {
923
- return `${getCodeAssistBaseUrl()}:${method}`;
924
- }
925
- function getCodeAssistOperationUrl(operationName) {
926
- return `${getCodeAssistBaseUrl()}/${operationName}`;
927
- }
928
- function shouldRetryCodeAssistStatus(status) {
929
- return status === 429 || status === 499 || status >= 500 && status <= 599;
930
- }
931
- function isVpcScAffectedError(error) {
932
- try {
933
- const parsed = JSON.parse(error.body);
934
- if (!parsed || typeof parsed !== "object" || !("error" in parsed)) return false;
935
- const details = parsed.error?.details;
936
- return Array.isArray(details) ? details.some(
937
- (detail) => detail != null && typeof detail === "object" && "reason" in detail && detail.reason === VPC_SC_REASON
938
- ) : false;
939
- } catch {
940
- return false;
941
- }
942
- }
943
- function codeAssistHeaders(accessToken) {
944
- return {
945
- Authorization: `Bearer ${accessToken}`,
946
- "Content-Type": "application/json",
947
- "User-Agent": "google-gemini-cli",
948
- "X-Goog-Api-Client": "gemini-cli/0.0.0"
949
- };
950
- }
951
-
952
- // src/oauth/kimi.ts
953
- var import_node_child_process = require("child_process");
954
- var import_node_crypto5 = require("crypto");
955
- var import_node_fs2 = require("fs");
956
- var import_node_os2 = require("os");
957
- var import_node_path4 = __toESM(require("path"), 1);
958
- var CLIENT_ID3 = "17e5f671-d194-4dfb-9706-5516cb48c098";
959
- var DEFAULT_OAUTH_HOST = "https://auth.kimi.com";
960
- var DEFAULT_CODING_BASE_URL = "https://api.kimi.com/coding/v1";
961
- var KIMI_PLATFORM = "kimi_code_cli";
962
- var DEFAULT_KIMI_VERSION = "1.0.11";
963
- var DEVICE_TIMEOUT_MS = 15 * 60 * 1e3;
964
- function oauthHost() {
965
- const host = process.env.KIMI_CODE_OAUTH_HOST ?? process.env.KIMI_OAUTH_HOST ?? DEFAULT_OAUTH_HOST;
966
- return host.replace(/\/+$/, "");
967
- }
968
- function kimiCodeBaseUrl() {
969
- return (process.env.KIMI_CODE_BASE_URL ?? DEFAULT_CODING_BASE_URL).replace(/\/+$/, "");
970
- }
971
- function kimiVersion() {
972
- const v = process.env.KIMI_CODE_VERSION ?? DEFAULT_KIMI_VERSION;
973
- return asciiHeader(v, DEFAULT_KIMI_VERSION);
974
- }
975
- function asciiHeader(value, fallback = "unknown") {
976
- const cleaned = value.replace(/[^\u0020-\u007E]/g, "").trim();
977
- return cleaned.length > 0 ? cleaned : fallback;
978
- }
979
- function macOsProductVersion() {
980
- try {
981
- const version = (0, import_node_child_process.execFileSync)("/usr/bin/sw_vers", ["-productVersion"], {
982
- encoding: "utf-8",
983
- timeout: 1e3
984
- }).trim();
985
- return version.length > 0 ? version : void 0;
986
- } catch {
987
- return void 0;
988
- }
989
- }
990
- function deviceModel() {
991
- const os2 = (0, import_node_os2.type)();
992
- const version = (0, import_node_os2.release)();
993
- const osArch = (0, import_node_os2.arch)();
994
- if (os2 === "Darwin") return `macOS ${macOsProductVersion() ?? version} ${osArch}`;
995
- if (os2 === "Windows_NT") return `Windows ${version} ${osArch}`;
996
- return `${os2} ${version} ${osArch}`.trim();
997
- }
998
- function deviceId() {
999
- const idPath = import_node_path4.default.join(getAppPaths().agentDir, "kimi_device_id");
1000
- if ((0, import_node_fs2.existsSync)(idPath)) {
1001
- try {
1002
- const text = (0, import_node_fs2.readFileSync)(idPath, "utf-8").trim();
1003
- if (text.length > 0) return text;
1004
- } catch {
1005
- }
1006
- }
1007
- const id = (0, import_node_crypto5.randomUUID)();
1008
- try {
1009
- (0, import_node_fs2.mkdirSync)(getAppPaths().agentDir, { recursive: true, mode: 448 });
1010
- (0, import_node_fs2.writeFileSync)(idPath, id, { encoding: "utf-8", mode: 384 });
1011
- } catch {
1012
- }
1013
- return id;
1014
- }
1015
- function deviceHeaders() {
1016
- return {
1017
- "X-Msh-Platform": KIMI_PLATFORM,
1018
- "X-Msh-Version": kimiVersion(),
1019
- "X-Msh-Device-Name": asciiHeader((0, import_node_os2.hostname)()),
1020
- "X-Msh-Device-Model": asciiHeader(deviceModel()),
1021
- "X-Msh-Os-Version": asciiHeader((0, import_node_os2.release)()),
1022
- "X-Msh-Device-Id": deviceId()
1023
- };
1024
- }
1025
- function kimiCodingHeaders() {
1026
- return {
1027
- "User-Agent": `kimi-code-cli/${kimiVersion()}`,
1028
- ...deviceHeaders()
1029
- };
1030
- }
1031
- function isKimiCodingEndpoint(baseUrl) {
1032
- if (typeof baseUrl !== "string" || baseUrl.length === 0) return false;
1033
- const normalized = baseUrl.replace(/\/+$/, "");
1034
- return normalized === kimiCodeBaseUrl() || /(^|\.)kimi\.com/i.test(normalized);
1035
- }
1036
- async function postForm(endpoint, params) {
1037
- const response = await fetch(`${oauthHost()}${endpoint}`, {
1038
- method: "POST",
1039
- headers: {
1040
- ...deviceHeaders(),
1041
- "Content-Type": "application/x-www-form-urlencoded",
1042
- Accept: "application/json"
1043
- },
1044
- body: new URLSearchParams(params).toString()
1045
- });
1046
- let data = {};
1047
- try {
1048
- const parsed = await response.json();
1049
- if (parsed && typeof parsed === "object") data = parsed;
1050
- } catch {
1051
- }
1052
- return { status: response.status, data };
1123
+ return response.ineligibleTiers?.find(
1124
+ (tier) => tier.reasonCode === VALIDATION_REQUIRED_REASON && typeof tier.validationUrl === "string"
1125
+ );
1053
1126
  }
1054
- function errorDetail(data) {
1055
- const desc = data.error_description ?? data.message ?? data.error;
1056
- return typeof desc === "string" && desc.length > 0 ? desc : "unknown error";
1127
+ function getOnboardTier(response) {
1128
+ const defaultTier = response.allowedTiers?.find((tier) => tier.isDefault);
1129
+ return defaultTier ?? { id: USER_TIER_LEGACY, name: "" };
1057
1130
  }
1058
- function credsFromTokenResponse(data, opts) {
1059
- const accessToken = data.access_token;
1060
- const responseRefreshToken = data.refresh_token;
1061
- const expiresIn = Number(data.expires_in);
1062
- if (typeof accessToken !== "string" || accessToken.length === 0) {
1063
- throw new Error("Kimi OAuth response missing access_token.");
1064
- }
1065
- const refreshToken = typeof responseRefreshToken === "string" && responseRefreshToken.length > 0 ? responseRefreshToken : opts?.fallbackRefreshToken ?? "";
1066
- if (refreshToken.length === 0) {
1067
- throw new Error("Kimi OAuth response missing refresh_token.");
1131
+ function throwProjectError(response) {
1132
+ const reasons = response.ineligibleTiers?.map((tier) => tier.reasonMessage ?? tier.tierName).filter((reason) => Boolean(reason));
1133
+ if (reasons && reasons.length > 0) {
1134
+ throw new Error(`Gemini Code Assist setup failed: ${reasons.join(", ")}`);
1068
1135
  }
1069
- if (!Number.isFinite(expiresIn) || expiresIn <= 0) {
1070
- throw new Error("Kimi OAuth response missing or invalid expires_in.");
1136
+ throw new Error(
1137
+ "Gemini requires a Google Cloud project for this account. Set GOOGLE_CLOUD_PROJECT and try again."
1138
+ );
1139
+ }
1140
+ async function codeAssistPost(accessToken, method, body) {
1141
+ let lastError;
1142
+ for (let attempt = 0; attempt <= CODE_ASSIST_POST_RETRIES; attempt++) {
1143
+ try {
1144
+ return await codeAssistRequest(getCodeAssistMethodUrl(method), accessToken, method, {
1145
+ method: "POST",
1146
+ body: JSON.stringify(body)
1147
+ });
1148
+ } catch (err) {
1149
+ if (!(err instanceof CodeAssistHttpError) || attempt === CODE_ASSIST_POST_RETRIES || !shouldRetryCodeAssistStatus(err.status)) {
1150
+ throw err;
1151
+ }
1152
+ lastError = err;
1153
+ }
1154
+ await new Promise((resolve) => setTimeout(resolve, CODE_ASSIST_POST_RETRY_DELAY_MS));
1071
1155
  }
1072
- return {
1073
- accessToken,
1074
- refreshToken,
1075
- expiresAt: Date.now() + expiresIn * 1e3,
1076
- baseUrl: kimiCodeBaseUrl()
1077
- };
1156
+ throw lastError ?? new Error(`Gemini Code Assist ${method} failed.`);
1078
1157
  }
1079
- async function requestDeviceAuthorization() {
1080
- const { status, data } = await postForm("/api/oauth/device_authorization", {
1081
- client_id: CLIENT_ID3
1158
+ async function codeAssistGet(accessToken, operationName) {
1159
+ return codeAssistRequest(getCodeAssistOperationUrl(operationName), accessToken, "operation", {
1160
+ method: "GET"
1082
1161
  });
1083
- if (status !== 200) {
1084
- throw new Error(`Kimi device authorization failed (${status}): ${errorDetail(data)}`);
1085
- }
1086
- const userCode = data.user_code;
1087
- const deviceCode = data.device_code;
1088
- const verificationUriComplete = data.verification_uri_complete;
1089
- if (typeof userCode !== "string" || typeof deviceCode !== "string") {
1090
- throw new Error("Kimi device authorization response missing user_code/device_code.");
1091
- }
1092
- return {
1093
- userCode,
1094
- deviceCode,
1095
- verificationUri: typeof data.verification_uri === "string" ? data.verification_uri : "",
1096
- verificationUriComplete: typeof verificationUriComplete === "string" ? verificationUriComplete : "",
1097
- interval: Number(data.interval ?? 5) || 5
1098
- };
1099
1162
  }
1100
- async function pollDeviceToken(deviceCode) {
1101
- const { status, data } = await postForm("/api/oauth/token", {
1102
- client_id: CLIENT_ID3,
1103
- device_code: deviceCode,
1104
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
1163
+ async function codeAssistRequest(url, accessToken, label, init) {
1164
+ const response = await fetch(url, {
1165
+ ...init,
1166
+ headers: codeAssistHeaders(accessToken)
1105
1167
  });
1106
- if (status === 200 && typeof data.access_token === "string") {
1107
- return { kind: "success", creds: credsFromTokenResponse(data) };
1108
- }
1109
- if (status >= 500) {
1110
- throw new Error(`Kimi token polling server error (${status}): ${errorDetail(data)}`);
1111
- }
1112
- const errorCode = typeof data.error === "string" ? data.error : "unknown_error";
1113
- switch (errorCode) {
1114
- case "authorization_pending":
1115
- return { kind: "pending" };
1116
- case "slow_down":
1117
- return { kind: "slow_down" };
1118
- case "expired_token":
1119
- return { kind: "expired" };
1120
- case "access_denied":
1121
- return { kind: "denied" };
1122
- default:
1123
- throw new Error(`Kimi token polling failed (${status}): ${errorDetail(data)}`);
1168
+ if (!response.ok) {
1169
+ const text = await response.text();
1170
+ throw new CodeAssistHttpError(label, response.status, text);
1124
1171
  }
1172
+ return await response.json();
1125
1173
  }
1126
- function sleep(ms) {
1127
- return new Promise((resolve) => {
1128
- setTimeout(resolve, ms);
1129
- });
1174
+ function getCodeAssistBaseUrl() {
1175
+ const endpoint = process.env.CODE_ASSIST_ENDPOINT ?? CODE_ASSIST_BASE_URL;
1176
+ const version = process.env.CODE_ASSIST_API_VERSION || CODE_ASSIST_API_VERSION;
1177
+ return `${endpoint}/${version}`;
1130
1178
  }
1131
- async function loginKimi(callbacks) {
1132
- const auth = await requestDeviceAuthorization();
1133
- callbacks.onStatus(
1134
- `Visit ${auth.verificationUri || auth.verificationUriComplete} and enter code: ${auth.userCode}`
1135
- );
1136
- callbacks.onOpenUrl(auth.verificationUriComplete || auth.verificationUri);
1137
- callbacks.onStatus("Waiting for you to authorize in the browser...");
1138
- const deadline = Date.now() + DEVICE_TIMEOUT_MS;
1139
- let interval = Math.max(auth.interval, 1);
1140
- while (Date.now() < deadline) {
1141
- await sleep(interval * 1e3);
1142
- const result = await pollDeviceToken(auth.deviceCode);
1143
- if (result.kind === "success") return result.creds;
1144
- if (result.kind === "denied") {
1145
- throw new Error("Kimi authorization was denied.");
1146
- }
1147
- if (result.kind === "expired") {
1148
- throw new Error("Kimi device code expired. Please run login again.");
1149
- }
1150
- if (result.kind === "slow_down") {
1151
- interval += 5;
1152
- }
1153
- }
1154
- throw new Error("Kimi login timed out. Please run login again.");
1179
+ function getCodeAssistMethodUrl(method) {
1180
+ return `${getCodeAssistBaseUrl()}:${method}`;
1155
1181
  }
1156
- async function refreshKimiToken(refreshToken) {
1157
- const { status, data } = await postForm("/api/oauth/token", {
1158
- client_id: CLIENT_ID3,
1159
- grant_type: "refresh_token",
1160
- refresh_token: refreshToken
1161
- });
1162
- if (status === 200 && typeof data.access_token === "string") {
1163
- return credsFromTokenResponse(data, { fallbackRefreshToken: refreshToken });
1182
+ function getCodeAssistOperationUrl(operationName) {
1183
+ return `${getCodeAssistBaseUrl()}/${operationName}`;
1184
+ }
1185
+ function shouldRetryCodeAssistStatus(status) {
1186
+ return status === 429 || status === 499 || status >= 500 && status <= 599;
1187
+ }
1188
+ function isVpcScAffectedError(error) {
1189
+ try {
1190
+ const parsed = JSON.parse(error.body);
1191
+ if (!parsed || typeof parsed !== "object" || !("error" in parsed)) return false;
1192
+ const details = parsed.error?.details;
1193
+ return Array.isArray(details) ? details.some(
1194
+ (detail) => detail != null && typeof detail === "object" && "reason" in detail && detail.reason === VPC_SC_REASON
1195
+ ) : false;
1196
+ } catch {
1197
+ return false;
1164
1198
  }
1165
- const errorCode = typeof data.error === "string" ? data.error : "";
1166
- throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
1199
+ }
1200
+ function codeAssistHeaders(accessToken) {
1201
+ return {
1202
+ Authorization: `Bearer ${accessToken}`,
1203
+ "Content-Type": "application/json",
1204
+ "User-Agent": "google-gemini-cli",
1205
+ "X-Goog-Api-Client": "gemini-cli/0.0.0"
1206
+ };
1167
1207
  }
1168
1208
 
1169
1209
  // src/file-lock.ts
@@ -1231,6 +1271,26 @@ function isAlive(pid) {
1231
1271
  // src/auth-storage.ts
1232
1272
  var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
1233
1273
  var XIAOMI_CREDITS_KEY = "xiaomi-credits";
1274
+ function activeBaseUrlEntry(data, provider) {
1275
+ if (provider === "moonshot") {
1276
+ const oauth = data[MOONSHOT_OAUTH_KEY];
1277
+ if (oauth) {
1278
+ const exhaustedUntil = oauth.usageExhaustedUntil ?? 0;
1279
+ if (Date.now() < exhaustedUntil && data["moonshot"]) return data["moonshot"];
1280
+ return oauth;
1281
+ }
1282
+ return data["moonshot"];
1283
+ }
1284
+ return data[provider];
1285
+ }
1286
+ function readStoredBaseUrlSync(authFile, provider) {
1287
+ try {
1288
+ const data = JSON.parse((0, import_node_fs3.readFileSync)(authFile, "utf-8"));
1289
+ return activeBaseUrlEntry(data, provider)?.baseUrl;
1290
+ } catch {
1291
+ return void 0;
1292
+ }
1293
+ }
1234
1294
  var REFRESH_SKEW_MS = 6e4;
1235
1295
  var USAGE_EXHAUSTED_DEFAULT_MS = 15 * 60 * 1e3;
1236
1296
  var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
@@ -1306,6 +1366,16 @@ var AuthStorage = class {
1306
1366
  }
1307
1367
  return STATIC_API_KEY_PROVIDERS.has(provider);
1308
1368
  }
1369
+ /**
1370
+ * The base URL on the credential that is active right now, if any.
1371
+ * Synchronous — call only after load()/resolveCredentials() populated the
1372
+ * snapshot. For `moonshot` this is the Kimi For Coding URL whenever the
1373
+ * OAuth entry is the one resolveCredentials would serve (i.e. not currently
1374
+ * usage-exhausted with an API key configured).
1375
+ */
1376
+ getStoredBaseUrl(provider) {
1377
+ return activeBaseUrlEntry(this.data, provider)?.baseUrl;
1378
+ }
1309
1379
  async load() {
1310
1380
  await withFileLock(this.filePath, async () => {
1311
1381
  try {
@@ -1337,27 +1407,46 @@ var AuthStorage = class {
1337
1407
  /**
1338
1408
  * Force a re-read from disk, discarding the in-memory cache. Needed when
1339
1409
  * another process mutates the auth file out-of-band — e.g. the desktop app
1340
- * writes API keys natively (Rust → ~/.ezcoder/auth.json) without going through
1341
- * this instance, so a long-lived daemon's cache would otherwise stay stale and
1342
- * never see a newly added provider key.
1410
+ * writes API keys natively without going through this instance.
1343
1411
  */
1344
1412
  async reload() {
1345
1413
  this.loaded = false;
1346
1414
  await this.load();
1347
1415
  }
1416
+ /**
1417
+ * Apply one provider-scoped mutation to the latest on-disk snapshot.
1418
+ * AuthStorage instances live in every app session/process, so writing this
1419
+ * instance's cached snapshot can erase credentials another instance just
1420
+ * added. The file lock only serializes writers; the re-read prevents stale
1421
+ * full-file overwrites.
1422
+ */
1423
+ async mutateLatest(mutator) {
1424
+ await this.ensureLoaded();
1425
+ await withFileLock(this.filePath, async () => {
1426
+ const latest = await readAuthData(this.filePath);
1427
+ mutator(latest);
1428
+ await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
1429
+ this.data = latest;
1430
+ });
1431
+ }
1432
+ async reloadLatest() {
1433
+ await withFileLock(this.filePath, async () => {
1434
+ this.data = await readAuthData(this.filePath);
1435
+ });
1436
+ }
1348
1437
  async getCredentials(provider) {
1349
1438
  await this.ensureLoaded();
1350
1439
  return this.data[provider];
1351
1440
  }
1352
1441
  async setCredentials(provider, creds) {
1353
- await this.ensureLoaded();
1354
- this.data[provider] = creds;
1355
- await this.save();
1442
+ await this.mutateLatest((latest) => {
1443
+ latest[provider] = creds;
1444
+ });
1356
1445
  }
1357
1446
  async clearCredentials(provider) {
1358
- await this.ensureLoaded();
1359
- delete this.data[provider];
1360
- await this.save();
1447
+ await this.mutateLatest((latest) => {
1448
+ delete latest[provider];
1449
+ });
1361
1450
  }
1362
1451
  /**
1363
1452
  * Mark the credential stored under `storageKey` as usage-exhausted until
@@ -1371,12 +1460,15 @@ var AuthStorage = class {
1371
1460
  * nothing is stored under `storageKey`.
1372
1461
  */
1373
1462
  async markUsageExhausted(storageKey, resetsAt) {
1374
- await this.ensureLoaded();
1375
- const creds = this.data[storageKey];
1376
- if (!creds) return;
1377
1463
  const until = resetsAt !== void 0 && resetsAt * 1e3 > Date.now() ? resetsAt * 1e3 : Date.now() + USAGE_EXHAUSTED_DEFAULT_MS;
1378
- creds.usageExhaustedUntil = until;
1379
- await this.save();
1464
+ let marked = false;
1465
+ await this.mutateLatest((latest) => {
1466
+ const creds = latest[storageKey];
1467
+ if (!creds) return;
1468
+ creds.usageExhaustedUntil = until;
1469
+ marked = true;
1470
+ });
1471
+ if (!marked) return;
1380
1472
  log(
1381
1473
  "WARN",
1382
1474
  "auth",
@@ -1384,8 +1476,11 @@ var AuthStorage = class {
1384
1476
  );
1385
1477
  }
1386
1478
  async clearAll() {
1387
- this.data = {};
1388
- await this.save();
1479
+ await this.ensureLoaded();
1480
+ await withFileLock(this.filePath, async () => {
1481
+ this.data = {};
1482
+ await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1483
+ });
1389
1484
  }
1390
1485
  /**
1391
1486
  * Returns valid credentials, auto-refreshing if expired.
@@ -1395,6 +1490,10 @@ var AuthStorage = class {
1395
1490
  */
1396
1491
  async resolveCredentials(provider, opts) {
1397
1492
  await this.ensureLoaded();
1493
+ const directStorageKeys = opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider) ? opts.storageKeys : provider === "moonshot" ? [MOONSHOT_OAUTH_KEY, "moonshot"] : [provider];
1494
+ if (!directStorageKeys.some((key) => Boolean(this.data[key]))) {
1495
+ await this.reloadLatest();
1496
+ }
1398
1497
  if (opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider)) {
1399
1498
  for (const key of opts.storageKeys) {
1400
1499
  const creds2 = this.data[key];
@@ -1441,41 +1540,44 @@ var AuthStorage = class {
1441
1540
  const existing = this.refreshLocks.get(provider);
1442
1541
  if (existing) return existing;
1443
1542
  const refreshPromise = withFileLock(this.filePath, async () => {
1444
- try {
1445
- const content = await import_promises4.default.readFile(this.filePath, "utf-8");
1446
- const freshData = JSON.parse(content);
1447
- const freshCreds = freshData[provider];
1448
- if (freshCreds && !opts?.forceRefresh && Date.now() < freshCreds.expiresAt - REFRESH_SKEW_MS) {
1449
- this.data[provider] = freshCreds;
1450
- return freshCreds;
1451
- }
1452
- } catch {
1543
+ const latest = await readAuthData(this.filePath);
1544
+ const latestCreds = latest[provider];
1545
+ if (!latestCreds) {
1546
+ this.data = latest;
1547
+ throw new NotLoggedInError(provider);
1548
+ }
1549
+ const credentialWasReplaced = latestCreds.accessToken !== creds.accessToken || latestCreds.refreshToken !== creds.refreshToken || latestCreds.expiresAt !== creds.expiresAt;
1550
+ if (credentialWasReplaced || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - REFRESH_SKEW_MS) {
1551
+ this.data = latest;
1552
+ return latestCreds;
1453
1553
  }
1454
1554
  const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
1455
1555
  let refreshed;
1456
1556
  try {
1457
- refreshed = await refreshFn(creds.refreshToken);
1557
+ refreshed = await refreshFn(latestCreds.refreshToken);
1458
1558
  } catch (err) {
1459
1559
  const msg = err instanceof Error ? err.message : String(err);
1460
1560
  const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh/i.test(msg) || /unauthorized/i.test(msg);
1461
1561
  if (isAuthFailure) {
1462
- delete this.data[provider];
1463
- await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1562
+ delete latest[provider];
1563
+ this.data = latest;
1564
+ await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
1464
1565
  throw new NotLoggedInError(provider);
1465
1566
  }
1466
1567
  throw err;
1467
1568
  }
1468
- if (!refreshed.accountId && creds.accountId) {
1469
- refreshed.accountId = creds.accountId;
1569
+ if (!refreshed.accountId && latestCreds.accountId) {
1570
+ refreshed.accountId = latestCreds.accountId;
1470
1571
  }
1471
- if (!refreshed.projectId && creds.projectId) {
1472
- refreshed.projectId = creds.projectId;
1572
+ if (!refreshed.projectId && latestCreds.projectId) {
1573
+ refreshed.projectId = latestCreds.projectId;
1473
1574
  }
1474
- if (!refreshed.baseUrl && creds.baseUrl) {
1475
- refreshed.baseUrl = creds.baseUrl;
1575
+ if (!refreshed.baseUrl && latestCreds.baseUrl) {
1576
+ refreshed.baseUrl = latestCreds.baseUrl;
1476
1577
  }
1477
- this.data[provider] = refreshed;
1478
- await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1578
+ latest[provider] = refreshed;
1579
+ this.data = latest;
1580
+ await atomicWriteFile(this.filePath, JSON.stringify(latest, null, 2));
1479
1581
  return refreshed;
1480
1582
  });
1481
1583
  this.refreshLocks.set(provider, refreshPromise);
@@ -1493,12 +1595,16 @@ var AuthStorage = class {
1493
1595
  const creds = await this.resolveCredentials(provider);
1494
1596
  return creds.accessToken;
1495
1597
  }
1496
- async save() {
1497
- await withFileLock(this.filePath, async () => {
1498
- await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1499
- });
1500
- }
1501
1598
  };
1599
+ async function readAuthData(filePath) {
1600
+ try {
1601
+ const content = await import_promises4.default.readFile(filePath, "utf-8");
1602
+ return JSON.parse(content);
1603
+ } catch (error) {
1604
+ if (error.code === "ENOENT") return {};
1605
+ throw error;
1606
+ }
1607
+ }
1502
1608
  async function atomicWriteFile(filePath, content) {
1503
1609
  const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${import_node_crypto6.default.randomUUID().slice(0, 8)}.tmp`;
1504
1610
  try {
@@ -1591,7 +1697,8 @@ var MODELS = [
1591
1697
  // ── OpenAI (Codex) ─────────────────────────────────────
1592
1698
  // GPT-5.6 family — three agentic coding tiers launched July 2026. The public
1593
1699
  // Responses API advertises a 1.05M context window; OpenAI's Codex product
1594
- // catalog advertises 372K on the ChatGPT OAuth route. All three take
1700
+ // catalog advertises 272K on the ChatGPT OAuth route (corrected from the
1701
+ // initially advertised 372K — openai/codex PR #33972, Jul 18 2026 hotfix). All three take
1595
1702
  // text+image input, freeform apply_patch, text+image web search, and parallel
1596
1703
  // tool calls.
1597
1704
  {
@@ -1603,7 +1710,7 @@ var MODELS = [
1603
1710
  name: "GPT-5.6 Sol",
1604
1711
  provider: "openai",
1605
1712
  contextWindow: 105e4,
1606
- codexContextWindow: 372e3,
1713
+ codexContextWindow: 272e3,
1607
1714
  maxOutputTokens: 128e3,
1608
1715
  supportsThinking: true,
1609
1716
  supportsImages: true,
@@ -1618,7 +1725,7 @@ var MODELS = [
1618
1725
  name: "GPT-5.6 Terra",
1619
1726
  provider: "openai",
1620
1727
  contextWindow: 105e4,
1621
- codexContextWindow: 372e3,
1728
+ codexContextWindow: 272e3,
1622
1729
  maxOutputTokens: 128e3,
1623
1730
  supportsThinking: true,
1624
1731
  supportsImages: true,
@@ -1633,7 +1740,7 @@ var MODELS = [
1633
1740
  name: "GPT-5.6 Luna",
1634
1741
  provider: "openai",
1635
1742
  contextWindow: 105e4,
1636
- codexContextWindow: 372e3,
1743
+ codexContextWindow: 272e3,
1637
1744
  maxOutputTokens: 128e3,
1638
1745
  supportsThinking: true,
1639
1746
  supportsImages: true,
@@ -1752,8 +1859,9 @@ var MODELS = [
1752
1859
  },
1753
1860
  // ── Moonshot (Kimi) ────────────────────────────────────
1754
1861
  // K3 is Kimi's 2.8T-parameter flagship for long-horizon coding, knowledge
1755
- // work, and deep reasoning. It always reasons at the `max` effort; the public
1756
- // API uses `reasoning_effort`, while Kimi Code OAuth keeps its managed wire shape.
1862
+ // work, and deep reasoning. Its effort ladder is server-declared as
1863
+ // low/high/max on both the public API (default max) and the Kimi For Coding
1864
+ // OAuth endpoint (default high); thinking can also be fully disabled.
1757
1865
  {
1758
1866
  id: "kimi-k3",
1759
1867
  name: "Kimi K3",
@@ -1987,6 +2095,11 @@ function getContextWindow(modelId, options) {
1987
2095
  function getMaxThinkingLevel(modelId) {
1988
2096
  return getModel(modelId)?.maxThinkingLevel ?? "high";
1989
2097
  }
2098
+ function getDefaultThinkingLevel(modelId, options) {
2099
+ const model = getModel(modelId);
2100
+ if (model?.id === "kimi-k3" && isKimiCodingEndpoint(options?.baseUrl)) return "high";
2101
+ return model?.maxThinkingLevel ?? "high";
2102
+ }
1990
2103
  function getSummaryModel(provider, currentModelId) {
1991
2104
  if (provider === "anthropic") {
1992
2105
  return MODELS.find((m) => m.id === "claude-sonnet-5");
@@ -2027,6 +2140,7 @@ var ANTHROPIC_ADAPTIVE_THINKING_LEVELS = [
2027
2140
  "high",
2028
2141
  "max"
2029
2142
  ];
2143
+ var MOONSHOT_K3_THINKING_LEVELS = ["low", "high", "max"];
2030
2144
  function isOpenAIGptModel(provider, model) {
2031
2145
  return provider === "openai" && model.startsWith("gpt-");
2032
2146
  }
@@ -2036,6 +2150,9 @@ function isSakanaModel(provider) {
2036
2150
  function isXaiModel(provider) {
2037
2151
  return provider === "xai";
2038
2152
  }
2153
+ function isMoonshotK3Model(provider, model) {
2154
+ return provider === "moonshot" && model === "kimi-k3";
2155
+ }
2039
2156
  function isAnthropicOpus48Or47Model(provider, model) {
2040
2157
  return provider === "anthropic" && /opus-4-8|opus-4-7/.test(model);
2041
2158
  }
@@ -2060,6 +2177,7 @@ function getSupportedThinkingLevels(provider, model) {
2060
2177
  if (maxIndex2 === -1) return XAI_THINKING_LEVELS;
2061
2178
  return XAI_THINKING_LEVELS.slice(0, maxIndex2 + 1);
2062
2179
  }
2180
+ if (isMoonshotK3Model(provider, model)) return MOONSHOT_K3_THINKING_LEVELS;
2063
2181
  if (!isOpenAIGptModel(provider, model)) return [maxLevel];
2064
2182
  const levels = model.startsWith("gpt-5.6-") ? OPENAI_GPT_56_THINKING_LEVELS : OPENAI_GPT_THINKING_LEVELS;
2065
2183
  const maxIndex = levels.indexOf(maxLevel);
@@ -2071,7 +2189,7 @@ function isThinkingLevelSupported(provider, model, level) {
2071
2189
  }
2072
2190
  function getNextThinkingLevel(provider, model, current) {
2073
2191
  const supportedLevels = getSupportedThinkingLevels(provider, model);
2074
- const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider) || isXaiModel(provider);
2192
+ const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider) || isXaiModel(provider) || isMoonshotK3Model(provider, model);
2075
2193
  if (!shouldCycleLevels) {
2076
2194
  return current ? void 0 : supportedLevels[0];
2077
2195
  }
@@ -2083,9 +2201,10 @@ function getNextThinkingLevel(provider, model, current) {
2083
2201
 
2084
2202
  // src/provider-usage.ts
2085
2203
  var SubscriptionUsageError = class extends Error {
2086
- constructor(message, status) {
2204
+ constructor(message, status, retryAfterMs2) {
2087
2205
  super(message);
2088
2206
  this.status = status;
2207
+ this.retryAfterMs = retryAfterMs2;
2089
2208
  this.name = "SubscriptionUsageError";
2090
2209
  }
2091
2210
  };
@@ -2135,12 +2254,20 @@ function normalizedCodexWindow(window, fallbackKind, now) {
2135
2254
  resetsAt: codexResetAt(window, now)
2136
2255
  };
2137
2256
  }
2138
- async function readUsageResponse(response) {
2257
+ function retryAfterMs(response, now) {
2258
+ const value = response.headers.get("retry-after")?.trim();
2259
+ if (!value) return void 0;
2260
+ if (/^\d+(?:\.\d+)?$/.test(value)) return Math.ceil(Number(value) * 1e3);
2261
+ const retryAt = Date.parse(value);
2262
+ return Number.isFinite(retryAt) ? Math.max(0, retryAt - now) : void 0;
2263
+ }
2264
+ async function readUsageResponse(response, now) {
2139
2265
  const text = await response.text();
2140
2266
  if (!response.ok) {
2141
2267
  throw new SubscriptionUsageError(
2142
2268
  `Subscription usage request failed with HTTP ${response.status}`,
2143
- response.status
2269
+ response.status,
2270
+ retryAfterMs(response, now)
2144
2271
  );
2145
2272
  }
2146
2273
  try {
@@ -2161,7 +2288,7 @@ async function fetchAnthropicUsage(credentials, fetchFn, signal, now) {
2161
2288
  "User-Agent": "ezcoder"
2162
2289
  }
2163
2290
  });
2164
- const data = await readUsageResponse(response);
2291
+ const data = await readUsageResponse(response, now());
2165
2292
  const windows = [];
2166
2293
  const currentPercent = clampPercent(data.five_hour?.utilization);
2167
2294
  if (currentPercent !== void 0) {
@@ -2197,7 +2324,7 @@ async function fetchCodexUsage(credentials, fetchFn, signal, now) {
2197
2324
  };
2198
2325
  if (credentials.accountId) headers["ChatGPT-Account-Id"] = credentials.accountId;
2199
2326
  const response = await fetchFn(CODEX_USAGE_URL, { method: "GET", signal, headers });
2200
- const data = await readUsageResponse(response);
2327
+ const data = await readUsageResponse(response, now());
2201
2328
  const windows = [
2202
2329
  normalizedCodexWindow(data.rate_limit?.primary_window, "current", now()),
2203
2330
  normalizedCodexWindow(data.rate_limit?.secondary_window, "weekly", now())
@@ -2213,12 +2340,97 @@ async function fetchCodexUsage(credentials, fetchFn, signal, now) {
2213
2340
  fetchedAt: now()
2214
2341
  };
2215
2342
  }
2343
+ function kimiNumber(value) {
2344
+ if (typeof value === "string" && value.trim()) {
2345
+ const parsed = Number(value);
2346
+ return Number.isFinite(parsed) ? parsed : void 0;
2347
+ }
2348
+ return finiteNumber(value);
2349
+ }
2350
+ function kimiUsagePercent(detail) {
2351
+ const used = kimiNumber(detail?.used);
2352
+ const limit = kimiNumber(detail?.limit);
2353
+ if (used === void 0 || limit === void 0 || limit <= 0) return void 0;
2354
+ return Math.min(100, Math.max(0, used / limit * 100));
2355
+ }
2356
+ function kimiWindowMinutes(window) {
2357
+ const duration = kimiNumber(window?.duration);
2358
+ if (duration === void 0 || duration <= 0) return void 0;
2359
+ switch (window?.timeUnit) {
2360
+ case "TIME_UNIT_SECOND":
2361
+ return duration / 60;
2362
+ case "TIME_UNIT_MINUTE":
2363
+ return duration;
2364
+ case "TIME_UNIT_HOUR":
2365
+ return duration * 60;
2366
+ case "TIME_UNIT_DAY":
2367
+ return duration * 24 * 60;
2368
+ default:
2369
+ return void 0;
2370
+ }
2371
+ }
2372
+ async function fetchKimiUsage(credentials, fetchFn, signal, now) {
2373
+ const response = await fetchFn(`${kimiCodeBaseUrl()}/usages`, {
2374
+ method: "GET",
2375
+ signal,
2376
+ headers: {
2377
+ Authorization: "Bearer " + credentials.accessToken,
2378
+ Accept: "application/json",
2379
+ // The managed coding endpoint gates on the kimi-code-cli client identity,
2380
+ // same as model requests.
2381
+ ...kimiCodingHeaders()
2382
+ }
2383
+ });
2384
+ const data = await readUsageResponse(response, now());
2385
+ const windows = [];
2386
+ const weeklyPercent = kimiUsagePercent(data.usage);
2387
+ if (weeklyPercent !== void 0) {
2388
+ windows.push({
2389
+ kind: "weekly",
2390
+ label: "Weekly",
2391
+ usedPercent: weeklyPercent,
2392
+ resetsAt: isoTimestamp(data.usage?.resetTime)
2393
+ });
2394
+ }
2395
+ for (const limit of data.limits ?? []) {
2396
+ const minutes = kimiWindowMinutes(limit?.window);
2397
+ const percent = kimiUsagePercent(limit?.detail);
2398
+ if (minutes === void 0 || percent === void 0) continue;
2399
+ if (minutes >= 6 * 24 * 60) {
2400
+ if (!windows.some((window) => window.kind === "weekly")) {
2401
+ windows.push({
2402
+ kind: "weekly",
2403
+ label: "Weekly",
2404
+ usedPercent: percent,
2405
+ resetsAt: isoTimestamp(limit?.detail?.resetTime)
2406
+ });
2407
+ }
2408
+ continue;
2409
+ }
2410
+ windows.push({
2411
+ kind: "current",
2412
+ label: `${Math.max(1, Math.round(minutes / 60))}-hour`,
2413
+ usedPercent: percent,
2414
+ resetsAt: isoTimestamp(limit?.detail?.resetTime)
2415
+ });
2416
+ }
2417
+ windows.sort((left, right) => {
2418
+ if (left.kind === right.kind) return 0;
2419
+ return left.kind === "current" ? -1 : 1;
2420
+ });
2421
+ return {
2422
+ provider: "moonshot",
2423
+ displayName: "Kimi",
2424
+ windows,
2425
+ fetchedAt: now()
2426
+ };
2427
+ }
2216
2428
  async function fetchSubscriptionUsage(provider, credentials, options = {}) {
2217
2429
  const fetchFn = options.fetchFn ?? fetch;
2218
2430
  const now = options.now ?? Date.now;
2219
2431
  const signal = AbortSignal.timeout(options.timeoutMs ?? 8e3);
2220
2432
  try {
2221
- return provider === "anthropic" ? await fetchAnthropicUsage(credentials, fetchFn, signal, now) : await fetchCodexUsage(credentials, fetchFn, signal, now);
2433
+ return provider === "anthropic" ? await fetchAnthropicUsage(credentials, fetchFn, signal, now) : provider === "openai" ? await fetchCodexUsage(credentials, fetchFn, signal, now) : await fetchKimiUsage(credentials, fetchFn, signal, now);
2222
2434
  } catch (error) {
2223
2435
  if (error instanceof SubscriptionUsageError) throw error;
2224
2436
  const message = error instanceof Error ? error.message : String(error);
@@ -2535,7 +2747,7 @@ async function transcribeVoice(fileUrl) {
2535
2747
 
2536
2748
  // src/auto-update.ts
2537
2749
  var import_node_child_process2 = require("child_process");
2538
- var import_node_fs3 = __toESM(require("fs"), 1);
2750
+ var import_node_fs4 = __toESM(require("fs"), 1);
2539
2751
  var import_node_path5 = __toESM(require("path"), 1);
2540
2752
  var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
2541
2753
  var FETCH_TIMEOUT_MS2 = 1e4;
@@ -2569,7 +2781,7 @@ function createAutoUpdater(config) {
2569
2781
  }
2570
2782
  function readState() {
2571
2783
  try {
2572
- const raw = import_node_fs3.default.readFileSync(stateFilePath(), "utf-8");
2784
+ const raw = import_node_fs4.default.readFileSync(stateFilePath(), "utf-8");
2573
2785
  return JSON.parse(raw);
2574
2786
  } catch {
2575
2787
  return null;
@@ -2578,8 +2790,8 @@ function createAutoUpdater(config) {
2578
2790
  function writeState(state) {
2579
2791
  try {
2580
2792
  const filePath = stateFilePath();
2581
- import_node_fs3.default.mkdirSync(import_node_path5.default.dirname(filePath), { recursive: true, mode: 448 });
2582
- import_node_fs3.default.writeFileSync(filePath, JSON.stringify(state));
2793
+ import_node_fs4.default.mkdirSync(import_node_path5.default.dirname(filePath), { recursive: true, mode: 448 });
2794
+ import_node_fs4.default.writeFileSync(filePath, JSON.stringify(state));
2583
2795
  } catch {
2584
2796
  }
2585
2797
  }
@@ -2724,6 +2936,7 @@ function createAutoUpdater(config) {
2724
2936
  getClaudeCodeVersion,
2725
2937
  getContextWindow,
2726
2938
  getDefaultModel,
2939
+ getDefaultThinkingLevel,
2727
2940
  getFastModel,
2728
2941
  getMaxThinkingLevel,
2729
2942
  getModel,
@@ -2746,6 +2959,7 @@ function createAutoUpdater(config) {
2746
2959
  loginKimi,
2747
2960
  loginOpenAI,
2748
2961
  openLog,
2962
+ readStoredBaseUrlSync,
2749
2963
  refreshAnthropicToken,
2750
2964
  refreshGeminiToken,
2751
2965
  refreshKimiToken,