@kenkaiiii/gg-core 5.2.0 → 5.4.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,443 +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-5",
130
- name: "Claude Sonnet 5",
131
- provider: "anthropic",
132
- contextWindow: 1e6,
133
- maxOutputTokens: 128e3,
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
- // Omni series: native full-modal understanding (image + audio + video).
355
- // Video/image ride the OpenAI-compatible transport as base64 data URLs
356
- // (`video_url`/`image_url`), which the shared transform already emits.
357
- {
358
- id: "mimo-v2.5",
359
- name: "MiMo-V2.5",
360
- provider: "xiaomi",
361
- contextWindow: 1e6,
362
- maxOutputTokens: 131072,
363
- supportsThinking: true,
364
- supportsImages: true,
365
- supportsVideo: true,
366
- maxVideoBytes: 36 * 1024 * 1024,
367
- costTier: "medium",
368
- maxThinkingLevel: "high"
369
- },
370
- // ── DeepSeek ───────────────────────────────────────────
371
- {
372
- id: "deepseek-v4-pro",
373
- name: "DeepSeek V4 Pro",
374
- provider: "deepseek",
375
- contextWindow: 1048576,
376
- maxOutputTokens: 384e3,
377
- supportsThinking: true,
378
- supportsImages: false,
379
- supportsVideo: false,
380
- costTier: "high",
381
- // DeepSeek V4 maps `xhigh` → its internal `max` tier.
382
- maxThinkingLevel: "xhigh"
383
- },
384
- {
385
- id: "deepseek-v4-flash",
386
- name: "DeepSeek V4 Flash",
387
- provider: "deepseek",
388
- contextWindow: 1048576,
389
- maxOutputTokens: 384e3,
390
- supportsThinking: true,
391
- supportsImages: false,
392
- supportsVideo: false,
393
- costTier: "low",
394
- maxThinkingLevel: "xhigh"
395
- },
396
- // ── OpenRouter ─────────────────────────────────────────
397
- {
398
- id: "qwen/qwen3.6-plus",
399
- name: "Qwen3.6-Plus",
400
- provider: "openrouter",
401
- contextWindow: 1e6,
402
- maxOutputTokens: 65536,
403
- supportsThinking: true,
404
- supportsImages: false,
405
- supportsVideo: false,
406
- costTier: "medium",
407
- maxThinkingLevel: "high"
408
- }
409
- ];
410
- function getModel(id) {
411
- return MODELS.find((m) => m.id === id);
412
- }
413
- function getModelsForProvider(provider) {
414
- return MODELS.filter((m) => m.provider === provider);
415
- }
416
- var DEFAULT_MAX_VIDEO_BYTES = 20 * 1024 * 1024;
417
- function getVideoByteLimit(modelId) {
418
- const model = getModel(modelId);
419
- if (!model?.supportsVideo) return void 0;
420
- return model.maxVideoBytes ?? DEFAULT_MAX_VIDEO_BYTES;
421
- }
422
- function getDefaultModel(provider) {
423
- if (provider === "xiaomi") return MODELS.find((m) => m.id === "mimo-v2.5-pro");
424
- if (provider === "openai") return MODELS.find((m) => m.id === "gpt-5.5");
425
- if (provider === "gemini") return MODELS.find((m) => m.id === "gemini-3.1-flash-lite-preview");
426
- if (provider === "glm") return MODELS.find((m) => m.id === "glm-5.2");
427
- if (provider === "moonshot") return MODELS.find((m) => m.id === "kimi-k2.7-code");
428
- if (provider === "minimax") return MODELS.find((m) => m.id === "MiniMax-M3");
429
- if (provider === "deepseek") return MODELS.find((m) => m.id === "deepseek-v4-pro");
430
- if (provider === "openrouter") return MODELS.find((m) => m.id === "qwen/qwen3.6-plus");
431
- if (provider === "sakana") return MODELS.find((m) => m.id === "fugu");
432
- return MODELS.find((m) => m.id === "claude-sonnet-5");
433
- }
434
- function usesOpenAICodexTransport(options) {
435
- return options?.provider === "openai" && Boolean(options.accountId);
436
- }
437
- function getContextWindow(modelId, options) {
438
- const model = getModel(modelId);
439
- if (!model) return 2e5;
440
- if (usesOpenAICodexTransport(options) && model.codexContextWindow) {
441
- return model.codexContextWindow;
442
- }
443
- return model.contextWindow;
444
- }
445
- function getMaxThinkingLevel(modelId) {
446
- return getModel(modelId)?.maxThinkingLevel ?? "high";
447
- }
448
- function getSummaryModel(provider, currentModelId) {
449
- if (provider === "anthropic") {
450
- return MODELS.find((m) => m.id === "claude-sonnet-5");
451
- }
452
- if (provider === "openai" || provider === "glm" || provider === "deepseek") {
453
- const low = getModelsForProvider(provider).find((m) => m.costTier === "low");
454
- if (low) return low;
455
- }
456
- return getModel(currentModelId) ?? getDefaultModel(provider);
457
- }
458
-
459
- // src/thinking-level.ts
460
- var OPENAI_GPT_THINKING_LEVELS = ["medium", "high", "xhigh"];
461
- var SAKANA_THINKING_LEVELS = ["high", "xhigh"];
462
- var ANTHROPIC_OPUS_48_47_THINKING_LEVELS = [
463
- "low",
464
- "medium",
465
- "high",
466
- "xhigh",
467
- "max"
468
- ];
469
- var ANTHROPIC_ADAPTIVE_THINKING_LEVELS = [
470
- "low",
471
- "medium",
472
- "high",
473
- "max"
474
- ];
475
- function isOpenAIGptModel(provider, model) {
476
- return provider === "openai" && model.startsWith("gpt-");
477
- }
478
- function isSakanaModel(provider) {
479
- return provider === "sakana";
480
- }
481
- function isAnthropicOpus48Or47Model(provider, model) {
482
- return provider === "anthropic" && /opus-4-8|opus-4-7/.test(model);
483
- }
484
- function isAnthropicAdaptiveModel(provider, model) {
485
- return provider === "anthropic" && /opus-4-8|opus-4-7|opus-4-6|sonnet-5|fable-5|mythos-5/.test(model);
486
- }
487
- function getSupportedThinkingLevels(provider, model) {
488
- const maxLevel = getMaxThinkingLevel(model);
489
- if (isAnthropicAdaptiveModel(provider, model)) {
490
- const levels = isAnthropicOpus48Or47Model(provider, model) ? ANTHROPIC_OPUS_48_47_THINKING_LEVELS : ANTHROPIC_ADAPTIVE_THINKING_LEVELS;
491
- const maxIndex2 = levels.indexOf(maxLevel);
492
- if (maxIndex2 === -1) return ["low", "medium", "high"];
493
- return levels.slice(0, maxIndex2 + 1);
494
- }
495
- if (isSakanaModel(provider)) {
496
- const maxIndex2 = SAKANA_THINKING_LEVELS.indexOf(maxLevel);
497
- if (maxIndex2 === -1) return SAKANA_THINKING_LEVELS;
498
- return SAKANA_THINKING_LEVELS.slice(0, maxIndex2 + 1);
499
- }
500
- if (!isOpenAIGptModel(provider, model)) return [maxLevel];
501
- const maxIndex = OPENAI_GPT_THINKING_LEVELS.indexOf(maxLevel);
502
- if (maxIndex === -1) return ["medium"];
503
- return OPENAI_GPT_THINKING_LEVELS.slice(0, maxIndex + 1);
504
- }
505
- function isThinkingLevelSupported(provider, model, level) {
506
- return getSupportedThinkingLevels(provider, model).includes(level);
507
- }
508
- function getNextThinkingLevel(provider, model, current) {
509
- const supportedLevels = getSupportedThinkingLevels(provider, model);
510
- const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider);
511
- if (!shouldCycleLevels) {
512
- return current ? void 0 : supportedLevels[0];
513
- }
514
- if (!current) return supportedLevels[0];
515
- const index = supportedLevels.indexOf(current);
516
- if (index === -1) return supportedLevels[0];
517
- return supportedLevels[index + 1];
518
- }
85
+ // src/auth-storage.ts
86
+ var import_promises4 = __toESM(require("fs/promises"), 1);
87
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
519
88
 
520
89
  // src/paths.ts
521
90
  var import_node_path = __toESM(require("path"), 1);
@@ -538,6 +107,31 @@ function getAppPaths() {
538
107
  };
539
108
  }
540
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
+
541
135
  // src/logger.ts
542
136
  var import_node_fs = __toESM(require("fs"), 1);
543
137
  var import_node_path2 = __toESM(require("path"), 1);
@@ -596,90 +190,26 @@ function log(level, category, message, data) {
596
190
  }
597
191
  line += "\n";
598
192
  try {
599
- import_node_fs.default.writeSync(fd, line);
600
- } catch {
601
- }
602
- }
603
- function registerLogCleanup(fn) {
604
- cleanups.push(fn);
605
- }
606
- function closeLogger(opts) {
607
- if (fd === null) return;
608
- if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
609
- try {
610
- import_node_fs.default.closeSync(fd);
611
- } catch {
612
- }
613
- fd = null;
614
- for (const unsub of cleanups) unsub();
615
- cleanups = [];
616
- }
617
-
618
- // src/file-lock.ts
619
- var import_promises = __toESM(require("fs/promises"), 1);
620
- var import_promises2 = require("timers/promises");
621
- var STALE_TIMEOUT_MS = 1e4;
622
- var RETRY_INTERVAL_MS = 50;
623
- var MAX_WAIT_MS = 5e3;
624
- async function withFileLock(filePath, fn) {
625
- const lockPath = filePath + ".lock";
626
- await acquireLock(lockPath);
627
- try {
628
- return await fn();
629
- } finally {
630
- await releaseLock(lockPath);
631
- }
632
- }
633
- async function acquireLock(lockPath) {
634
- const startTime = Date.now();
635
- while (true) {
636
- try {
637
- const info = { pid: process.pid, timestamp: Date.now() };
638
- await import_promises.default.writeFile(lockPath, JSON.stringify(info), { flag: "wx" });
639
- return;
640
- } catch (err) {
641
- if (err.code !== "EEXIST") throw err;
642
- try {
643
- const content = await import_promises.default.readFile(lockPath, "utf-8");
644
- const info = JSON.parse(content);
645
- const isProcessAlive = isAlive(info.pid);
646
- const isStale = Date.now() - info.timestamp > STALE_TIMEOUT_MS;
647
- if (!isProcessAlive || isStale) {
648
- await import_promises.default.unlink(lockPath).catch(() => {
649
- });
650
- continue;
651
- }
652
- } catch {
653
- await import_promises.default.unlink(lockPath).catch(() => {
654
- });
655
- continue;
656
- }
657
- if (Date.now() - startTime > MAX_WAIT_MS) {
658
- await import_promises.default.unlink(lockPath).catch(() => {
659
- });
660
- continue;
661
- }
662
- await (0, import_promises2.setTimeout)(RETRY_INTERVAL_MS);
663
- }
193
+ import_node_fs.default.writeSync(fd, line);
194
+ } catch {
664
195
  }
665
196
  }
666
- async function releaseLock(lockPath) {
667
- await import_promises.default.unlink(lockPath).catch(() => {
668
- });
197
+ function registerLogCleanup(fn) {
198
+ cleanups.push(fn);
669
199
  }
670
- function isAlive(pid) {
200
+ function closeLogger(opts) {
201
+ if (fd === null) return;
202
+ if (opts?.shutdownLine !== false) log("INFO", "shutdown", `${appName} shutting down`);
671
203
  try {
672
- process.kill(pid, 0);
673
- return true;
674
- } catch (err) {
675
- if (err.code === "EPERM") return true;
676
- return false;
204
+ import_node_fs.default.closeSync(fd);
205
+ } catch {
677
206
  }
207
+ fd = null;
208
+ for (const unsub of cleanups) unsub();
209
+ cleanups = [];
678
210
  }
679
211
 
680
212
  // src/claude-code-version.ts
681
- var import_promises3 = __toESM(require("fs/promises"), 1);
682
- var import_node_path3 = __toESM(require("path"), 1);
683
213
  var NPM_LATEST_URL = "https://registry.npmjs.org/@anthropic-ai/claude-code/latest";
684
214
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
685
215
  var FETCH_TIMEOUT_MS = 3e3;
@@ -691,7 +221,7 @@ function cachePath() {
691
221
  }
692
222
  async function readDiskCache() {
693
223
  try {
694
- const raw = await import_promises3.default.readFile(cachePath(), "utf-8");
224
+ const raw = await import_promises.default.readFile(cachePath(), "utf-8");
695
225
  const parsed = JSON.parse(raw);
696
226
  if (typeof parsed.version === "string" && typeof parsed.fetchedAt === "number") {
697
227
  return parsed;
@@ -703,8 +233,8 @@ async function readDiskCache() {
703
233
  }
704
234
  async function writeDiskCache(data) {
705
235
  try {
706
- await import_promises3.default.mkdir(getAppPaths().agentDir, { recursive: true, mode: 448 });
707
- 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 });
708
238
  } catch (err) {
709
239
  log(
710
240
  "WARN",
@@ -768,31 +298,6 @@ async function getClaudeCliUserAgent() {
768
298
  return `claude-cli/${version} (external, cli)`;
769
299
  }
770
300
 
771
- // src/auth-storage.ts
772
- var import_promises4 = __toESM(require("fs/promises"), 1);
773
- var import_node_crypto6 = __toESM(require("crypto"), 1);
774
-
775
- // src/oauth/anthropic.ts
776
- var import_node_crypto2 = __toESM(require("crypto"), 1);
777
-
778
- // src/oauth/pkce.ts
779
- function base64urlEncode(bytes) {
780
- let binary = "";
781
- for (const byte of bytes) {
782
- binary += String.fromCharCode(byte);
783
- }
784
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
785
- }
786
- async function generatePKCE() {
787
- const verifierBytes = new Uint8Array(32);
788
- crypto.getRandomValues(verifierBytes);
789
- const verifier = base64urlEncode(verifierBytes);
790
- const data = new TextEncoder().encode(verifier);
791
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
792
- const challenge = base64urlEncode(new Uint8Array(hashBuffer));
793
- return { verifier, challenge };
794
- }
795
-
796
301
  // src/oauth/anthropic.ts
797
302
  var CLIENT_ID = atob("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
798
303
  var AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
@@ -1643,8 +1148,71 @@ async function refreshKimiToken(refreshToken) {
1643
1148
  throw new Error(`Kimi token refresh failed (${status}): ${errorCode || errorDetail(data)}`);
1644
1149
  }
1645
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
+
1646
1213
  // src/auth-storage.ts
1647
1214
  var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
1215
+ var XIAOMI_CREDITS_KEY = "xiaomi-credits";
1648
1216
  var REFRESH_SKEW_MS = 6e4;
1649
1217
  var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
1650
1218
  "glm",
@@ -1678,6 +1246,17 @@ var AuthStorage = class {
1678
1246
  await this.ensureLoaded();
1679
1247
  return Boolean(this.data[provider]);
1680
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
+ }
1681
1260
  /**
1682
1261
  * True if the user has any usable auth for the logical provider. For
1683
1262
  * `moonshot` this is satisfied by either the Kimi OAuth credential or the
@@ -1688,6 +1267,9 @@ var AuthStorage = class {
1688
1267
  if (provider === "moonshot") {
1689
1268
  return Boolean(this.data[MOONSHOT_OAUTH_KEY] || this.data["moonshot"]);
1690
1269
  }
1270
+ if (provider === "xiaomi") {
1271
+ return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
1272
+ }
1691
1273
  return Boolean(this.data[provider]);
1692
1274
  }
1693
1275
  /**
@@ -1744,123 +1326,593 @@ var AuthStorage = class {
1744
1326
  delete this.data[provider];
1745
1327
  await this.save();
1746
1328
  }
1747
- async clearAll() {
1748
- this.data = {};
1749
- await this.save();
1329
+ async clearAll() {
1330
+ this.data = {};
1331
+ await this.save();
1332
+ }
1333
+ /**
1334
+ * Returns valid credentials, auto-refreshing if expired.
1335
+ * If `forceRefresh` is true, refreshes even if the token hasn't expired
1336
+ * (useful when the provider rejects a token with 401 before its stored expiry).
1337
+ * Throws if not logged in.
1338
+ */
1339
+ async resolveCredentials(provider, opts) {
1340
+ await this.ensureLoaded();
1341
+ if (opts?.storageKeys && !(opts.storageKeys.length === 1 && opts.storageKeys[0] === provider)) {
1342
+ for (const key of opts.storageKeys) {
1343
+ const creds2 = this.data[key];
1344
+ if (creds2) return creds2;
1345
+ }
1346
+ throw new NotLoggedInError(provider);
1347
+ }
1348
+ if (provider === "moonshot" && this.data[MOONSHOT_OAUTH_KEY]) {
1349
+ try {
1350
+ return await this.resolveCredentials(MOONSHOT_OAUTH_KEY, opts);
1351
+ } catch (err) {
1352
+ if (err instanceof NotLoggedInError && this.data["moonshot"]) {
1353
+ log(
1354
+ "WARN",
1355
+ "auth",
1356
+ 'Kimi OAuth credential is no longer valid \u2014 falling back to the Moonshot API key. Run "ggcoder login" and choose Kimi OAuth to restore OAuth auth.'
1357
+ );
1358
+ return this.data["moonshot"];
1359
+ }
1360
+ throw err;
1361
+ }
1362
+ }
1363
+ const creds = this.data[provider];
1364
+ if (!creds) {
1365
+ throw new NotLoggedInError(provider);
1366
+ }
1367
+ if (STATIC_API_KEY_PROVIDERS.has(provider)) {
1368
+ return creds;
1369
+ }
1370
+ if (!opts?.forceRefresh && Date.now() < creds.expiresAt - REFRESH_SKEW_MS) {
1371
+ return creds;
1372
+ }
1373
+ const existing = this.refreshLocks.get(provider);
1374
+ if (existing) return existing;
1375
+ const refreshPromise = withFileLock(this.filePath, async () => {
1376
+ try {
1377
+ const content = await import_promises4.default.readFile(this.filePath, "utf-8");
1378
+ const freshData = JSON.parse(content);
1379
+ const freshCreds = freshData[provider];
1380
+ if (freshCreds && !opts?.forceRefresh && Date.now() < freshCreds.expiresAt - REFRESH_SKEW_MS) {
1381
+ this.data[provider] = freshCreds;
1382
+ return freshCreds;
1383
+ }
1384
+ } catch {
1385
+ }
1386
+ const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
1387
+ let refreshed;
1388
+ try {
1389
+ refreshed = await refreshFn(creds.refreshToken);
1390
+ } catch (err) {
1391
+ const msg = err instanceof Error ? err.message : String(err);
1392
+ const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh/i.test(msg) || /unauthorized/i.test(msg);
1393
+ if (isAuthFailure) {
1394
+ delete this.data[provider];
1395
+ await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1396
+ throw new NotLoggedInError(provider);
1397
+ }
1398
+ throw err;
1399
+ }
1400
+ if (!refreshed.accountId && creds.accountId) {
1401
+ refreshed.accountId = creds.accountId;
1402
+ }
1403
+ if (!refreshed.projectId && creds.projectId) {
1404
+ refreshed.projectId = creds.projectId;
1405
+ }
1406
+ if (!refreshed.baseUrl && creds.baseUrl) {
1407
+ refreshed.baseUrl = creds.baseUrl;
1408
+ }
1409
+ this.data[provider] = refreshed;
1410
+ await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1411
+ return refreshed;
1412
+ });
1413
+ this.refreshLocks.set(provider, refreshPromise);
1414
+ try {
1415
+ return await refreshPromise;
1416
+ } finally {
1417
+ this.refreshLocks.delete(provider);
1418
+ }
1419
+ }
1420
+ /**
1421
+ * Returns a valid access token, auto-refreshing if expired.
1422
+ * Throws if not logged in.
1423
+ */
1424
+ async resolveToken(provider) {
1425
+ const creds = await this.resolveCredentials(provider);
1426
+ return creds.accessToken;
1427
+ }
1428
+ async save() {
1429
+ await withFileLock(this.filePath, async () => {
1430
+ await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1431
+ });
1432
+ }
1433
+ };
1434
+ async function atomicWriteFile(filePath, content) {
1435
+ const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${import_node_crypto6.default.randomUUID().slice(0, 8)}.tmp`;
1436
+ try {
1437
+ await import_promises4.default.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
1438
+ await import_promises4.default.rename(tmpPath, filePath);
1439
+ } catch (err) {
1440
+ await import_promises4.default.unlink(tmpPath).catch(() => {
1441
+ });
1442
+ throw err;
1443
+ }
1444
+ }
1445
+ var NotLoggedInError = class extends Error {
1446
+ provider;
1447
+ constructor(provider) {
1448
+ super(`Not logged in to ${provider}. Run "ggcoder login" to authenticate.`);
1449
+ this.name = "NotLoggedInError";
1450
+ this.provider = provider;
1451
+ }
1452
+ };
1453
+
1454
+ // src/model-registry.ts
1455
+ var MODELS = [
1456
+ // ── Anthropic ──────────────────────────────────────────
1457
+ // NOTE: Claude Mythos 5 (`claude-mythos-5`) is kept commented out — it's a
1458
+ // Project Glasswing (limited, invitation-only) model unavailable to most
1459
+ // users. Re-enable once it's generally available.
1460
+ {
1461
+ id: "claude-fable-5",
1462
+ name: "Claude Fable 5",
1463
+ provider: "anthropic",
1464
+ contextWindow: 1e6,
1465
+ maxOutputTokens: 128e3,
1466
+ supportsThinking: true,
1467
+ supportsImages: true,
1468
+ supportsVideo: false,
1469
+ costTier: "high",
1470
+ maxThinkingLevel: "max"
1471
+ },
1472
+ // {
1473
+ // // Mythos-class model offered through Project Glasswing (limited
1474
+ // // availability, invitation-only). Same underlying model as Fable 5 with
1475
+ // // some safeguards lifted; kept here so approved accounts can select it.
1476
+ // id: "claude-mythos-5",
1477
+ // name: "Claude Mythos 5",
1478
+ // provider: "anthropic",
1479
+ // contextWindow: 1_000_000,
1480
+ // maxOutputTokens: 128_000,
1481
+ // supportsThinking: true,
1482
+ // supportsImages: true,
1483
+ // supportsVideo: false,
1484
+ // costTier: "high",
1485
+ // maxThinkingLevel: "max",
1486
+ // },
1487
+ {
1488
+ id: "claude-opus-4-8",
1489
+ name: "Claude Opus 4.8",
1490
+ provider: "anthropic",
1491
+ contextWindow: 1e6,
1492
+ maxOutputTokens: 128e3,
1493
+ supportsThinking: true,
1494
+ supportsImages: true,
1495
+ supportsVideo: false,
1496
+ costTier: "high",
1497
+ maxThinkingLevel: "max"
1498
+ },
1499
+ {
1500
+ id: "claude-sonnet-5",
1501
+ name: "Claude Sonnet 5",
1502
+ provider: "anthropic",
1503
+ contextWindow: 1e6,
1504
+ maxOutputTokens: 128e3,
1505
+ supportsThinking: true,
1506
+ supportsImages: true,
1507
+ supportsVideo: false,
1508
+ costTier: "medium",
1509
+ maxThinkingLevel: "max"
1510
+ },
1511
+ {
1512
+ id: "claude-haiku-4-5-20251001",
1513
+ name: "Claude Haiku 4.5",
1514
+ provider: "anthropic",
1515
+ contextWindow: 2e5,
1516
+ maxOutputTokens: 64e3,
1517
+ supportsThinking: true,
1518
+ supportsImages: true,
1519
+ supportsVideo: false,
1520
+ costTier: "low",
1521
+ maxThinkingLevel: "high"
1522
+ },
1523
+ // ── OpenAI (Codex) ─────────────────────────────────────
1524
+ {
1525
+ id: "gpt-5.5",
1526
+ name: "GPT-5.5",
1527
+ provider: "openai",
1528
+ contextWindow: 105e4,
1529
+ codexContextWindow: 272e3,
1530
+ maxOutputTokens: 128e3,
1531
+ supportsThinking: true,
1532
+ supportsImages: true,
1533
+ supportsVideo: false,
1534
+ costTier: "high",
1535
+ maxThinkingLevel: "xhigh"
1536
+ },
1537
+ {
1538
+ id: "gpt-5.4",
1539
+ name: "GPT-5.4",
1540
+ provider: "openai",
1541
+ contextWindow: 105e4,
1542
+ codexContextWindow: 272e3,
1543
+ maxOutputTokens: 128e3,
1544
+ supportsThinking: true,
1545
+ supportsImages: true,
1546
+ supportsVideo: false,
1547
+ costTier: "high",
1548
+ maxThinkingLevel: "xhigh"
1549
+ },
1550
+ {
1551
+ id: "gpt-5.4-mini",
1552
+ name: "GPT-5.4 Mini",
1553
+ provider: "openai",
1554
+ contextWindow: 4e5,
1555
+ maxOutputTokens: 128e3,
1556
+ supportsThinking: true,
1557
+ supportsImages: true,
1558
+ supportsVideo: false,
1559
+ costTier: "low",
1560
+ maxThinkingLevel: "xhigh"
1561
+ },
1562
+ {
1563
+ id: "gpt-5.3-codex",
1564
+ name: "GPT-5.3 Codex",
1565
+ provider: "openai",
1566
+ contextWindow: 4e5,
1567
+ maxOutputTokens: 128e3,
1568
+ supportsThinking: true,
1569
+ supportsImages: true,
1570
+ supportsVideo: false,
1571
+ costTier: "high",
1572
+ maxThinkingLevel: "xhigh"
1573
+ },
1574
+ // ── Sakana (Fugu) ──────────────────────────────────────
1575
+ // Sakana Fugu is a multi-agent system surfaced as a standard LLM via the
1576
+ // OpenAI-compatible Sakana API (https://api.sakana.ai/v1). Both models take
1577
+ // text + image input and only accept "high"/"xhigh" reasoning effort, so the
1578
+ // top tier is `xhigh`. `fugu` routes across all providers; `fugu-ultra` is
1579
+ // the heavier tier (may need larger client timeouts on complex tasks).
1580
+ {
1581
+ id: "fugu",
1582
+ name: "Fugu",
1583
+ provider: "sakana",
1584
+ contextWindow: 1e6,
1585
+ maxOutputTokens: 128e3,
1586
+ supportsThinking: true,
1587
+ supportsImages: true,
1588
+ supportsVideo: false,
1589
+ costTier: "medium",
1590
+ maxThinkingLevel: "xhigh"
1591
+ },
1592
+ {
1593
+ id: "fugu-ultra",
1594
+ name: "Fugu Ultra",
1595
+ provider: "sakana",
1596
+ contextWindow: 1e6,
1597
+ maxOutputTokens: 128e3,
1598
+ supportsThinking: true,
1599
+ supportsImages: true,
1600
+ supportsVideo: false,
1601
+ costTier: "high",
1602
+ maxThinkingLevel: "xhigh"
1603
+ },
1604
+ // ── Gemini ─────────────────────────────────────────────
1605
+ {
1606
+ id: "gemini-3.1-flash-lite-preview",
1607
+ name: "Gemini 3.1 Flash Lite Preview",
1608
+ provider: "gemini",
1609
+ contextWindow: 1048576,
1610
+ maxOutputTokens: 65536,
1611
+ supportsThinking: true,
1612
+ supportsImages: true,
1613
+ supportsVideo: true,
1614
+ maxVideoBytes: 20 * 1024 * 1024,
1615
+ costTier: "low",
1616
+ maxThinkingLevel: "high"
1617
+ },
1618
+ {
1619
+ id: "gemini-3.5-flash",
1620
+ name: "Gemini 3.5 Flash",
1621
+ provider: "gemini",
1622
+ contextWindow: 1048576,
1623
+ maxOutputTokens: 65536,
1624
+ supportsThinking: true,
1625
+ supportsImages: true,
1626
+ supportsVideo: true,
1627
+ maxVideoBytes: 20 * 1024 * 1024,
1628
+ costTier: "low",
1629
+ maxThinkingLevel: "high"
1630
+ },
1631
+ // ── Moonshot (Kimi) ────────────────────────────────────
1632
+ {
1633
+ id: "kimi-k2.7-code",
1634
+ name: "Kimi K2.7",
1635
+ provider: "moonshot",
1636
+ contextWindow: 262144,
1637
+ maxOutputTokens: 262144,
1638
+ supportsThinking: true,
1639
+ supportsImages: true,
1640
+ supportsVideo: true,
1641
+ maxVideoBytes: 100 * 1024 * 1024,
1642
+ costTier: "medium",
1643
+ maxThinkingLevel: "high"
1644
+ },
1645
+ // ── Z.AI (GLM) ─────────────────────────────────────────
1646
+ // GLM-5.2: coding-first flagship with a usable 1M-token context window
1647
+ // (5x jump over GLM-5.1's ~200K) and 131K max output. Released 2026-06-13.
1648
+ {
1649
+ id: "glm-5.2",
1650
+ name: "GLM-5.2",
1651
+ provider: "glm",
1652
+ contextWindow: 1e6,
1653
+ maxOutputTokens: 131072,
1654
+ supportsThinking: true,
1655
+ supportsImages: false,
1656
+ supportsVideo: false,
1657
+ costTier: "medium",
1658
+ maxThinkingLevel: "high"
1659
+ },
1660
+ {
1661
+ id: "glm-5.1",
1662
+ name: "GLM-5.1",
1663
+ provider: "glm",
1664
+ contextWindow: 204800,
1665
+ maxOutputTokens: 131072,
1666
+ supportsThinking: true,
1667
+ supportsImages: false,
1668
+ supportsVideo: false,
1669
+ costTier: "medium",
1670
+ maxThinkingLevel: "high"
1671
+ },
1672
+ {
1673
+ id: "glm-4.7",
1674
+ name: "GLM-4.7",
1675
+ provider: "glm",
1676
+ contextWindow: 2e5,
1677
+ maxOutputTokens: 131072,
1678
+ supportsThinking: true,
1679
+ supportsImages: false,
1680
+ supportsVideo: false,
1681
+ costTier: "low",
1682
+ maxThinkingLevel: "high"
1683
+ },
1684
+ {
1685
+ id: "glm-4.7-flash",
1686
+ name: "GLM-4.7 Flash",
1687
+ provider: "glm",
1688
+ contextWindow: 2e5,
1689
+ maxOutputTokens: 131072,
1690
+ supportsThinking: true,
1691
+ supportsImages: false,
1692
+ supportsVideo: false,
1693
+ costTier: "low",
1694
+ maxThinkingLevel: "high"
1695
+ },
1696
+ // ── MiniMax ────────────────────────────────────────────
1697
+ {
1698
+ id: "MiniMax-M3",
1699
+ name: "MiniMax M3",
1700
+ provider: "minimax",
1701
+ contextWindow: 1e6,
1702
+ maxOutputTokens: 131072,
1703
+ supportsThinking: true,
1704
+ supportsImages: true,
1705
+ supportsVideo: true,
1706
+ maxVideoBytes: 50 * 1024 * 1024,
1707
+ costTier: "medium",
1708
+ maxThinkingLevel: "high"
1709
+ },
1710
+ // ── Xiaomi (MiMo) ──────────────────────────────────────
1711
+ // Pro series: text-only coding/agentic flagship. The legacy mimo-v2-pro
1712
+ // auto-routes to v2.5 on 2026-06-01 and is fully deprecated by 2026-06-30.
1713
+ {
1714
+ id: "mimo-v2.5-pro",
1715
+ name: "MiMo-V2.5-Pro",
1716
+ provider: "xiaomi",
1717
+ contextWindow: 1e6,
1718
+ maxOutputTokens: 131072,
1719
+ supportsThinking: true,
1720
+ supportsImages: false,
1721
+ supportsVideo: false,
1722
+ costTier: "medium",
1723
+ maxThinkingLevel: "high",
1724
+ authStorageKeys: ["xiaomi", XIAOMI_CREDITS_KEY]
1725
+ },
1726
+ // UltraSpeed: lower-latency sibling of the Pro coding flagship, same
1727
+ // text-only capability surface, premium-priced for the throughput gain.
1728
+ // API-only — not served over the Token Plan endpoint, so credentials
1729
+ // resolve from the distinct API Credits key only (see authStorageKeys doc).
1730
+ {
1731
+ id: "mimo-v2.5-pro-ultraspeed",
1732
+ name: "MiMo-V2.5-Pro-UltraSpeed",
1733
+ provider: "xiaomi",
1734
+ contextWindow: 1e6,
1735
+ maxOutputTokens: 131072,
1736
+ supportsThinking: true,
1737
+ supportsImages: false,
1738
+ supportsVideo: false,
1739
+ costTier: "high",
1740
+ maxThinkingLevel: "high",
1741
+ authStorageKeys: [XIAOMI_CREDITS_KEY]
1742
+ },
1743
+ // Omni series: native full-modal understanding (image + audio + video).
1744
+ // Video/image ride the OpenAI-compatible transport as base64 data URLs
1745
+ // (`video_url`/`image_url`), which the shared transform already emits.
1746
+ {
1747
+ id: "mimo-v2.5",
1748
+ name: "MiMo-V2.5",
1749
+ provider: "xiaomi",
1750
+ contextWindow: 1e6,
1751
+ maxOutputTokens: 131072,
1752
+ supportsThinking: true,
1753
+ supportsImages: true,
1754
+ supportsVideo: true,
1755
+ maxVideoBytes: 36 * 1024 * 1024,
1756
+ costTier: "medium",
1757
+ maxThinkingLevel: "high",
1758
+ authStorageKeys: ["xiaomi", XIAOMI_CREDITS_KEY]
1759
+ },
1760
+ // ── DeepSeek ───────────────────────────────────────────
1761
+ {
1762
+ id: "deepseek-v4-pro",
1763
+ name: "DeepSeek V4 Pro",
1764
+ provider: "deepseek",
1765
+ contextWindow: 1048576,
1766
+ maxOutputTokens: 384e3,
1767
+ supportsThinking: true,
1768
+ supportsImages: false,
1769
+ supportsVideo: false,
1770
+ costTier: "high",
1771
+ // DeepSeek V4 maps `xhigh` → its internal `max` tier.
1772
+ maxThinkingLevel: "xhigh"
1773
+ },
1774
+ {
1775
+ id: "deepseek-v4-flash",
1776
+ name: "DeepSeek V4 Flash",
1777
+ provider: "deepseek",
1778
+ contextWindow: 1048576,
1779
+ maxOutputTokens: 384e3,
1780
+ supportsThinking: true,
1781
+ supportsImages: false,
1782
+ supportsVideo: false,
1783
+ costTier: "low",
1784
+ maxThinkingLevel: "xhigh"
1785
+ },
1786
+ // ── OpenRouter ─────────────────────────────────────────
1787
+ {
1788
+ id: "qwen/qwen3.6-plus",
1789
+ name: "Qwen3.6-Plus",
1790
+ provider: "openrouter",
1791
+ contextWindow: 1e6,
1792
+ maxOutputTokens: 65536,
1793
+ supportsThinking: true,
1794
+ supportsImages: false,
1795
+ supportsVideo: false,
1796
+ costTier: "medium",
1797
+ maxThinkingLevel: "high"
1798
+ }
1799
+ ];
1800
+ function getModel(id) {
1801
+ return MODELS.find((m) => m.id === id);
1802
+ }
1803
+ function getModelsForProvider(provider) {
1804
+ return MODELS.filter((m) => m.provider === provider);
1805
+ }
1806
+ function getAuthStorageKeys(provider, modelId) {
1807
+ const model = MODELS.find((m) => m.id === modelId && m.provider === provider);
1808
+ return model?.authStorageKeys ?? [provider];
1809
+ }
1810
+ function getAuthStorageKey(provider, modelId) {
1811
+ return getAuthStorageKeys(provider, modelId)[0];
1812
+ }
1813
+ var DEFAULT_MAX_VIDEO_BYTES = 20 * 1024 * 1024;
1814
+ function getVideoByteLimit(modelId) {
1815
+ const model = getModel(modelId);
1816
+ if (!model?.supportsVideo) return void 0;
1817
+ return model.maxVideoBytes ?? DEFAULT_MAX_VIDEO_BYTES;
1818
+ }
1819
+ function getDefaultModel(provider) {
1820
+ if (provider === "xiaomi") return MODELS.find((m) => m.id === "mimo-v2.5-pro");
1821
+ if (provider === "openai") return MODELS.find((m) => m.id === "gpt-5.5");
1822
+ if (provider === "gemini") return MODELS.find((m) => m.id === "gemini-3.1-flash-lite-preview");
1823
+ if (provider === "glm") return MODELS.find((m) => m.id === "glm-5.2");
1824
+ if (provider === "moonshot") return MODELS.find((m) => m.id === "kimi-k2.7-code");
1825
+ if (provider === "minimax") return MODELS.find((m) => m.id === "MiniMax-M3");
1826
+ if (provider === "deepseek") return MODELS.find((m) => m.id === "deepseek-v4-pro");
1827
+ if (provider === "openrouter") return MODELS.find((m) => m.id === "qwen/qwen3.6-plus");
1828
+ if (provider === "sakana") return MODELS.find((m) => m.id === "fugu");
1829
+ return MODELS.find((m) => m.id === "claude-sonnet-5");
1830
+ }
1831
+ function usesOpenAICodexTransport(options) {
1832
+ return options?.provider === "openai" && Boolean(options.accountId);
1833
+ }
1834
+ function getContextWindow(modelId, options) {
1835
+ const model = getModel(modelId);
1836
+ if (!model) return 2e5;
1837
+ if (usesOpenAICodexTransport(options) && model.codexContextWindow) {
1838
+ return model.codexContextWindow;
1750
1839
  }
1751
- /**
1752
- * Returns valid credentials, auto-refreshing if expired.
1753
- * If `forceRefresh` is true, refreshes even if the token hasn't expired
1754
- * (useful when the provider rejects a token with 401 before its stored expiry).
1755
- * Throws if not logged in.
1756
- */
1757
- async resolveCredentials(provider, opts) {
1758
- await this.ensureLoaded();
1759
- if (provider === "moonshot" && this.data[MOONSHOT_OAUTH_KEY]) {
1760
- try {
1761
- return await this.resolveCredentials(MOONSHOT_OAUTH_KEY, opts);
1762
- } catch (err) {
1763
- if (err instanceof NotLoggedInError && this.data["moonshot"]) {
1764
- log(
1765
- "WARN",
1766
- "auth",
1767
- 'Kimi OAuth credential is no longer valid \u2014 falling back to the Moonshot API key. Run "ggcoder login" and choose Kimi OAuth to restore OAuth auth.'
1768
- );
1769
- return this.data["moonshot"];
1770
- }
1771
- throw err;
1772
- }
1773
- }
1774
- const creds = this.data[provider];
1775
- if (!creds) {
1776
- throw new NotLoggedInError(provider);
1777
- }
1778
- if (STATIC_API_KEY_PROVIDERS.has(provider)) {
1779
- return creds;
1780
- }
1781
- if (!opts?.forceRefresh && Date.now() < creds.expiresAt - REFRESH_SKEW_MS) {
1782
- return creds;
1783
- }
1784
- const existing = this.refreshLocks.get(provider);
1785
- if (existing) return existing;
1786
- const refreshPromise = withFileLock(this.filePath, async () => {
1787
- try {
1788
- const content = await import_promises4.default.readFile(this.filePath, "utf-8");
1789
- const freshData = JSON.parse(content);
1790
- const freshCreds = freshData[provider];
1791
- if (freshCreds && !opts?.forceRefresh && Date.now() < freshCreds.expiresAt - REFRESH_SKEW_MS) {
1792
- this.data[provider] = freshCreds;
1793
- return freshCreds;
1794
- }
1795
- } catch {
1796
- }
1797
- const refreshFn = provider === "anthropic" ? refreshAnthropicToken : provider === "gemini" ? refreshGeminiToken : provider === MOONSHOT_OAUTH_KEY ? refreshKimiToken : refreshOpenAIToken;
1798
- let refreshed;
1799
- try {
1800
- refreshed = await refreshFn(creds.refreshToken);
1801
- } catch (err) {
1802
- const msg = err instanceof Error ? err.message : String(err);
1803
- const isAuthFailure = /\((401|400)\)/.test(msg) || /invalid_grant|invalid_token|invalid.*refresh/i.test(msg) || /unauthorized/i.test(msg);
1804
- if (isAuthFailure) {
1805
- delete this.data[provider];
1806
- await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1807
- throw new NotLoggedInError(provider);
1808
- }
1809
- throw err;
1810
- }
1811
- if (!refreshed.accountId && creds.accountId) {
1812
- refreshed.accountId = creds.accountId;
1813
- }
1814
- if (!refreshed.projectId && creds.projectId) {
1815
- refreshed.projectId = creds.projectId;
1816
- }
1817
- if (!refreshed.baseUrl && creds.baseUrl) {
1818
- refreshed.baseUrl = creds.baseUrl;
1819
- }
1820
- this.data[provider] = refreshed;
1821
- await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1822
- return refreshed;
1823
- });
1824
- this.refreshLocks.set(provider, refreshPromise);
1825
- try {
1826
- return await refreshPromise;
1827
- } finally {
1828
- this.refreshLocks.delete(provider);
1829
- }
1840
+ return model.contextWindow;
1841
+ }
1842
+ function getMaxThinkingLevel(modelId) {
1843
+ return getModel(modelId)?.maxThinkingLevel ?? "high";
1844
+ }
1845
+ function getSummaryModel(provider, currentModelId) {
1846
+ if (provider === "anthropic") {
1847
+ return MODELS.find((m) => m.id === "claude-sonnet-5");
1830
1848
  }
1831
- /**
1832
- * Returns a valid access token, auto-refreshing if expired.
1833
- * Throws if not logged in.
1834
- */
1835
- async resolveToken(provider) {
1836
- const creds = await this.resolveCredentials(provider);
1837
- return creds.accessToken;
1849
+ if (provider === "openai" || provider === "glm" || provider === "deepseek") {
1850
+ const low = getModelsForProvider(provider).find((m) => m.costTier === "low");
1851
+ if (low) return low;
1838
1852
  }
1839
- async save() {
1840
- await withFileLock(this.filePath, async () => {
1841
- await atomicWriteFile(this.filePath, JSON.stringify(this.data, null, 2));
1842
- });
1853
+ return getModel(currentModelId) ?? getDefaultModel(provider);
1854
+ }
1855
+
1856
+ // src/thinking-level.ts
1857
+ var OPENAI_GPT_THINKING_LEVELS = ["medium", "high", "xhigh"];
1858
+ var SAKANA_THINKING_LEVELS = ["high", "xhigh"];
1859
+ var ANTHROPIC_OPUS_48_47_THINKING_LEVELS = [
1860
+ "low",
1861
+ "medium",
1862
+ "high",
1863
+ "xhigh",
1864
+ "max"
1865
+ ];
1866
+ var ANTHROPIC_ADAPTIVE_THINKING_LEVELS = [
1867
+ "low",
1868
+ "medium",
1869
+ "high",
1870
+ "max"
1871
+ ];
1872
+ function isOpenAIGptModel(provider, model) {
1873
+ return provider === "openai" && model.startsWith("gpt-");
1874
+ }
1875
+ function isSakanaModel(provider) {
1876
+ return provider === "sakana";
1877
+ }
1878
+ function isAnthropicOpus48Or47Model(provider, model) {
1879
+ return provider === "anthropic" && /opus-4-8|opus-4-7/.test(model);
1880
+ }
1881
+ function isAnthropicAdaptiveModel(provider, model) {
1882
+ return provider === "anthropic" && /opus-4-8|opus-4-7|opus-4-6|sonnet-5|fable-5|mythos-5/.test(model);
1883
+ }
1884
+ function getSupportedThinkingLevels(provider, model) {
1885
+ const maxLevel = getMaxThinkingLevel(model);
1886
+ if (isAnthropicAdaptiveModel(provider, model)) {
1887
+ const levels = isAnthropicOpus48Or47Model(provider, model) ? ANTHROPIC_OPUS_48_47_THINKING_LEVELS : ANTHROPIC_ADAPTIVE_THINKING_LEVELS;
1888
+ const maxIndex2 = levels.indexOf(maxLevel);
1889
+ if (maxIndex2 === -1) return ["low", "medium", "high"];
1890
+ return levels.slice(0, maxIndex2 + 1);
1843
1891
  }
1844
- };
1845
- async function atomicWriteFile(filePath, content) {
1846
- const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${import_node_crypto6.default.randomUUID().slice(0, 8)}.tmp`;
1847
- try {
1848
- await import_promises4.default.writeFile(tmpPath, content, { encoding: "utf-8", mode: 384 });
1849
- await import_promises4.default.rename(tmpPath, filePath);
1850
- } catch (err) {
1851
- await import_promises4.default.unlink(tmpPath).catch(() => {
1852
- });
1853
- throw err;
1892
+ if (isSakanaModel(provider)) {
1893
+ const maxIndex2 = SAKANA_THINKING_LEVELS.indexOf(maxLevel);
1894
+ if (maxIndex2 === -1) return SAKANA_THINKING_LEVELS;
1895
+ return SAKANA_THINKING_LEVELS.slice(0, maxIndex2 + 1);
1854
1896
  }
1897
+ if (!isOpenAIGptModel(provider, model)) return [maxLevel];
1898
+ const maxIndex = OPENAI_GPT_THINKING_LEVELS.indexOf(maxLevel);
1899
+ if (maxIndex === -1) return ["medium"];
1900
+ return OPENAI_GPT_THINKING_LEVELS.slice(0, maxIndex + 1);
1855
1901
  }
1856
- var NotLoggedInError = class extends Error {
1857
- provider;
1858
- constructor(provider) {
1859
- super(`Not logged in to ${provider}. Run "ggcoder login" to authenticate.`);
1860
- this.name = "NotLoggedInError";
1861
- this.provider = provider;
1902
+ function isThinkingLevelSupported(provider, model, level) {
1903
+ return getSupportedThinkingLevels(provider, model).includes(level);
1904
+ }
1905
+ function getNextThinkingLevel(provider, model, current) {
1906
+ const supportedLevels = getSupportedThinkingLevels(provider, model);
1907
+ const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider);
1908
+ if (!shouldCycleLevels) {
1909
+ return current ? void 0 : supportedLevels[0];
1862
1910
  }
1863
- };
1911
+ if (!current) return supportedLevels[0];
1912
+ const index = supportedLevels.indexOf(current);
1913
+ if (index === -1) return supportedLevels[0];
1914
+ return supportedLevels[index + 1];
1915
+ }
1864
1916
 
1865
1917
  // src/telegram.ts
1866
1918
  var TELEGRAM_API = "https://api.telegram.org";
@@ -2345,12 +2397,15 @@ function createAutoUpdater(config) {
2345
2397
  MOONSHOT_OAUTH_KEY,
2346
2398
  NotLoggedInError,
2347
2399
  TelegramBot,
2400
+ XIAOMI_CREDITS_KEY,
2348
2401
  closeLogger,
2349
2402
  createAutoUpdater,
2350
2403
  decodeOggOpus,
2351
2404
  downmixToMono,
2352
2405
  generatePKCE,
2353
2406
  getAppPaths,
2407
+ getAuthStorageKey,
2408
+ getAuthStorageKeys,
2354
2409
  getClaudeCliUserAgent,
2355
2410
  getClaudeCodeVersion,
2356
2411
  getContextWindow,