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