@prestyj/core 5.1.0 → 5.2.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
@@ -36,12 +36,15 @@ __export(index_exports, {
36
36
  MOONSHOT_OAUTH_KEY: () => MOONSHOT_OAUTH_KEY,
37
37
  NotLoggedInError: () => NotLoggedInError,
38
38
  TelegramBot: () => TelegramBot,
39
+ XIAOMI_CREDITS_KEY: () => XIAOMI_CREDITS_KEY,
39
40
  closeLogger: () => closeLogger,
40
41
  createAutoUpdater: () => createAutoUpdater,
41
42
  decodeOggOpus: () => decodeOggOpus,
42
43
  downmixToMono: () => downmixToMono,
43
44
  generatePKCE: () => generatePKCE,
44
45
  getAppPaths: () => getAppPaths,
46
+ getAuthStorageKey: () => getAuthStorageKey,
47
+ getAuthStorageKeys: () => getAuthStorageKeys,
45
48
  getClaudeCliUserAgent: () => getClaudeCliUserAgent,
46
49
  getClaudeCodeVersion: () => getClaudeCodeVersion,
47
50
  getContextWindow: () => getContextWindow,
@@ -79,457 +82,9 @@ __export(index_exports, {
79
82
  });
80
83
  module.exports = __toCommonJS(index_exports);
81
84
 
82
- // src/model-registry.ts
83
- var MODELS = [
84
- // ── Anthropic ──────────────────────────────────────────
85
- // NOTE: Claude Fable 5 (`claude-fable-5`) and Claude Mythos 5
86
- // (`claude-mythos-5`) are temporarily unavailable, so they're commented out
87
- // here to keep them out of the /model selector and avoid user confusion.
88
- // Re-enable once they're generally available again.
89
- // {
90
- // id: "claude-fable-5",
91
- // name: "Claude Fable 5",
92
- // provider: "anthropic",
93
- // contextWindow: 1_000_000,
94
- // maxOutputTokens: 128_000,
95
- // supportsThinking: true,
96
- // supportsImages: true,
97
- // supportsVideo: false,
98
- // costTier: "high",
99
- // maxThinkingLevel: "max",
100
- // },
101
- // {
102
- // // Mythos-class model offered through Project Glasswing (limited
103
- // // availability, invitation-only). Same underlying model as Fable 5 with
104
- // // some safeguards lifted; kept here so approved accounts can select it.
105
- // id: "claude-mythos-5",
106
- // name: "Claude Mythos 5",
107
- // provider: "anthropic",
108
- // contextWindow: 1_000_000,
109
- // maxOutputTokens: 128_000,
110
- // supportsThinking: true,
111
- // supportsImages: true,
112
- // supportsVideo: false,
113
- // costTier: "high",
114
- // maxThinkingLevel: "max",
115
- // },
116
- {
117
- id: "claude-opus-4-8",
118
- name: "Claude Opus 4.8",
119
- provider: "anthropic",
120
- contextWindow: 1e6,
121
- maxOutputTokens: 128e3,
122
- supportsThinking: true,
123
- supportsImages: true,
124
- supportsVideo: false,
125
- costTier: "high",
126
- maxThinkingLevel: "max"
127
- },
128
- {
129
- id: "claude-sonnet-4-6",
130
- name: "Claude Sonnet 4.6",
131
- provider: "anthropic",
132
- contextWindow: 1e6,
133
- maxOutputTokens: 64e3,
134
- supportsThinking: true,
135
- supportsImages: true,
136
- supportsVideo: false,
137
- costTier: "medium",
138
- maxThinkingLevel: "max"
139
- },
140
- {
141
- id: "claude-haiku-4-5-20251001",
142
- name: "Claude Haiku 4.5",
143
- provider: "anthropic",
144
- contextWindow: 2e5,
145
- maxOutputTokens: 64e3,
146
- supportsThinking: true,
147
- supportsImages: true,
148
- supportsVideo: false,
149
- costTier: "low",
150
- maxThinkingLevel: "high"
151
- },
152
- // ── OpenAI (Codex) ─────────────────────────────────────
153
- {
154
- id: "gpt-5.5",
155
- name: "GPT-5.5",
156
- provider: "openai",
157
- contextWindow: 105e4,
158
- codexContextWindow: 272e3,
159
- maxOutputTokens: 128e3,
160
- supportsThinking: true,
161
- supportsImages: true,
162
- supportsVideo: false,
163
- costTier: "high",
164
- maxThinkingLevel: "xhigh"
165
- },
166
- {
167
- id: "gpt-5.4",
168
- name: "GPT-5.4",
169
- provider: "openai",
170
- contextWindow: 105e4,
171
- codexContextWindow: 272e3,
172
- maxOutputTokens: 128e3,
173
- supportsThinking: true,
174
- supportsImages: true,
175
- supportsVideo: false,
176
- costTier: "high",
177
- maxThinkingLevel: "xhigh"
178
- },
179
- {
180
- id: "gpt-5.4-mini",
181
- name: "GPT-5.4 Mini",
182
- provider: "openai",
183
- contextWindow: 4e5,
184
- maxOutputTokens: 128e3,
185
- supportsThinking: true,
186
- supportsImages: true,
187
- supportsVideo: false,
188
- costTier: "low",
189
- maxThinkingLevel: "xhigh"
190
- },
191
- {
192
- id: "gpt-5.3-codex",
193
- name: "GPT-5.3 Codex",
194
- provider: "openai",
195
- contextWindow: 4e5,
196
- maxOutputTokens: 128e3,
197
- supportsThinking: true,
198
- supportsImages: true,
199
- supportsVideo: false,
200
- costTier: "high",
201
- maxThinkingLevel: "xhigh"
202
- },
203
- // ── Sakana (Fugu) ──────────────────────────────────────
204
- // Sakana Fugu is a multi-agent system surfaced as a standard LLM via the
205
- // OpenAI-compatible Sakana API (https://api.sakana.ai/v1). Both models take
206
- // text + image input and only accept "high"/"xhigh" reasoning effort, so the
207
- // top tier is `xhigh`. `fugu` routes across all providers; `fugu-ultra` is
208
- // the heavier tier (may need larger client timeouts on complex tasks).
209
- {
210
- id: "fugu",
211
- name: "Fugu",
212
- provider: "sakana",
213
- contextWindow: 1e6,
214
- maxOutputTokens: 128e3,
215
- supportsThinking: true,
216
- supportsImages: true,
217
- supportsVideo: false,
218
- costTier: "medium",
219
- maxThinkingLevel: "xhigh"
220
- },
221
- {
222
- id: "fugu-ultra",
223
- name: "Fugu Ultra",
224
- provider: "sakana",
225
- contextWindow: 1e6,
226
- maxOutputTokens: 128e3,
227
- supportsThinking: true,
228
- supportsImages: true,
229
- supportsVideo: false,
230
- costTier: "high",
231
- maxThinkingLevel: "xhigh"
232
- },
233
- // ── Gemini ─────────────────────────────────────────────
234
- {
235
- id: "gemini-3.1-flash-lite-preview",
236
- name: "Gemini 3.1 Flash Lite Preview",
237
- provider: "gemini",
238
- contextWindow: 1048576,
239
- maxOutputTokens: 65536,
240
- supportsThinking: true,
241
- supportsImages: true,
242
- supportsVideo: true,
243
- maxVideoBytes: 20 * 1024 * 1024,
244
- costTier: "low",
245
- maxThinkingLevel: "high"
246
- },
247
- {
248
- id: "gemini-3.5-flash",
249
- name: "Gemini 3.5 Flash",
250
- provider: "gemini",
251
- contextWindow: 1048576,
252
- maxOutputTokens: 65536,
253
- supportsThinking: true,
254
- supportsImages: true,
255
- supportsVideo: true,
256
- maxVideoBytes: 20 * 1024 * 1024,
257
- costTier: "low",
258
- maxThinkingLevel: "high"
259
- },
260
- // ── Moonshot (Kimi) ────────────────────────────────────
261
- {
262
- id: "kimi-k2.7-code",
263
- name: "Kimi K2.7",
264
- provider: "moonshot",
265
- contextWindow: 262144,
266
- maxOutputTokens: 262144,
267
- supportsThinking: true,
268
- supportsImages: true,
269
- supportsVideo: true,
270
- maxVideoBytes: 100 * 1024 * 1024,
271
- costTier: "medium",
272
- maxThinkingLevel: "high"
273
- },
274
- // ── Z.AI (GLM) ─────────────────────────────────────────
275
- // GLM-5.2: coding-first flagship with a usable 1M-token context window
276
- // (5x jump over GLM-5.1's ~200K) and 131K max output. Released 2026-06-13.
277
- {
278
- id: "glm-5.2",
279
- name: "GLM-5.2",
280
- provider: "glm",
281
- contextWindow: 1e6,
282
- maxOutputTokens: 131072,
283
- supportsThinking: true,
284
- supportsImages: false,
285
- supportsVideo: false,
286
- costTier: "medium",
287
- maxThinkingLevel: "high"
288
- },
289
- {
290
- id: "glm-5.1",
291
- name: "GLM-5.1",
292
- provider: "glm",
293
- contextWindow: 204800,
294
- maxOutputTokens: 131072,
295
- supportsThinking: true,
296
- supportsImages: false,
297
- supportsVideo: false,
298
- costTier: "medium",
299
- maxThinkingLevel: "high"
300
- },
301
- {
302
- id: "glm-4.7",
303
- name: "GLM-4.7",
304
- provider: "glm",
305
- contextWindow: 2e5,
306
- maxOutputTokens: 131072,
307
- supportsThinking: true,
308
- supportsImages: false,
309
- supportsVideo: false,
310
- costTier: "low",
311
- maxThinkingLevel: "high"
312
- },
313
- {
314
- id: "glm-4.7-flash",
315
- name: "GLM-4.7 Flash",
316
- provider: "glm",
317
- contextWindow: 2e5,
318
- maxOutputTokens: 131072,
319
- supportsThinking: true,
320
- supportsImages: false,
321
- supportsVideo: false,
322
- costTier: "low",
323
- maxThinkingLevel: "high"
324
- },
325
- // ── MiniMax ────────────────────────────────────────────
326
- {
327
- id: "MiniMax-M3",
328
- name: "MiniMax M3",
329
- provider: "minimax",
330
- contextWindow: 1e6,
331
- maxOutputTokens: 131072,
332
- supportsThinking: true,
333
- supportsImages: true,
334
- supportsVideo: true,
335
- maxVideoBytes: 50 * 1024 * 1024,
336
- costTier: "medium",
337
- maxThinkingLevel: "high"
338
- },
339
- // ── Xiaomi (MiMo) ──────────────────────────────────────
340
- // Pro series: text-only coding/agentic flagship. The legacy mimo-v2-pro
341
- // auto-routes to v2.5 on 2026-06-01 and is fully deprecated by 2026-06-30.
342
- {
343
- id: "mimo-v2.5-pro",
344
- name: "MiMo-V2.5-Pro",
345
- provider: "xiaomi",
346
- contextWindow: 1e6,
347
- maxOutputTokens: 131072,
348
- supportsThinking: true,
349
- supportsImages: false,
350
- supportsVideo: false,
351
- costTier: "medium",
352
- maxThinkingLevel: "high"
353
- },
354
- // UltraSpeed: latency-optimized Pro variant (beta). Same text-only coding
355
- // profile as mimo-v2.5-pro, tuned for faster time-to-first-token.
356
- {
357
- id: "mimo-v2.5-pro-ultraspeed",
358
- name: "MiMo-V2.5-Pro UltraSpeed",
359
- provider: "xiaomi",
360
- contextWindow: 1e6,
361
- maxOutputTokens: 131072,
362
- supportsThinking: true,
363
- supportsImages: false,
364
- supportsVideo: false,
365
- costTier: "medium",
366
- maxThinkingLevel: "high"
367
- },
368
- // Omni series: native full-modal understanding (image + audio + video).
369
- // Video/image ride the OpenAI-compatible transport as base64 data URLs
370
- // (`video_url`/`image_url`), which the shared transform already emits.
371
- {
372
- id: "mimo-v2.5",
373
- name: "MiMo-V2.5",
374
- provider: "xiaomi",
375
- contextWindow: 1e6,
376
- maxOutputTokens: 131072,
377
- supportsThinking: true,
378
- supportsImages: true,
379
- supportsVideo: true,
380
- maxVideoBytes: 36 * 1024 * 1024,
381
- costTier: "medium",
382
- maxThinkingLevel: "high"
383
- },
384
- // ── DeepSeek ───────────────────────────────────────────
385
- {
386
- id: "deepseek-v4-pro",
387
- name: "DeepSeek V4 Pro",
388
- provider: "deepseek",
389
- contextWindow: 1048576,
390
- maxOutputTokens: 384e3,
391
- supportsThinking: true,
392
- supportsImages: false,
393
- supportsVideo: false,
394
- costTier: "high",
395
- // DeepSeek V4 maps `xhigh` → its internal `max` tier.
396
- maxThinkingLevel: "xhigh"
397
- },
398
- {
399
- id: "deepseek-v4-flash",
400
- name: "DeepSeek V4 Flash",
401
- provider: "deepseek",
402
- contextWindow: 1048576,
403
- maxOutputTokens: 384e3,
404
- supportsThinking: true,
405
- supportsImages: false,
406
- supportsVideo: false,
407
- costTier: "low",
408
- maxThinkingLevel: "xhigh"
409
- },
410
- // ── OpenRouter ─────────────────────────────────────────
411
- {
412
- id: "qwen/qwen3.6-plus",
413
- name: "Qwen3.6-Plus",
414
- provider: "openrouter",
415
- contextWindow: 1e6,
416
- maxOutputTokens: 65536,
417
- supportsThinking: true,
418
- supportsImages: false,
419
- supportsVideo: false,
420
- costTier: "medium",
421
- maxThinkingLevel: "high"
422
- }
423
- ];
424
- function getModel(id) {
425
- return MODELS.find((m) => m.id === id);
426
- }
427
- function getModelsForProvider(provider) {
428
- return MODELS.filter((m) => m.provider === provider);
429
- }
430
- var DEFAULT_MAX_VIDEO_BYTES = 20 * 1024 * 1024;
431
- function getVideoByteLimit(modelId) {
432
- const model = getModel(modelId);
433
- if (!model?.supportsVideo) return void 0;
434
- return model.maxVideoBytes ?? DEFAULT_MAX_VIDEO_BYTES;
435
- }
436
- function getDefaultModel(provider) {
437
- if (provider === "xiaomi") return MODELS.find((m) => m.id === "mimo-v2.5-pro");
438
- if (provider === "openai") return MODELS.find((m) => m.id === "gpt-5.5");
439
- if (provider === "gemini") return MODELS.find((m) => m.id === "gemini-3.1-flash-lite-preview");
440
- if (provider === "glm") return MODELS.find((m) => m.id === "glm-5.2");
441
- if (provider === "moonshot") return MODELS.find((m) => m.id === "kimi-k2.7-code");
442
- if (provider === "minimax") return MODELS.find((m) => m.id === "MiniMax-M3");
443
- if (provider === "deepseek") return MODELS.find((m) => m.id === "deepseek-v4-pro");
444
- if (provider === "openrouter") return MODELS.find((m) => m.id === "qwen/qwen3.6-plus");
445
- if (provider === "sakana") return MODELS.find((m) => m.id === "fugu");
446
- return MODELS.find((m) => m.id === "claude-sonnet-4-6");
447
- }
448
- function usesOpenAICodexTransport(options) {
449
- return options?.provider === "openai" && Boolean(options.accountId);
450
- }
451
- function getContextWindow(modelId, options) {
452
- const model = getModel(modelId);
453
- if (!model) return 2e5;
454
- if (usesOpenAICodexTransport(options) && model.codexContextWindow) {
455
- return model.codexContextWindow;
456
- }
457
- return model.contextWindow;
458
- }
459
- function getMaxThinkingLevel(modelId) {
460
- return getModel(modelId)?.maxThinkingLevel ?? "high";
461
- }
462
- function getSummaryModel(provider, currentModelId) {
463
- if (provider === "anthropic") {
464
- return MODELS.find((m) => m.id === "claude-sonnet-4-6");
465
- }
466
- if (provider === "openai" || provider === "glm" || provider === "deepseek") {
467
- const low = getModelsForProvider(provider).find((m) => m.costTier === "low");
468
- if (low) return low;
469
- }
470
- return getModel(currentModelId) ?? getDefaultModel(provider);
471
- }
472
-
473
- // src/thinking-level.ts
474
- var OPENAI_GPT_THINKING_LEVELS = ["medium", "high", "xhigh"];
475
- var SAKANA_THINKING_LEVELS = ["high", "xhigh"];
476
- var ANTHROPIC_OPUS_48_47_THINKING_LEVELS = [
477
- "low",
478
- "medium",
479
- "high",
480
- "xhigh",
481
- "max"
482
- ];
483
- var ANTHROPIC_ADAPTIVE_THINKING_LEVELS = [
484
- "low",
485
- "medium",
486
- "high",
487
- "max"
488
- ];
489
- function isOpenAIGptModel(provider, model) {
490
- return provider === "openai" && model.startsWith("gpt-");
491
- }
492
- function isSakanaModel(provider) {
493
- return provider === "sakana";
494
- }
495
- function isAnthropicOpus48Or47Model(provider, model) {
496
- return provider === "anthropic" && /opus-4-8|opus-4-7/.test(model);
497
- }
498
- function isAnthropicAdaptiveModel(provider, model) {
499
- return provider === "anthropic" && /opus-4-8|opus-4-7|opus-4-6|sonnet-4-6|fable-5|mythos-5/.test(model);
500
- }
501
- function getSupportedThinkingLevels(provider, model) {
502
- const maxLevel = getMaxThinkingLevel(model);
503
- if (isAnthropicAdaptiveModel(provider, model)) {
504
- const levels = isAnthropicOpus48Or47Model(provider, model) ? ANTHROPIC_OPUS_48_47_THINKING_LEVELS : ANTHROPIC_ADAPTIVE_THINKING_LEVELS;
505
- const maxIndex2 = levels.indexOf(maxLevel);
506
- if (maxIndex2 === -1) return ["low", "medium", "high"];
507
- return levels.slice(0, maxIndex2 + 1);
508
- }
509
- if (isSakanaModel(provider)) {
510
- const maxIndex2 = SAKANA_THINKING_LEVELS.indexOf(maxLevel);
511
- if (maxIndex2 === -1) return SAKANA_THINKING_LEVELS;
512
- return SAKANA_THINKING_LEVELS.slice(0, maxIndex2 + 1);
513
- }
514
- if (!isOpenAIGptModel(provider, model)) return [maxLevel];
515
- const maxIndex = OPENAI_GPT_THINKING_LEVELS.indexOf(maxLevel);
516
- if (maxIndex === -1) return ["medium"];
517
- return OPENAI_GPT_THINKING_LEVELS.slice(0, maxIndex + 1);
518
- }
519
- function isThinkingLevelSupported(provider, model, level) {
520
- return getSupportedThinkingLevels(provider, model).includes(level);
521
- }
522
- function getNextThinkingLevel(provider, model, current) {
523
- const supportedLevels = getSupportedThinkingLevels(provider, model);
524
- const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider);
525
- if (!shouldCycleLevels) {
526
- return current ? void 0 : supportedLevels[0];
527
- }
528
- if (!current) return supportedLevels[0];
529
- const index = supportedLevels.indexOf(current);
530
- if (index === -1) return supportedLevels[0];
531
- return supportedLevels[index + 1];
532
- }
85
+ // src/auth-storage.ts
86
+ var import_promises4 = __toESM(require("fs/promises"), 1);
87
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
533
88
 
534
89
  // src/paths.ts
535
90
  var import_node_path = __toESM(require("path"), 1);
@@ -552,6 +107,31 @@ function getAppPaths() {
552
107
  };
553
108
  }
554
109
 
110
+ // src/oauth/anthropic.ts
111
+ var import_node_crypto2 = __toESM(require("crypto"), 1);
112
+
113
+ // src/oauth/pkce.ts
114
+ function base64urlEncode(bytes) {
115
+ let binary = "";
116
+ for (const byte of bytes) {
117
+ binary += String.fromCharCode(byte);
118
+ }
119
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
120
+ }
121
+ async function generatePKCE() {
122
+ const verifierBytes = new Uint8Array(32);
123
+ crypto.getRandomValues(verifierBytes);
124
+ const verifier = base64urlEncode(verifierBytes);
125
+ const data = new TextEncoder().encode(verifier);
126
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
127
+ const challenge = base64urlEncode(new Uint8Array(hashBuffer));
128
+ return { verifier, challenge };
129
+ }
130
+
131
+ // src/claude-code-version.ts
132
+ var import_promises = __toESM(require("fs/promises"), 1);
133
+ var import_node_path3 = __toESM(require("path"), 1);
134
+
555
135
  // src/logger.ts
556
136
  var import_node_fs = __toESM(require("fs"), 1);
557
137
  var import_node_path2 = __toESM(require("path"), 1);
@@ -609,91 +189,27 @@ function log(level, category, message, data) {
609
189
  if (pairs) line += ` ${pairs}`;
610
190
  }
611
191
  line += "\n";
612
- try {
613
- import_node_fs.default.writeSync(fd, line);
614
- } catch {
615
- }
616
- }
617
- function registerLogCleanup(fn) {
618
- cleanups.push(fn);
619
- }
620
- function closeLogger(opts) {
621
- if (fd === null) return;
622
- if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
623
- try {
624
- import_node_fs.default.closeSync(fd);
625
- } catch {
626
- }
627
- fd = null;
628
- for (const unsub of cleanups) unsub();
629
- cleanups = [];
630
- }
631
-
632
- // src/file-lock.ts
633
- var import_promises = __toESM(require("fs/promises"), 1);
634
- var import_promises2 = require("timers/promises");
635
- var STALE_TIMEOUT_MS = 1e4;
636
- var RETRY_INTERVAL_MS = 50;
637
- var MAX_WAIT_MS = 5e3;
638
- async function withFileLock(filePath, fn) {
639
- const lockPath = filePath + ".lock";
640
- await acquireLock(lockPath);
641
- try {
642
- return await fn();
643
- } finally {
644
- await releaseLock(lockPath);
645
- }
646
- }
647
- async function acquireLock(lockPath) {
648
- const startTime = Date.now();
649
- while (true) {
650
- try {
651
- const info = { pid: process.pid, timestamp: Date.now() };
652
- await import_promises.default.writeFile(lockPath, JSON.stringify(info), { flag: "wx" });
653
- return;
654
- } catch (err) {
655
- if (err.code !== "EEXIST") throw err;
656
- try {
657
- const content = await import_promises.default.readFile(lockPath, "utf-8");
658
- const info = JSON.parse(content);
659
- const isProcessAlive = isAlive(info.pid);
660
- const isStale = Date.now() - info.timestamp > STALE_TIMEOUT_MS;
661
- if (!isProcessAlive || isStale) {
662
- await import_promises.default.unlink(lockPath).catch(() => {
663
- });
664
- continue;
665
- }
666
- } catch {
667
- await import_promises.default.unlink(lockPath).catch(() => {
668
- });
669
- continue;
670
- }
671
- if (Date.now() - startTime > MAX_WAIT_MS) {
672
- await import_promises.default.unlink(lockPath).catch(() => {
673
- });
674
- continue;
675
- }
676
- await (0, import_promises2.setTimeout)(RETRY_INTERVAL_MS);
677
- }
192
+ try {
193
+ import_node_fs.default.writeSync(fd, line);
194
+ } catch {
678
195
  }
679
196
  }
680
- async function releaseLock(lockPath) {
681
- await import_promises.default.unlink(lockPath).catch(() => {
682
- });
197
+ function registerLogCleanup(fn) {
198
+ cleanups.push(fn);
683
199
  }
684
- function isAlive(pid) {
200
+ function closeLogger(opts) {
201
+ if (fd === null) return;
202
+ if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
685
203
  try {
686
- process.kill(pid, 0);
687
- return true;
688
- } catch (err) {
689
- if (err.code === "EPERM") return true;
690
- return false;
204
+ import_node_fs.default.closeSync(fd);
205
+ } catch {
691
206
  }
207
+ fd = null;
208
+ for (const unsub of cleanups) unsub();
209
+ cleanups = [];
692
210
  }
693
211
 
694
212
  // src/claude-code-version.ts
695
- var import_promises3 = __toESM(require("fs/promises"), 1);
696
- var import_node_path3 = __toESM(require("path"), 1);
697
213
  var NPM_LATEST_URL = "https://registry.npmjs.org/@anthropic-ai/claude-code/latest";
698
214
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
699
215
  var FETCH_TIMEOUT_MS = 3e3;
@@ -705,7 +221,7 @@ function cachePath() {
705
221
  }
706
222
  async function readDiskCache() {
707
223
  try {
708
- const raw = await import_promises3.default.readFile(cachePath(), "utf-8");
224
+ const raw = await import_promises.default.readFile(cachePath(), "utf-8");
709
225
  const parsed = JSON.parse(raw);
710
226
  if (typeof parsed.version === "string" && typeof parsed.fetchedAt === "number") {
711
227
  return parsed;
@@ -717,8 +233,8 @@ async function readDiskCache() {
717
233
  }
718
234
  async function writeDiskCache(data) {
719
235
  try {
720
- await import_promises3.default.mkdir(getAppPaths().agentDir, { recursive: true, mode: 448 });
721
- await import_promises3.default.writeFile(cachePath(), JSON.stringify(data), { mode: 384 });
236
+ await import_promises.default.mkdir(getAppPaths().agentDir, { recursive: true, mode: 448 });
237
+ await import_promises.default.writeFile(cachePath(), JSON.stringify(data), { mode: 384 });
722
238
  } catch (err) {
723
239
  log(
724
240
  "WARN",
@@ -782,31 +298,6 @@ async function getClaudeCliUserAgent() {
782
298
  return `claude-cli/${version} (external, cli)`;
783
299
  }
784
300
 
785
- // src/auth-storage.ts
786
- var import_promises4 = __toESM(require("fs/promises"), 1);
787
- var import_node_crypto6 = __toESM(require("crypto"), 1);
788
-
789
- // src/oauth/anthropic.ts
790
- var import_node_crypto2 = __toESM(require("crypto"), 1);
791
-
792
- // src/oauth/pkce.ts
793
- function base64urlEncode(bytes) {
794
- let binary = "";
795
- for (const byte of bytes) {
796
- binary += String.fromCharCode(byte);
797
- }
798
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
799
- }
800
- async function generatePKCE() {
801
- const verifierBytes = new Uint8Array(32);
802
- crypto.getRandomValues(verifierBytes);
803
- const verifier = base64urlEncode(verifierBytes);
804
- const data = new TextEncoder().encode(verifier);
805
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
806
- const challenge = base64urlEncode(new Uint8Array(hashBuffer));
807
- return { verifier, challenge };
808
- }
809
-
810
301
  // src/oauth/anthropic.ts
811
302
  var CLIENT_ID = atob("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
812
303
  var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
@@ -1657,8 +1148,71 @@ async function refreshKimiToken(refreshToken) {
1657
1148
  throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
1658
1149
  }
1659
1150
 
1151
+ // src/file-lock.ts
1152
+ var import_promises2 = __toESM(require("fs/promises"), 1);
1153
+ var import_promises3 = require("timers/promises");
1154
+ var STALE_TIMEOUT_MS = 1e4;
1155
+ var RETRY_INTERVAL_MS = 50;
1156
+ var MAX_WAIT_MS = 5e3;
1157
+ async function withFileLock(filePath, fn) {
1158
+ const lockPath = filePath + ".lock";
1159
+ await acquireLock(lockPath);
1160
+ try {
1161
+ return await fn();
1162
+ } finally {
1163
+ await releaseLock(lockPath);
1164
+ }
1165
+ }
1166
+ async function acquireLock(lockPath) {
1167
+ const startTime = Date.now();
1168
+ while (true) {
1169
+ try {
1170
+ const info = { pid: process.pid, timestamp: Date.now() };
1171
+ await import_promises2.default.writeFile(lockPath, JSON.stringify(info), { flag: "wx" });
1172
+ return;
1173
+ } catch (err) {
1174
+ if (err.code !== "EEXIST") throw err;
1175
+ try {
1176
+ const content = await import_promises2.default.readFile(lockPath, "utf-8");
1177
+ const info = JSON.parse(content);
1178
+ const isProcessAlive = isAlive(info.pid);
1179
+ const isStale = Date.now() - info.timestamp > STALE_TIMEOUT_MS;
1180
+ if (!isProcessAlive || isStale) {
1181
+ await import_promises2.default.unlink(lockPath).catch(() => {
1182
+ });
1183
+ continue;
1184
+ }
1185
+ } catch {
1186
+ await import_promises2.default.unlink(lockPath).catch(() => {
1187
+ });
1188
+ continue;
1189
+ }
1190
+ if (Date.now() - startTime > MAX_WAIT_MS) {
1191
+ await import_promises2.default.unlink(lockPath).catch(() => {
1192
+ });
1193
+ continue;
1194
+ }
1195
+ await (0, import_promises3.setTimeout)(RETRY_INTERVAL_MS);
1196
+ }
1197
+ }
1198
+ }
1199
+ async function releaseLock(lockPath) {
1200
+ await import_promises2.default.unlink(lockPath).catch(() => {
1201
+ });
1202
+ }
1203
+ function isAlive(pid) {
1204
+ try {
1205
+ process.kill(pid, 0);
1206
+ return true;
1207
+ } catch (err) {
1208
+ if (err.code === "EPERM") return true;
1209
+ return false;
1210
+ }
1211
+ }
1212
+
1660
1213
  // src/auth-storage.ts
1661
1214
  var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
1215
+ var XIAOMI_CREDITS_KEY = "xiaomi-credits";
1662
1216
  var REFRESH_SKEW_MS = 6e4;
1663
1217
  var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
1664
1218
  "glm",
@@ -1692,6 +1246,17 @@ var AuthStorage = class {
1692
1246
  await this.ensureLoaded();
1693
1247
  return Boolean(this.data[provider]);
1694
1248
  }
1249
+ /**
1250
+ * First key in `keys` (in order) that has stored credentials, or `undefined`
1251
+ * if none do. Mirrors the first-match logic `resolveCredentials({ storageKeys })`
1252
+ * uses internally — callers that need to know WHICH credential will actually
1253
+ * be used (e.g. to clear the right one after a 401) call this directly
1254
+ * instead of re-deriving the same order.
1255
+ */
1256
+ async pickStorageKey(keys) {
1257
+ await this.ensureLoaded();
1258
+ return keys.find((key) => Boolean(this.data[key]));
1259
+ }
1695
1260
  /**
1696
1261
  * True if the user has any usable auth for the logical provider. For
1697
1262
  * `moonshot` this is satisfied by either the Kimi OAuth credential or the
@@ -1702,6 +1267,9 @@ var AuthStorage = class {
1702
1267
  if (provider === "moonshot") {
1703
1268
  return Boolean(this.data[MOONSHOT_OAUTH_KEY] || this.data["moonshot"]);
1704
1269
  }
1270
+ if (provider === "xiaomi") {
1271
+ return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
1272
+ }
1705
1273
  return Boolean(this.data[provider]);
1706
1274
  }
1707
1275
  /**
@@ -1744,6 +1312,17 @@ var AuthStorage = class {
1744
1312
  async ensureLoaded() {
1745
1313
  if (!this.loaded) await this.load();
1746
1314
  }
1315
+ /**
1316
+ * Force a re-read from disk, discarding the in-memory cache. Needed when
1317
+ * another process mutates the auth file out-of-band — e.g. the desktop app
1318
+ * writes API keys natively (Rust → ~/.ezcoder/auth.json) without going through
1319
+ * this instance, so a long-lived daemon's cache would otherwise stay stale and
1320
+ * never see a newly added provider key.
1321
+ */
1322
+ async reload() {
1323
+ this.loaded = false;
1324
+ await this.load();
1325
+ }
1747
1326
  async getCredentials(provider) {
1748
1327
  await this.ensureLoaded();
1749
1328
  return this.data[provider];
@@ -1758,123 +1337,593 @@ var AuthStorage = class {
1758
1337
  delete this.data[provider];
1759
1338
  await this.save();
1760
1339
  }
1761
- async clearAll() {
1762
- this.data = {};
1763
- await this.save();
1340
+ async clearAll() {
1341
+ this.data = {};
1342
+ await this.save();
1343
+ }
1344
+ /**
1345
+ * Returns valid credentials, auto-refreshing if expired.
1346
+ * If `forceRefresh` is true, refreshes even if the token hasn't expired
1347
+ * (useful when the provider rejects a token with 401 before its stored expiry).
1348
+ * Throws if not logged in.
1349
+ */
1350
+ async resolveCredentials(provider, opts) {
1351
+ await this.ensureLoaded();
1352
+ if (opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider)) {
1353
+ for (const key of opts.storageKeys) {
1354
+ const creds2 = this.data[key];
1355
+ if (creds2) return creds2;
1356
+ }
1357
+ throw new NotLoggedInError(provider);
1358
+ }
1359
+ if (provider === "moonshot" && this.data[MOONSHOT_OAUTH_KEY]) {
1360
+ try {
1361
+ return await this.resolveCredentials(MOONSHOT_OAUTH_KEY, opts);
1362
+ } catch (err) {
1363
+ if (err instanceof NotLoggedInError && this.data["moonshot"]) {
1364
+ log(
1365
+ "WARN",
1366
+ "auth",
1367
+ 'Kimi OAuth credential is no longer valid \u2014 falling back to the Moonshot API key. Run "ezcoder login" and choose Kimi OAuth to restore OAuth auth.'
1368
+ );
1369
+ return this.data["moonshot"];
1370
+ }
1371
+ throw err;
1372
+ }
1373
+ }
1374
+ const creds = this.data[provider];
1375
+ if (!creds) {
1376
+ throw new NotLoggedInError(provider);
1377
+ }
1378
+ if (STATIC_API_KEY_PROVIDERS.has(provider)) {
1379
+ return creds;
1380
+ }
1381
+ if (!opts?.forceRefresh && Date.now() < creds.expiresAt - REFRESH_SKEW_MS) {
1382
+ return creds;
1383
+ }
1384
+ const existing = this.refreshLocks.get(provider);
1385
+ if (existing) return existing;
1386
+ const refreshPromise = withFileLock(this.filePath, async () => {
1387
+ try {
1388
+ const content = await import_promises4.default.readFile(this.filePath, "utf-8");
1389
+ const freshData = JSON.parse(content);
1390
+ const freshCreds = freshData[provider];
1391
+ if (freshCreds && !opts?.forceRefresh && Date.now() < freshCreds.expiresAt - REFRESH_SKEW_MS) {
1392
+ this.data[provider] = freshCreds;
1393
+ return freshCreds;
1394
+ }
1395
+ } catch {
1396
+ }
1397
+ const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
1398
+ let refreshed;
1399
+ try {
1400
+ refreshed = await refreshFn(creds.refreshToken);
1401
+ } catch (err) {
1402
+ const msg = err instanceof Error ? err.message : String(err);
1403
+ const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh/i.test(msg) || /unauthorized/i.test(msg);
1404
+ if (isAuthFailure) {
1405
+ delete this.data[provider];
1406
+ await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1407
+ throw new NotLoggedInError(provider);
1408
+ }
1409
+ throw err;
1410
+ }
1411
+ if (!refreshed.accountId && creds.accountId) {
1412
+ refreshed.accountId = creds.accountId;
1413
+ }
1414
+ if (!refreshed.projectId && creds.projectId) {
1415
+ refreshed.projectId = creds.projectId;
1416
+ }
1417
+ if (!refreshed.baseUrl && creds.baseUrl) {
1418
+ refreshed.baseUrl = creds.baseUrl;
1419
+ }
1420
+ this.data[provider] = refreshed;
1421
+ await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1422
+ return refreshed;
1423
+ });
1424
+ this.refreshLocks.set(provider, refreshPromise);
1425
+ try {
1426
+ return await refreshPromise;
1427
+ } finally {
1428
+ this.refreshLocks.delete(provider);
1429
+ }
1430
+ }
1431
+ /**
1432
+ * Returns a valid access token, auto-refreshing if expired.
1433
+ * Throws if not logged in.
1434
+ */
1435
+ async resolveToken(provider) {
1436
+ const creds = await this.resolveCredentials(provider);
1437
+ return creds.accessToken;
1438
+ }
1439
+ async save() {
1440
+ await withFileLock(this.filePath, async () => {
1441
+ await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1442
+ });
1443
+ }
1444
+ };
1445
+ async function atomicWriteFile(filePath, content) {
1446
+ const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${import_node_crypto6.default.randomUUID().slice(0, 8)}.tmp`;
1447
+ try {
1448
+ await import_promises4.default.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
1449
+ await import_promises4.default.rename(tmpPath, filePath);
1450
+ } catch (err) {
1451
+ await import_promises4.default.unlink(tmpPath).catch(() => {
1452
+ });
1453
+ throw err;
1454
+ }
1455
+ }
1456
+ var NotLoggedInError = class extends Error {
1457
+ provider;
1458
+ constructor(provider) {
1459
+ super(`Not logged in to ${provider}. Run "ezcoder login" to authenticate.`);
1460
+ this.name = "NotLoggedInError";
1461
+ this.provider = provider;
1462
+ }
1463
+ };
1464
+
1465
+ // src/model-registry.ts
1466
+ var MODELS = [
1467
+ // ── Anthropic ──────────────────────────────────────────
1468
+ // NOTE: Claude Mythos 5 (`claude-mythos-5`) is kept commented out — it's a
1469
+ // Project Glasswing (limited, invitation-only) model unavailable to most
1470
+ // users. Re-enable once it's generally available.
1471
+ {
1472
+ id: "claude-fable-5",
1473
+ name: "Claude Fable 5",
1474
+ provider: "anthropic",
1475
+ contextWindow: 1e6,
1476
+ maxOutputTokens: 128e3,
1477
+ supportsThinking: true,
1478
+ supportsImages: true,
1479
+ supportsVideo: false,
1480
+ costTier: "high",
1481
+ maxThinkingLevel: "max"
1482
+ },
1483
+ // {
1484
+ // // Mythos-class model offered through Project Glasswing (limited
1485
+ // // availability, invitation-only). Same underlying model as Fable 5 with
1486
+ // // some safeguards lifted; kept here so approved accounts can select it.
1487
+ // id: "claude-mythos-5",
1488
+ // name: "Claude Mythos 5",
1489
+ // provider: "anthropic",
1490
+ // contextWindow: 1_000_000,
1491
+ // maxOutputTokens: 128_000,
1492
+ // supportsThinking: true,
1493
+ // supportsImages: true,
1494
+ // supportsVideo: false,
1495
+ // costTier: "high",
1496
+ // maxThinkingLevel: "max",
1497
+ // },
1498
+ {
1499
+ id: "claude-opus-4-8",
1500
+ name: "Claude Opus 4.8",
1501
+ provider: "anthropic",
1502
+ contextWindow: 1e6,
1503
+ maxOutputTokens: 128e3,
1504
+ supportsThinking: true,
1505
+ supportsImages: true,
1506
+ supportsVideo: false,
1507
+ costTier: "high",
1508
+ maxThinkingLevel: "max"
1509
+ },
1510
+ {
1511
+ id: "claude-sonnet-5",
1512
+ name: "Claude Sonnet 5",
1513
+ provider: "anthropic",
1514
+ contextWindow: 1e6,
1515
+ maxOutputTokens: 128e3,
1516
+ supportsThinking: true,
1517
+ supportsImages: true,
1518
+ supportsVideo: false,
1519
+ costTier: "medium",
1520
+ maxThinkingLevel: "max"
1521
+ },
1522
+ {
1523
+ id: "claude-haiku-4-5-20251001",
1524
+ name: "Claude Haiku 4.5",
1525
+ provider: "anthropic",
1526
+ contextWindow: 2e5,
1527
+ maxOutputTokens: 64e3,
1528
+ supportsThinking: true,
1529
+ supportsImages: true,
1530
+ supportsVideo: false,
1531
+ costTier: "low",
1532
+ maxThinkingLevel: "high"
1533
+ },
1534
+ // ── OpenAI (Codex) ─────────────────────────────────────
1535
+ {
1536
+ id: "gpt-5.5",
1537
+ name: "GPT-5.5",
1538
+ provider: "openai",
1539
+ contextWindow: 105e4,
1540
+ codexContextWindow: 272e3,
1541
+ maxOutputTokens: 128e3,
1542
+ supportsThinking: true,
1543
+ supportsImages: true,
1544
+ supportsVideo: false,
1545
+ costTier: "high",
1546
+ maxThinkingLevel: "xhigh"
1547
+ },
1548
+ {
1549
+ id: "gpt-5.4",
1550
+ name: "GPT-5.4",
1551
+ provider: "openai",
1552
+ contextWindow: 105e4,
1553
+ codexContextWindow: 272e3,
1554
+ maxOutputTokens: 128e3,
1555
+ supportsThinking: true,
1556
+ supportsImages: true,
1557
+ supportsVideo: false,
1558
+ costTier: "high",
1559
+ maxThinkingLevel: "xhigh"
1560
+ },
1561
+ {
1562
+ id: "gpt-5.4-mini",
1563
+ name: "GPT-5.4 Mini",
1564
+ provider: "openai",
1565
+ contextWindow: 4e5,
1566
+ maxOutputTokens: 128e3,
1567
+ supportsThinking: true,
1568
+ supportsImages: true,
1569
+ supportsVideo: false,
1570
+ costTier: "low",
1571
+ maxThinkingLevel: "xhigh"
1572
+ },
1573
+ {
1574
+ id: "gpt-5.3-codex",
1575
+ name: "GPT-5.3 Codex",
1576
+ provider: "openai",
1577
+ contextWindow: 4e5,
1578
+ maxOutputTokens: 128e3,
1579
+ supportsThinking: true,
1580
+ supportsImages: true,
1581
+ supportsVideo: false,
1582
+ costTier: "high",
1583
+ maxThinkingLevel: "xhigh"
1584
+ },
1585
+ // ── Sakana (Fugu) ──────────────────────────────────────
1586
+ // Sakana Fugu is a multi-agent system surfaced as a standard LLM via the
1587
+ // OpenAI-compatible Sakana API (https://api.sakana.ai/v1). Both models take
1588
+ // text + image input and only accept "high"/"xhigh" reasoning effort, so the
1589
+ // top tier is `xhigh`. `fugu` routes across all providers; `fugu-ultra` is
1590
+ // the heavier tier (may need larger client timeouts on complex tasks).
1591
+ {
1592
+ id: "fugu",
1593
+ name: "Fugu",
1594
+ provider: "sakana",
1595
+ contextWindow: 1e6,
1596
+ maxOutputTokens: 128e3,
1597
+ supportsThinking: true,
1598
+ supportsImages: true,
1599
+ supportsVideo: false,
1600
+ costTier: "medium",
1601
+ maxThinkingLevel: "xhigh"
1602
+ },
1603
+ {
1604
+ id: "fugu-ultra",
1605
+ name: "Fugu Ultra",
1606
+ provider: "sakana",
1607
+ contextWindow: 1e6,
1608
+ maxOutputTokens: 128e3,
1609
+ supportsThinking: true,
1610
+ supportsImages: true,
1611
+ supportsVideo: false,
1612
+ costTier: "high",
1613
+ maxThinkingLevel: "xhigh"
1614
+ },
1615
+ // ── Gemini ─────────────────────────────────────────────
1616
+ {
1617
+ id: "gemini-3.1-flash-lite-preview",
1618
+ name: "Gemini 3.1 Flash Lite Preview",
1619
+ provider: "gemini",
1620
+ contextWindow: 1048576,
1621
+ maxOutputTokens: 65536,
1622
+ supportsThinking: true,
1623
+ supportsImages: true,
1624
+ supportsVideo: true,
1625
+ maxVideoBytes: 20 * 1024 * 1024,
1626
+ costTier: "low",
1627
+ maxThinkingLevel: "high"
1628
+ },
1629
+ {
1630
+ id: "gemini-3.5-flash",
1631
+ name: "Gemini 3.5 Flash",
1632
+ provider: "gemini",
1633
+ contextWindow: 1048576,
1634
+ maxOutputTokens: 65536,
1635
+ supportsThinking: true,
1636
+ supportsImages: true,
1637
+ supportsVideo: true,
1638
+ maxVideoBytes: 20 * 1024 * 1024,
1639
+ costTier: "low",
1640
+ maxThinkingLevel: "high"
1641
+ },
1642
+ // ── Moonshot (Kimi) ────────────────────────────────────
1643
+ {
1644
+ id: "kimi-k2.7-code",
1645
+ name: "Kimi K2.7",
1646
+ provider: "moonshot",
1647
+ contextWindow: 262144,
1648
+ maxOutputTokens: 262144,
1649
+ supportsThinking: true,
1650
+ supportsImages: true,
1651
+ supportsVideo: true,
1652
+ maxVideoBytes: 100 * 1024 * 1024,
1653
+ costTier: "medium",
1654
+ maxThinkingLevel: "high"
1655
+ },
1656
+ // ── Z.AI (GLM) ─────────────────────────────────────────
1657
+ // GLM-5.2: coding-first flagship with a usable 1M-token context window
1658
+ // (5x jump over GLM-5.1's ~200K) and 131K max output. Released 2026-06-13.
1659
+ {
1660
+ id: "glm-5.2",
1661
+ name: "GLM-5.2",
1662
+ provider: "glm",
1663
+ contextWindow: 1e6,
1664
+ maxOutputTokens: 131072,
1665
+ supportsThinking: true,
1666
+ supportsImages: false,
1667
+ supportsVideo: false,
1668
+ costTier: "medium",
1669
+ maxThinkingLevel: "high"
1670
+ },
1671
+ {
1672
+ id: "glm-5.1",
1673
+ name: "GLM-5.1",
1674
+ provider: "glm",
1675
+ contextWindow: 204800,
1676
+ maxOutputTokens: 131072,
1677
+ supportsThinking: true,
1678
+ supportsImages: false,
1679
+ supportsVideo: false,
1680
+ costTier: "medium",
1681
+ maxThinkingLevel: "high"
1682
+ },
1683
+ {
1684
+ id: "glm-4.7",
1685
+ name: "GLM-4.7",
1686
+ provider: "glm",
1687
+ contextWindow: 2e5,
1688
+ maxOutputTokens: 131072,
1689
+ supportsThinking: true,
1690
+ supportsImages: false,
1691
+ supportsVideo: false,
1692
+ costTier: "low",
1693
+ maxThinkingLevel: "high"
1694
+ },
1695
+ {
1696
+ id: "glm-4.7-flash",
1697
+ name: "GLM-4.7 Flash",
1698
+ provider: "glm",
1699
+ contextWindow: 2e5,
1700
+ maxOutputTokens: 131072,
1701
+ supportsThinking: true,
1702
+ supportsImages: false,
1703
+ supportsVideo: false,
1704
+ costTier: "low",
1705
+ maxThinkingLevel: "high"
1706
+ },
1707
+ // ── MiniMax ────────────────────────────────────────────
1708
+ {
1709
+ id: "MiniMax-M3",
1710
+ name: "MiniMax M3",
1711
+ provider: "minimax",
1712
+ contextWindow: 1e6,
1713
+ maxOutputTokens: 131072,
1714
+ supportsThinking: true,
1715
+ supportsImages: true,
1716
+ supportsVideo: true,
1717
+ maxVideoBytes: 50 * 1024 * 1024,
1718
+ costTier: "medium",
1719
+ maxThinkingLevel: "high"
1720
+ },
1721
+ // ── Xiaomi (MiMo) ──────────────────────────────────────
1722
+ // Pro series: text-only coding/agentic flagship. The legacy mimo-v2-pro
1723
+ // auto-routes to v2.5 on 2026-06-01 and is fully deprecated by 2026-06-30.
1724
+ {
1725
+ id: "mimo-v2.5-pro",
1726
+ name: "MiMo-V2.5-Pro",
1727
+ provider: "xiaomi",
1728
+ contextWindow: 1e6,
1729
+ maxOutputTokens: 131072,
1730
+ supportsThinking: true,
1731
+ supportsImages: false,
1732
+ supportsVideo: false,
1733
+ costTier: "medium",
1734
+ maxThinkingLevel: "high",
1735
+ authStorageKeys: ["xiaomi", XIAOMI_CREDITS_KEY]
1736
+ },
1737
+ // UltraSpeed: lower-latency sibling of the Pro coding flagship, same
1738
+ // text-only capability surface, premium-priced for the throughput gain.
1739
+ // API-only — not served over the Token Plan endpoint, so credentials
1740
+ // resolve from the distinct API Credits key only (see authStorageKeys doc).
1741
+ {
1742
+ id: "mimo-v2.5-pro-ultraspeed",
1743
+ name: "MiMo-V2.5-Pro-UltraSpeed",
1744
+ provider: "xiaomi",
1745
+ contextWindow: 1e6,
1746
+ maxOutputTokens: 131072,
1747
+ supportsThinking: true,
1748
+ supportsImages: false,
1749
+ supportsVideo: false,
1750
+ costTier: "high",
1751
+ maxThinkingLevel: "high",
1752
+ authStorageKeys: [XIAOMI_CREDITS_KEY]
1753
+ },
1754
+ // Omni series: native full-modal understanding (image + audio + video).
1755
+ // Video/image ride the OpenAI-compatible transport as base64 data URLs
1756
+ // (`video_url`/`image_url`), which the shared transform already emits.
1757
+ {
1758
+ id: "mimo-v2.5",
1759
+ name: "MiMo-V2.5",
1760
+ provider: "xiaomi",
1761
+ contextWindow: 1e6,
1762
+ maxOutputTokens: 131072,
1763
+ supportsThinking: true,
1764
+ supportsImages: true,
1765
+ supportsVideo: true,
1766
+ maxVideoBytes: 36 * 1024 * 1024,
1767
+ costTier: "medium",
1768
+ maxThinkingLevel: "high",
1769
+ authStorageKeys: ["xiaomi", XIAOMI_CREDITS_KEY]
1770
+ },
1771
+ // ── DeepSeek ───────────────────────────────────────────
1772
+ {
1773
+ id: "deepseek-v4-pro",
1774
+ name: "DeepSeek V4 Pro",
1775
+ provider: "deepseek",
1776
+ contextWindow: 1048576,
1777
+ maxOutputTokens: 384e3,
1778
+ supportsThinking: true,
1779
+ supportsImages: false,
1780
+ supportsVideo: false,
1781
+ costTier: "high",
1782
+ // DeepSeek V4 maps `xhigh` → its internal `max` tier.
1783
+ maxThinkingLevel: "xhigh"
1784
+ },
1785
+ {
1786
+ id: "deepseek-v4-flash",
1787
+ name: "DeepSeek V4 Flash",
1788
+ provider: "deepseek",
1789
+ contextWindow: 1048576,
1790
+ maxOutputTokens: 384e3,
1791
+ supportsThinking: true,
1792
+ supportsImages: false,
1793
+ supportsVideo: false,
1794
+ costTier: "low",
1795
+ maxThinkingLevel: "xhigh"
1796
+ },
1797
+ // ── OpenRouter ─────────────────────────────────────────
1798
+ {
1799
+ id: "qwen/qwen3.6-plus",
1800
+ name: "Qwen3.6-Plus",
1801
+ provider: "openrouter",
1802
+ contextWindow: 1e6,
1803
+ maxOutputTokens: 65536,
1804
+ supportsThinking: true,
1805
+ supportsImages: false,
1806
+ supportsVideo: false,
1807
+ costTier: "medium",
1808
+ maxThinkingLevel: "high"
1809
+ }
1810
+ ];
1811
+ function getModel(id) {
1812
+ return MODELS.find((m) => m.id === id);
1813
+ }
1814
+ function getModelsForProvider(provider) {
1815
+ return MODELS.filter((m) => m.provider === provider);
1816
+ }
1817
+ function getAuthStorageKeys(provider, modelId) {
1818
+ const model = MODELS.find((m) => m.id === modelId && m.provider === provider);
1819
+ return model?.authStorageKeys ?? [provider];
1820
+ }
1821
+ function getAuthStorageKey(provider, modelId) {
1822
+ return getAuthStorageKeys(provider, modelId)[0];
1823
+ }
1824
+ var DEFAULT_MAX_VIDEO_BYTES = 20 * 1024 * 1024;
1825
+ function getVideoByteLimit(modelId) {
1826
+ const model = getModel(modelId);
1827
+ if (!model?.supportsVideo) return void 0;
1828
+ return model.maxVideoBytes ?? DEFAULT_MAX_VIDEO_BYTES;
1829
+ }
1830
+ function getDefaultModel(provider) {
1831
+ if (provider === "xiaomi") return MODELS.find((m) => m.id === "mimo-v2.5-pro");
1832
+ if (provider === "openai") return MODELS.find((m) => m.id === "gpt-5.5");
1833
+ if (provider === "gemini") return MODELS.find((m) => m.id === "gemini-3.1-flash-lite-preview");
1834
+ if (provider === "glm") return MODELS.find((m) => m.id === "glm-5.2");
1835
+ if (provider === "moonshot") return MODELS.find((m) => m.id === "kimi-k2.7-code");
1836
+ if (provider === "minimax") return MODELS.find((m) => m.id === "MiniMax-M3");
1837
+ if (provider === "deepseek") return MODELS.find((m) => m.id === "deepseek-v4-pro");
1838
+ if (provider === "openrouter") return MODELS.find((m) => m.id === "qwen/qwen3.6-plus");
1839
+ if (provider === "sakana") return MODELS.find((m) => m.id === "fugu");
1840
+ return MODELS.find((m) => m.id === "claude-sonnet-5");
1841
+ }
1842
+ function usesOpenAICodexTransport(options) {
1843
+ return options?.provider === "openai" && Boolean(options.accountId);
1844
+ }
1845
+ function getContextWindow(modelId, options) {
1846
+ const model = getModel(modelId);
1847
+ if (!model) return 2e5;
1848
+ if (usesOpenAICodexTransport(options) && model.codexContextWindow) {
1849
+ return model.codexContextWindow;
1764
1850
  }
1765
- /**
1766
- * Returns valid credentials, auto-refreshing if expired.
1767
- * If `forceRefresh` is true, refreshes even if the token hasn't expired
1768
- * (useful when the provider rejects a token with 401 before its stored expiry).
1769
- * Throws if not logged in.
1770
- */
1771
- async resolveCredentials(provider, opts) {
1772
- await this.ensureLoaded();
1773
- if (provider === "moonshot" && this.data[MOONSHOT_OAUTH_KEY]) {
1774
- try {
1775
- return await this.resolveCredentials(MOONSHOT_OAUTH_KEY, opts);
1776
- } catch (err) {
1777
- if (err instanceof NotLoggedInError && this.data["moonshot"]) {
1778
- log(
1779
- "WARN",
1780
- "auth",
1781
- 'Kimi OAuth credential is no longer valid \u2014 falling back to the Moonshot API key. Run "ezcoder login" and choose Kimi OAuth to restore OAuth auth.'
1782
- );
1783
- return this.data["moonshot"];
1784
- }
1785
- throw err;
1786
- }
1787
- }
1788
- const creds = this.data[provider];
1789
- if (!creds) {
1790
- throw new NotLoggedInError(provider);
1791
- }
1792
- if (STATIC_API_KEY_PROVIDERS.has(provider)) {
1793
- return creds;
1794
- }
1795
- if (!opts?.forceRefresh && Date.now() < creds.expiresAt - REFRESH_SKEW_MS) {
1796
- return creds;
1797
- }
1798
- const existing = this.refreshLocks.get(provider);
1799
- if (existing) return existing;
1800
- const refreshPromise = withFileLock(this.filePath, async () => {
1801
- try {
1802
- const content = await import_promises4.default.readFile(this.filePath, "utf-8");
1803
- const freshData = JSON.parse(content);
1804
- const freshCreds = freshData[provider];
1805
- if (freshCreds && !opts?.forceRefresh && Date.now() < freshCreds.expiresAt - REFRESH_SKEW_MS) {
1806
- this.data[provider] = freshCreds;
1807
- return freshCreds;
1808
- }
1809
- } catch {
1810
- }
1811
- const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
1812
- let refreshed;
1813
- try {
1814
- refreshed = await refreshFn(creds.refreshToken);
1815
- } catch (err) {
1816
- const msg = err instanceof Error ? err.message : String(err);
1817
- const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh/i.test(msg) || /unauthorized/i.test(msg);
1818
- if (isAuthFailure) {
1819
- delete this.data[provider];
1820
- await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1821
- throw new NotLoggedInError(provider);
1822
- }
1823
- throw err;
1824
- }
1825
- if (!refreshed.accountId && creds.accountId) {
1826
- refreshed.accountId = creds.accountId;
1827
- }
1828
- if (!refreshed.projectId && creds.projectId) {
1829
- refreshed.projectId = creds.projectId;
1830
- }
1831
- if (!refreshed.baseUrl && creds.baseUrl) {
1832
- refreshed.baseUrl = creds.baseUrl;
1833
- }
1834
- this.data[provider] = refreshed;
1835
- await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1836
- return refreshed;
1837
- });
1838
- this.refreshLocks.set(provider, refreshPromise);
1839
- try {
1840
- return await refreshPromise;
1841
- } finally {
1842
- this.refreshLocks.delete(provider);
1843
- }
1851
+ return model.contextWindow;
1852
+ }
1853
+ function getMaxThinkingLevel(modelId) {
1854
+ return getModel(modelId)?.maxThinkingLevel ?? "high";
1855
+ }
1856
+ function getSummaryModel(provider, currentModelId) {
1857
+ if (provider === "anthropic") {
1858
+ return MODELS.find((m) => m.id === "claude-sonnet-5");
1844
1859
  }
1845
- /**
1846
- * Returns a valid access token, auto-refreshing if expired.
1847
- * Throws if not logged in.
1848
- */
1849
- async resolveToken(provider) {
1850
- const creds = await this.resolveCredentials(provider);
1851
- return creds.accessToken;
1860
+ if (provider === "openai" || provider === "glm" || provider === "deepseek") {
1861
+ const low = getModelsForProvider(provider).find((m) => m.costTier === "low");
1862
+ if (low) return low;
1852
1863
  }
1853
- async save() {
1854
- await withFileLock(this.filePath, async () => {
1855
- await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1856
- });
1864
+ return getModel(currentModelId) ?? getDefaultModel(provider);
1865
+ }
1866
+
1867
+ // src/thinking-level.ts
1868
+ var OPENAI_GPT_THINKING_LEVELS = ["medium", "high", "xhigh"];
1869
+ var SAKANA_THINKING_LEVELS = ["high", "xhigh"];
1870
+ var ANTHROPIC_OPUS_48_47_THINKING_LEVELS = [
1871
+ "low",
1872
+ "medium",
1873
+ "high",
1874
+ "xhigh",
1875
+ "max"
1876
+ ];
1877
+ var ANTHROPIC_ADAPTIVE_THINKING_LEVELS = [
1878
+ "low",
1879
+ "medium",
1880
+ "high",
1881
+ "max"
1882
+ ];
1883
+ function isOpenAIGptModel(provider, model) {
1884
+ return provider === "openai" && model.startsWith("gpt-");
1885
+ }
1886
+ function isSakanaModel(provider) {
1887
+ return provider === "sakana";
1888
+ }
1889
+ function isAnthropicOpus48Or47Model(provider, model) {
1890
+ return provider === "anthropic" && /opus-4-8|opus-4-7/.test(model);
1891
+ }
1892
+ function isAnthropicAdaptiveModel(provider, model) {
1893
+ return provider === "anthropic" && /opus-4-8|opus-4-7|opus-4-6|sonnet-5|fable-5|mythos-5/.test(model);
1894
+ }
1895
+ function getSupportedThinkingLevels(provider, model) {
1896
+ const maxLevel = getMaxThinkingLevel(model);
1897
+ if (isAnthropicAdaptiveModel(provider, model)) {
1898
+ const levels = isAnthropicOpus48Or47Model(provider, model) ? ANTHROPIC_OPUS_48_47_THINKING_LEVELS : ANTHROPIC_ADAPTIVE_THINKING_LEVELS;
1899
+ const maxIndex2 = levels.indexOf(maxLevel);
1900
+ if (maxIndex2 === -1) return ["low", "medium", "high"];
1901
+ return levels.slice(0, maxIndex2 + 1);
1857
1902
  }
1858
- };
1859
- async function atomicWriteFile(filePath, content) {
1860
- const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${import_node_crypto6.default.randomUUID().slice(0, 8)}.tmp`;
1861
- try {
1862
- await import_promises4.default.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
1863
- await import_promises4.default.rename(tmpPath, filePath);
1864
- } catch (err) {
1865
- await import_promises4.default.unlink(tmpPath).catch(() => {
1866
- });
1867
- throw err;
1903
+ if (isSakanaModel(provider)) {
1904
+ const maxIndex2 = SAKANA_THINKING_LEVELS.indexOf(maxLevel);
1905
+ if (maxIndex2 === -1) return SAKANA_THINKING_LEVELS;
1906
+ return SAKANA_THINKING_LEVELS.slice(0, maxIndex2 + 1);
1868
1907
  }
1908
+ if (!isOpenAIGptModel(provider, model)) return [maxLevel];
1909
+ const maxIndex = OPENAI_GPT_THINKING_LEVELS.indexOf(maxLevel);
1910
+ if (maxIndex === -1) return ["medium"];
1911
+ return OPENAI_GPT_THINKING_LEVELS.slice(0, maxIndex + 1);
1869
1912
  }
1870
- var NotLoggedInError = class extends Error {
1871
- provider;
1872
- constructor(provider) {
1873
- super(`Not logged in to ${provider}. Run "ezcoder login" to authenticate.`);
1874
- this.name = "NotLoggedInError";
1875
- this.provider = provider;
1913
+ function isThinkingLevelSupported(provider, model, level) {
1914
+ return getSupportedThinkingLevels(provider, model).includes(level);
1915
+ }
1916
+ function getNextThinkingLevel(provider, model, current) {
1917
+ const supportedLevels = getSupportedThinkingLevels(provider, model);
1918
+ const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider);
1919
+ if (!shouldCycleLevels) {
1920
+ return current ? void 0 : supportedLevels[0];
1876
1921
  }
1877
- };
1922
+ if (!current) return supportedLevels[0];
1923
+ const index = supportedLevels.indexOf(current);
1924
+ if (index === -1) return supportedLevels[0];
1925
+ return supportedLevels[index + 1];
1926
+ }
1878
1927
 
1879
1928
  // src/telegram.ts
1880
1929
  var TELEGRAM_API = "https://api.telegram.org";
@@ -2359,12 +2408,15 @@ function createAutoUpdater(config) {
2359
2408
  MOONSHOT_OAUTH_KEY,
2360
2409
  NotLoggedInError,
2361
2410
  TelegramBot,
2411
+ XIAOMI_CREDITS_KEY,
2362
2412
  closeLogger,
2363
2413
  createAutoUpdater,
2364
2414
  decodeOggOpus,
2365
2415
  downmixToMono,
2366
2416
  generatePKCE,
2367
2417
  getAppPaths,
2418
+ getAuthStorageKey,
2419
+ getAuthStorageKeys,
2368
2420
  getClaudeCliUserAgent,
2369
2421
  getClaudeCodeVersion,
2370
2422
  getContextWindow,