aihezu 2.8.9 → 2.8.11
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 +36 -158
- 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,32 +295,49 @@ function usageHint(current, limit) {
|
|
|
295
295
|
return '';
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
function
|
|
299
|
-
//
|
|
300
|
-
//
|
|
298
|
+
function getUsageApiBase(baseUrl) {
|
|
299
|
+
// 去掉路径中的 /openai 或 /api 后缀
|
|
300
|
+
// - Codex base_url 常见格式 https://xxx/openai
|
|
301
|
+
// - 部分用户 base_url 为 https://xxx/api
|
|
302
|
+
// - Claude 默认 base_url 不带后缀,等价于 no-op
|
|
303
|
+
const stripSuffix = (pathname) => {
|
|
304
|
+
let p = pathname.replace(/\/+$/, '');
|
|
305
|
+
const lower = p.toLowerCase();
|
|
306
|
+
if (lower.endsWith('/openai')) {
|
|
307
|
+
p = p.slice(0, -'/openai'.length);
|
|
308
|
+
} else if (lower.endsWith('/api')) {
|
|
309
|
+
p = p.slice(0, -'/api'.length);
|
|
310
|
+
}
|
|
311
|
+
return p;
|
|
312
|
+
};
|
|
313
|
+
|
|
301
314
|
try {
|
|
302
315
|
const url = new URL(baseUrl);
|
|
303
|
-
|
|
304
|
-
if (pathname.toLowerCase().endsWith('/openai')) {
|
|
305
|
-
pathname = pathname.slice(0, -'/openai'.length);
|
|
306
|
-
}
|
|
316
|
+
const pathname = stripSuffix(url.pathname);
|
|
307
317
|
return `${url.origin}${pathname}`;
|
|
308
318
|
} catch (error) {
|
|
309
|
-
return baseUrl.replace(/\/+$/, '')
|
|
319
|
+
return stripSuffix(baseUrl.replace(/\/+$/, ''));
|
|
310
320
|
}
|
|
311
321
|
}
|
|
312
322
|
|
|
313
|
-
async function
|
|
323
|
+
async function queryUnifiedUsage(baseUrl, authToken) {
|
|
314
324
|
try {
|
|
315
|
-
const apiBase =
|
|
325
|
+
const apiBase = getUsageApiBase(baseUrl);
|
|
316
326
|
const usageUrl = `${apiBase}/v1/usage`;
|
|
317
327
|
const res = await getJson(usageUrl, {
|
|
318
328
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
319
329
|
});
|
|
320
330
|
|
|
321
331
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
322
|
-
|
|
323
|
-
|
|
332
|
+
let detail = '';
|
|
333
|
+
const j = res.json;
|
|
334
|
+
if (j) {
|
|
335
|
+
if (typeof j.message === 'string') detail = j.message;
|
|
336
|
+
else if (typeof j.error === 'string') detail = j.error;
|
|
337
|
+
else if (j.error && typeof j.error.message === 'string') detail = j.error.message;
|
|
338
|
+
else if (typeof j.code === 'string') detail = j.code;
|
|
339
|
+
}
|
|
340
|
+
return { error: detail ? `HTTP ${res.statusCode} - ${detail}` : `HTTP ${res.statusCode}` };
|
|
324
341
|
}
|
|
325
342
|
|
|
326
343
|
if (!res.json || typeof res.json !== 'object') {
|
|
@@ -333,7 +350,7 @@ async function queryCodexUsage(baseUrl, authToken) {
|
|
|
333
350
|
}
|
|
334
351
|
}
|
|
335
352
|
|
|
336
|
-
function
|
|
353
|
+
function displayUnifiedUsageStats(stats, origin, source) {
|
|
337
354
|
const subscription = stats.subscription || {};
|
|
338
355
|
const today = (stats.usage && stats.usage.today) || {};
|
|
339
356
|
const total = (stats.usage && stats.usage.total) || {};
|
|
@@ -456,141 +473,6 @@ function displayCodexUsageStats(stats, origin, source) {
|
|
|
456
473
|
console.log('');
|
|
457
474
|
}
|
|
458
475
|
|
|
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
476
|
function showUsageHelp() {
|
|
595
477
|
console.log('用法: aihezu usage [service] [--json] [--key <apiKey>]');
|
|
596
478
|
console.log('');
|
|
@@ -688,10 +570,10 @@ async function usageCommand(args = []) {
|
|
|
688
570
|
const effectiveKey = overrideKey || config.authToken;
|
|
689
571
|
const sourceLabel = overrideKey ? `命令行 Key (${config.source})` : config.source;
|
|
690
572
|
console.log(`[查询中] ${sourceLabel} - Token: ${maskToken(effectiveKey)}`);
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
:
|
|
694
|
-
|
|
573
|
+
return {
|
|
574
|
+
sourceLabel,
|
|
575
|
+
promise: queryUnifiedUsage(config.baseUrl, effectiveKey)
|
|
576
|
+
};
|
|
695
577
|
});
|
|
696
578
|
|
|
697
579
|
const results = await Promise.allSettled(requests.map(item => item.promise));
|
|
@@ -712,11 +594,7 @@ async function usageCommand(args = []) {
|
|
|
712
594
|
});
|
|
713
595
|
} else {
|
|
714
596
|
if (!outputJson) {
|
|
715
|
-
|
|
716
|
-
displayCodexUsageStats(result.stats, result.origin, sourceLabel);
|
|
717
|
-
} else {
|
|
718
|
-
displayUsageStats(result.stats, result.origin, sourceLabel);
|
|
719
|
-
}
|
|
597
|
+
displayUnifiedUsageStats(result.stats, result.origin, sourceLabel);
|
|
720
598
|
}
|
|
721
599
|
allResults[service.name].push({
|
|
722
600
|
source: sourceLabel,
|