aihezu 2.8.9 → 2.8.10
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/commands/usage.js +21 -153
- package/package.json +1 -1
package/commands/usage.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const {
|
|
4
|
+
const { getJson } = require('../lib/http');
|
|
5
5
|
const platform = require('../lib/platform');
|
|
6
6
|
|
|
7
7
|
function normalizeHttpUrl(url) {
|
|
@@ -295,9 +295,9 @@ function usageHint(current, limit) {
|
|
|
295
295
|
return '';
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
function
|
|
299
|
-
//
|
|
300
|
-
//
|
|
298
|
+
function getUsageApiBase(baseUrl) {
|
|
299
|
+
// 去掉路径中的 /openai 后缀(Codex 的 base_url 常见格式 https://xxx/openai;
|
|
300
|
+
// Claude 的 base_url 不会带,等价于 no-op)
|
|
301
301
|
try {
|
|
302
302
|
const url = new URL(baseUrl);
|
|
303
303
|
let pathname = url.pathname.replace(/\/+$/, '');
|
|
@@ -310,17 +310,24 @@ function getCodexUsageBase(baseUrl) {
|
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
async function
|
|
313
|
+
async function queryUnifiedUsage(baseUrl, authToken) {
|
|
314
314
|
try {
|
|
315
|
-
const apiBase =
|
|
315
|
+
const apiBase = getUsageApiBase(baseUrl);
|
|
316
316
|
const usageUrl = `${apiBase}/v1/usage`;
|
|
317
317
|
const res = await getJson(usageUrl, {
|
|
318
318
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
319
319
|
});
|
|
320
320
|
|
|
321
321
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
322
|
-
|
|
323
|
-
|
|
322
|
+
let detail = '';
|
|
323
|
+
const j = res.json;
|
|
324
|
+
if (j) {
|
|
325
|
+
if (typeof j.message === 'string') detail = j.message;
|
|
326
|
+
else if (typeof j.error === 'string') detail = j.error;
|
|
327
|
+
else if (j.error && typeof j.error.message === 'string') detail = j.error.message;
|
|
328
|
+
else if (typeof j.code === 'string') detail = j.code;
|
|
329
|
+
}
|
|
330
|
+
return { error: detail ? `HTTP ${res.statusCode} - ${detail}` : `HTTP ${res.statusCode}` };
|
|
324
331
|
}
|
|
325
332
|
|
|
326
333
|
if (!res.json || typeof res.json !== 'object') {
|
|
@@ -333,7 +340,7 @@ async function queryCodexUsage(baseUrl, authToken) {
|
|
|
333
340
|
}
|
|
334
341
|
}
|
|
335
342
|
|
|
336
|
-
function
|
|
343
|
+
function displayUnifiedUsageStats(stats, origin, source) {
|
|
337
344
|
const subscription = stats.subscription || {};
|
|
338
345
|
const today = (stats.usage && stats.usage.today) || {};
|
|
339
346
|
const total = (stats.usage && stats.usage.total) || {};
|
|
@@ -456,141 +463,6 @@ function displayCodexUsageStats(stats, origin, source) {
|
|
|
456
463
|
console.log('');
|
|
457
464
|
}
|
|
458
465
|
|
|
459
|
-
async function queryUsage(baseUrl, authToken) {
|
|
460
|
-
try {
|
|
461
|
-
const origin = new URL(baseUrl).origin;
|
|
462
|
-
const keyIdUrl = `${origin}/apiStats/api/get-key-id`;
|
|
463
|
-
const statsUrl = `${origin}/apiStats/api/user-stats`;
|
|
464
|
-
|
|
465
|
-
const keyIdRes = await postJson(keyIdUrl, { apiKey: authToken });
|
|
466
|
-
if (keyIdRes.statusCode < 200 || keyIdRes.statusCode >= 300) {
|
|
467
|
-
return { error: `HTTP ${keyIdRes.statusCode}` };
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const apiId = keyIdRes?.json?.data?.id || keyIdRes?.json?.id || null;
|
|
471
|
-
if (!apiId) {
|
|
472
|
-
return { error: '未找到 API Key ID' };
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const statsRes = await postJson(statsUrl, { apiId });
|
|
476
|
-
if (statsRes.statusCode < 200 || statsRes.statusCode >= 300) {
|
|
477
|
-
return { error: `HTTP ${statsRes.statusCode}` };
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (!statsRes.json || typeof statsRes.json !== 'object') {
|
|
481
|
-
return { error: '返回内容不是合法的 JSON' };
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const stats = (statsRes?.json?.data) ? statsRes.json.data : (statsRes.json || {});
|
|
485
|
-
return { stats, origin };
|
|
486
|
-
} catch (error) {
|
|
487
|
-
return { error: error.message };
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
function displayUsageStats(stats, origin, source) {
|
|
492
|
-
const limits = (stats && typeof stats === 'object' && stats.limits && typeof stats.limits === 'object')
|
|
493
|
-
? stats.limits
|
|
494
|
-
: {};
|
|
495
|
-
|
|
496
|
-
const currentDailyCost = (stats.currentDailyCost !== undefined) ? stats.currentDailyCost : limits.currentDailyCost;
|
|
497
|
-
const dailyCostLimit = (stats.dailyCostLimit !== undefined) ? stats.dailyCostLimit : limits.dailyCostLimit;
|
|
498
|
-
const currentWindowCost = (stats.currentWindowCost !== undefined) ? stats.currentWindowCost : limits.currentWindowCost;
|
|
499
|
-
const rateLimitCost = (stats.rateLimitCost !== undefined) ? stats.rateLimitCost : limits.rateLimitCost;
|
|
500
|
-
const windowStartTime = (stats.windowStartTime !== undefined) ? stats.windowStartTime : limits.windowStartTime;
|
|
501
|
-
const windowEndTime = (stats.windowEndTime !== undefined) ? stats.windowEndTime : limits.windowEndTime;
|
|
502
|
-
const windowRemainingSeconds = (() => {
|
|
503
|
-
const raw = (stats.windowRemainingSeconds !== undefined)
|
|
504
|
-
? stats.windowRemainingSeconds
|
|
505
|
-
: limits.windowRemainingSeconds;
|
|
506
|
-
const provided = asNumber(raw);
|
|
507
|
-
if (provided !== null) return provided;
|
|
508
|
-
const endMs = normalizeEpochMs(windowEndTime);
|
|
509
|
-
if (endMs === null) return null;
|
|
510
|
-
return Math.max(0, Math.floor((endMs - Date.now()) / 1000));
|
|
511
|
-
})();
|
|
512
|
-
const windowDurationMinutes = (() => {
|
|
513
|
-
const startMs = normalizeEpochMs(windowStartTime);
|
|
514
|
-
const endMs = normalizeEpochMs(windowEndTime);
|
|
515
|
-
if (startMs !== null && endMs !== null && endMs > startMs) {
|
|
516
|
-
return Math.round((endMs - startMs) / 60000);
|
|
517
|
-
}
|
|
518
|
-
const raw = (stats.rateLimitWindow !== undefined) ? stats.rateLimitWindow : limits.rateLimitWindow;
|
|
519
|
-
const minutes = asNumber(raw);
|
|
520
|
-
if (minutes !== null) return minutes;
|
|
521
|
-
return null;
|
|
522
|
-
})();
|
|
523
|
-
|
|
524
|
-
const dailyRemaining = (() => {
|
|
525
|
-
const c = asNumber(currentDailyCost);
|
|
526
|
-
const l = asNumber(dailyCostLimit);
|
|
527
|
-
if (c === null || l === null) return null;
|
|
528
|
-
return l - c;
|
|
529
|
-
})();
|
|
530
|
-
|
|
531
|
-
const windowRemaining = (() => {
|
|
532
|
-
const c = asNumber(currentWindowCost);
|
|
533
|
-
const l = asNumber(rateLimitCost);
|
|
534
|
-
if (c === null || l === null) return null;
|
|
535
|
-
return l - c;
|
|
536
|
-
})();
|
|
537
|
-
|
|
538
|
-
// 提取日请求次数和 Token 用量(API 中 currentWindowRequests/Tokens 实际为日数据)
|
|
539
|
-
const currentDailyRequests = (stats.currentDailyRequests !== undefined) ? stats.currentDailyRequests
|
|
540
|
-
: (limits.currentDailyRequests !== undefined) ? limits.currentDailyRequests
|
|
541
|
-
: (stats.currentWindowRequests !== undefined) ? stats.currentWindowRequests : limits.currentWindowRequests;
|
|
542
|
-
const currentDailyTokens = (stats.currentDailyTokens !== undefined) ? stats.currentDailyTokens
|
|
543
|
-
: (limits.currentDailyTokens !== undefined) ? limits.currentDailyTokens
|
|
544
|
-
: (stats.currentWindowTokens !== undefined) ? stats.currentWindowTokens : limits.currentWindowTokens;
|
|
545
|
-
|
|
546
|
-
console.log(`域名: ${origin}`);
|
|
547
|
-
console.log(`来源: ${source}`);
|
|
548
|
-
console.log('');
|
|
549
|
-
console.log(
|
|
550
|
-
`日额度: ${formatCost(currentDailyCost)} / ${formatCost(dailyCostLimit)} (${formatPercent(currentDailyCost, dailyCostLimit)}) ` +
|
|
551
|
-
`${renderBar(currentDailyCost, dailyCostLimit)} ${usageHint(currentDailyCost, dailyCostLimit)}`.trimEnd()
|
|
552
|
-
);
|
|
553
|
-
if (dailyRemaining !== null) {
|
|
554
|
-
console.log(`日剩余: ${formatCost(dailyRemaining)}`);
|
|
555
|
-
}
|
|
556
|
-
if (asNumber(currentDailyRequests) !== null || asNumber(currentDailyTokens) !== null) {
|
|
557
|
-
const parts = [];
|
|
558
|
-
if (asNumber(currentDailyRequests) !== null) parts.push(`请求 ${formatNumber(currentDailyRequests)} 次`);
|
|
559
|
-
if (asNumber(currentDailyTokens) !== null) parts.push(`Token ${formatCompactNumber(currentDailyTokens)}`);
|
|
560
|
-
console.log(`日统计: ${parts.join(' | ')}`);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
console.log('');
|
|
564
|
-
console.log(
|
|
565
|
-
`窗口: ${formatCost(currentWindowCost)} / ${formatCost(rateLimitCost)} (${formatPercent(currentWindowCost, rateLimitCost)}) ` +
|
|
566
|
-
`${renderBar(currentWindowCost, rateLimitCost)} ${usageHint(currentWindowCost, rateLimitCost)}`.trimEnd()
|
|
567
|
-
);
|
|
568
|
-
if (windowRemaining !== null) {
|
|
569
|
-
console.log(`窗口剩余: ${formatCost(windowRemaining)}`);
|
|
570
|
-
}
|
|
571
|
-
const windowStartText = formatDateTime(windowStartTime);
|
|
572
|
-
const windowEndText = formatDateTime(windowEndTime);
|
|
573
|
-
if (windowStartText !== '-' || windowEndText !== '-') {
|
|
574
|
-
if (windowStartText !== '-' && windowEndText !== '-') {
|
|
575
|
-
console.log(`时间窗口: ${windowStartText} ~ ${windowEndText}`);
|
|
576
|
-
} else if (windowStartText !== '-') {
|
|
577
|
-
console.log(`时间窗口开始: ${windowStartText}`);
|
|
578
|
-
} else {
|
|
579
|
-
console.log(`时间窗口结束: ${windowEndText}`);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
const windowDurationText = formatDurationMinutes(windowDurationMinutes);
|
|
583
|
-
if (windowDurationText !== '-') {
|
|
584
|
-
console.log(`周时间窗口: ${windowDurationText}`);
|
|
585
|
-
}
|
|
586
|
-
const resetCountdownText = formatDurationSeconds(windowRemainingSeconds);
|
|
587
|
-
if (resetCountdownText !== '-') {
|
|
588
|
-
console.log(`窗口剩余时间: ${resetCountdownText} (距重置)`);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
console.log('');
|
|
592
|
-
}
|
|
593
|
-
|
|
594
466
|
function showUsageHelp() {
|
|
595
467
|
console.log('用法: aihezu usage [service] [--json] [--key <apiKey>]');
|
|
596
468
|
console.log('');
|
|
@@ -688,10 +560,10 @@ async function usageCommand(args = []) {
|
|
|
688
560
|
const effectiveKey = overrideKey || config.authToken;
|
|
689
561
|
const sourceLabel = overrideKey ? `命令行 Key (${config.source})` : config.source;
|
|
690
562
|
console.log(`[查询中] ${sourceLabel} - Token: ${maskToken(effectiveKey)}`);
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
:
|
|
694
|
-
|
|
563
|
+
return {
|
|
564
|
+
sourceLabel,
|
|
565
|
+
promise: queryUnifiedUsage(config.baseUrl, effectiveKey)
|
|
566
|
+
};
|
|
695
567
|
});
|
|
696
568
|
|
|
697
569
|
const results = await Promise.allSettled(requests.map(item => item.promise));
|
|
@@ -712,11 +584,7 @@ async function usageCommand(args = []) {
|
|
|
712
584
|
});
|
|
713
585
|
} else {
|
|
714
586
|
if (!outputJson) {
|
|
715
|
-
|
|
716
|
-
displayCodexUsageStats(result.stats, result.origin, sourceLabel);
|
|
717
|
-
} else {
|
|
718
|
-
displayUsageStats(result.stats, result.origin, sourceLabel);
|
|
719
|
-
}
|
|
587
|
+
displayUnifiedUsageStats(result.stats, result.origin, sourceLabel);
|
|
720
588
|
}
|
|
721
589
|
allResults[service.name].push({
|
|
722
590
|
source: sourceLabel,
|