aihezu 2.8.11 → 2.8.13
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/config.js +5 -1
- package/commands/install.js +5 -1
- package/commands/usage.js +134 -155
- package/lib/url.js +32 -0
- package/package.json +1 -1
- package/services/claude.js +1 -2
- package/services/codex.js +1 -2
package/commands/config.js
CHANGED
|
@@ -2,6 +2,7 @@ const readline = require('readline');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const { askQuestion } = require('../lib/prompts');
|
|
5
|
+
const { stripBaseSuffix } = require('../lib/url');
|
|
5
6
|
|
|
6
7
|
// Helper to extract value from args like --api=xxx or --api xxx
|
|
7
8
|
function getArgValue(args, keys) {
|
|
@@ -59,7 +60,10 @@ async function configCommand(service, args = []) {
|
|
|
59
60
|
apiUrl = 'https://' + apiUrl;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
//
|
|
63
|
+
// 兼容老用户输入:剥离 base_url 末尾的 /api 或 /openai 后缀
|
|
64
|
+
apiUrl = stripBaseSuffix(apiUrl);
|
|
65
|
+
|
|
66
|
+
// gemini 仍然需要保留 /gemini 后缀
|
|
63
67
|
const suffix = service.apiSuffix || '';
|
|
64
68
|
if (suffix) {
|
|
65
69
|
try {
|
package/commands/install.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const { modifyHostsFile } = require('../lib/hosts');
|
|
5
5
|
const { cleanCache } = require('../lib/cache');
|
|
6
6
|
const { askQuestion } = require('../lib/prompts');
|
|
7
|
+
const { stripBaseSuffix } = require('../lib/url');
|
|
7
8
|
const platform = require('../lib/platform');
|
|
8
9
|
|
|
9
10
|
// Helper to extract value from args like --api=xxx or --api xxx
|
|
@@ -60,7 +61,10 @@ async function installCommand(service, args = []) {
|
|
|
60
61
|
apiUrl = 'https://' + apiUrl;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
//
|
|
64
|
+
// 兼容老用户输入:剥离 base_url 末尾的 /api 或 /openai 后缀
|
|
65
|
+
apiUrl = stripBaseSuffix(apiUrl);
|
|
66
|
+
|
|
67
|
+
// gemini 仍然需要保留 /gemini 后缀
|
|
64
68
|
const suffix = service.apiSuffix || '';
|
|
65
69
|
if (suffix) {
|
|
66
70
|
try {
|
package/commands/usage.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { getJson } = require('../lib/http');
|
|
5
|
+
const { stripBaseSuffix } = require('../lib/url');
|
|
5
6
|
const platform = require('../lib/platform');
|
|
6
7
|
|
|
7
8
|
function normalizeHttpUrl(url) {
|
|
@@ -230,32 +231,16 @@ function formatDateTime(value) {
|
|
|
230
231
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
231
232
|
}
|
|
232
233
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const parts = [];
|
|
245
|
-
if (days) parts.push(`${days} 天`);
|
|
246
|
-
if (hours) parts.push(`${hours} 小时`);
|
|
247
|
-
if (minutes) parts.push(`${minutes} 分`);
|
|
248
|
-
if (seconds || parts.length === 0) parts.push(`${seconds} 秒`);
|
|
249
|
-
return parts.join(' ');
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function formatDurationMinutes(value) {
|
|
253
|
-
const minutes = asNumber(value);
|
|
254
|
-
if (minutes === null || minutes <= 0) return '-';
|
|
255
|
-
const human = formatDurationSeconds(minutes * 60);
|
|
256
|
-
if (human === '-') return '-';
|
|
257
|
-
const minuteText = Number.isInteger(minutes) ? String(minutes) : minutes.toFixed(2);
|
|
258
|
-
return `${human} (${minuteText} 分钟)`;
|
|
234
|
+
// 把毫秒格式化为简洁的人类可读时长:11661.24 → "11.7s",950 → "950ms",65000 → "1m5s"
|
|
235
|
+
function formatDuration(ms) {
|
|
236
|
+
const n = asNumber(ms);
|
|
237
|
+
if (n === null || n < 0) return '-';
|
|
238
|
+
if (n < 1000) return `${Math.round(n)}ms`;
|
|
239
|
+
const seconds = n / 1000;
|
|
240
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
241
|
+
const m = Math.floor(seconds / 60);
|
|
242
|
+
const s = Math.round(seconds % 60);
|
|
243
|
+
return s ? `${m}m${s}s` : `${m}m`;
|
|
259
244
|
}
|
|
260
245
|
|
|
261
246
|
function formatNumber(value) {
|
|
@@ -296,28 +281,7 @@ function usageHint(current, limit) {
|
|
|
296
281
|
}
|
|
297
282
|
|
|
298
283
|
function getUsageApiBase(baseUrl) {
|
|
299
|
-
|
|
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
|
-
|
|
314
|
-
try {
|
|
315
|
-
const url = new URL(baseUrl);
|
|
316
|
-
const pathname = stripSuffix(url.pathname);
|
|
317
|
-
return `${url.origin}${pathname}`;
|
|
318
|
-
} catch (error) {
|
|
319
|
-
return stripSuffix(baseUrl.replace(/\/+$/, ''));
|
|
320
|
-
}
|
|
284
|
+
return stripBaseSuffix(baseUrl);
|
|
321
285
|
}
|
|
322
286
|
|
|
323
287
|
async function queryUnifiedUsage(baseUrl, authToken) {
|
|
@@ -325,7 +289,12 @@ async function queryUnifiedUsage(baseUrl, authToken) {
|
|
|
325
289
|
const apiBase = getUsageApiBase(baseUrl);
|
|
326
290
|
const usageUrl = `${apiBase}/v1/usage`;
|
|
327
291
|
const res = await getJson(usageUrl, {
|
|
328
|
-
headers: {
|
|
292
|
+
headers: {
|
|
293
|
+
Authorization: `Bearer ${authToken}`,
|
|
294
|
+
// 防御性禁用任何中间缓存,确保每次拿到最新快照
|
|
295
|
+
'Cache-Control': 'no-cache',
|
|
296
|
+
Pragma: 'no-cache'
|
|
297
|
+
}
|
|
329
298
|
});
|
|
330
299
|
|
|
331
300
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
@@ -350,123 +319,130 @@ async function queryUnifiedUsage(baseUrl, authToken) {
|
|
|
350
319
|
}
|
|
351
320
|
}
|
|
352
321
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
const
|
|
322
|
+
// 渲染订阅制额度行(仅当账号返回了 *_limit_usd 字段时使用)
|
|
323
|
+
function renderQuotaLine(label, used, limit) {
|
|
324
|
+
const u = asNumber(used);
|
|
325
|
+
const l = asNumber(limit);
|
|
326
|
+
if (l === null || l <= 0) return null;
|
|
327
|
+
return `${label}: ${formatCost(u)} / ${formatCost(l)} (${formatPercent(u, l)}) ` +
|
|
328
|
+
`${renderBar(u, l)} ${usageHint(u, l)}`.trimEnd();
|
|
329
|
+
}
|
|
357
330
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
const
|
|
331
|
+
// 余额 / 额度区块:钱包模式显示余额,订阅模式显示日/周/月额度进度条
|
|
332
|
+
function displayBalanceOrQuota(stats) {
|
|
333
|
+
const sub = stats.subscription || {};
|
|
334
|
+
const lines = [];
|
|
335
|
+
|
|
336
|
+
const dailyLine = renderQuotaLine('日额度', sub.daily_usage_usd, sub.daily_limit_usd);
|
|
337
|
+
const weeklyLine = renderQuotaLine('周额度', sub.weekly_usage_usd, sub.weekly_limit_usd);
|
|
338
|
+
const monthlyLine = renderQuotaLine('月额度', sub.monthly_usage_usd, sub.monthly_limit_usd);
|
|
339
|
+
if (dailyLine) lines.push(dailyLine);
|
|
340
|
+
if (weeklyLine) lines.push(weeklyLine);
|
|
341
|
+
if (monthlyLine) lines.push(monthlyLine);
|
|
342
|
+
|
|
343
|
+
if (sub.expires_at) {
|
|
344
|
+
const expiresMs = Date.parse(sub.expires_at);
|
|
345
|
+
if (!Number.isNaN(expiresMs)) lines.push(`套餐到期: ${formatDateTime(expiresMs)}`);
|
|
346
|
+
}
|
|
364
347
|
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
348
|
+
// 钱包余额(无订阅额度时)
|
|
349
|
+
const balance = asNumber(stats.remaining) !== null ? asNumber(stats.remaining) : asNumber(stats.balance);
|
|
350
|
+
if (!lines.length && balance !== null) {
|
|
351
|
+
lines.push(`余额: ${formatCost(balance)}`);
|
|
352
|
+
}
|
|
369
353
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
console.log('');
|
|
354
|
+
// 累计已用(usage.total.cost 优先,回退到 model_stats 合计)
|
|
355
|
+
const totalCost = asNumber(stats.usage && stats.usage.total && stats.usage.total.cost);
|
|
356
|
+
if (totalCost !== null) lines.push(`累计已用: ${formatCost(totalCost)}`);
|
|
374
357
|
|
|
375
|
-
if (
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
console.log(
|
|
379
|
-
`日额度: ${usageText} / ${formatCost(dailyLimit)} (${formatPercent(dailyUsage, dailyLimit)}) ` +
|
|
380
|
-
`${renderBar(dailyUsage, dailyLimit)} ${usageHint(dailyUsage, dailyLimit)}`.trimEnd()
|
|
381
|
-
);
|
|
382
|
-
const dailyRemaining = asNumber(stats.remaining);
|
|
383
|
-
if (dailyRemaining !== null) {
|
|
384
|
-
console.log(`日剩余: ${formatCost(dailyRemaining)}`);
|
|
385
|
-
} else if (dailyUsage !== null) {
|
|
386
|
-
console.log(`日剩余: ${formatCost(dailyLimit - dailyUsage)}`);
|
|
387
|
-
}
|
|
388
|
-
} else {
|
|
389
|
-
console.log(`日用量: ${usageText} (无限制)`);
|
|
390
|
-
}
|
|
358
|
+
if (lines.length) {
|
|
359
|
+
console.log('');
|
|
360
|
+
lines.forEach(line => console.log(line));
|
|
391
361
|
}
|
|
362
|
+
}
|
|
392
363
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
);
|
|
398
|
-
} else if (weeklyUsage !== null && weeklyUsage > 0) {
|
|
399
|
-
console.log(`周用量: ${formatCost(weeklyUsage)}`);
|
|
400
|
-
}
|
|
364
|
+
function displayUnifiedUsageStats(stats, origin, source, tokenMask) {
|
|
365
|
+
const usage = stats.usage || {};
|
|
366
|
+
const today = usage.today || {};
|
|
367
|
+
const total = usage.total || {};
|
|
401
368
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
} else if (monthlyUsage !== null && monthlyUsage > 0) {
|
|
408
|
-
console.log(`月用量: ${formatCost(monthlyUsage)}`);
|
|
409
|
-
}
|
|
369
|
+
const unit = stats.unit || 'USD';
|
|
370
|
+
const planName = stats.planName || '-';
|
|
371
|
+
const mode = stats.mode || '-';
|
|
372
|
+
const valid = stats.isValid === false ? '失效' : '有效';
|
|
373
|
+
const host = origin.replace(/^https?:\/\//, '');
|
|
410
374
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
375
|
+
console.log(`域名: ${host}`);
|
|
376
|
+
console.log(`来源: ${source}${tokenMask ? ' · ' + tokenMask : ''}`);
|
|
377
|
+
console.log(`套餐: ${planName} | ${mode} | ${valid} | ${unit}`);
|
|
378
|
+
|
|
379
|
+
// 余额 / 订阅额度
|
|
380
|
+
displayBalanceOrQuota(stats);
|
|
418
381
|
|
|
419
|
-
|
|
382
|
+
// 今日(日期取 daily_usage 末项,回退 today 自带)
|
|
383
|
+
const daily = Array.isArray(stats.daily_usage) ? stats.daily_usage : [];
|
|
384
|
+
const todayDate = (daily.length && daily[daily.length - 1].date) || today.date || '';
|
|
385
|
+
if (Object.keys(today).length) {
|
|
420
386
|
console.log('');
|
|
421
|
-
console.log('
|
|
422
|
-
const
|
|
423
|
-
if (asNumber(today.requests) !== null)
|
|
424
|
-
if (asNumber(today.total_tokens) !== null)
|
|
425
|
-
if (asNumber(today.
|
|
426
|
-
if (
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (
|
|
430
|
-
if (asNumber(today.
|
|
387
|
+
console.log(`[今日${todayDate ? ' ' + todayDate : ''}]`);
|
|
388
|
+
const head = [];
|
|
389
|
+
if (asNumber(today.requests) !== null) head.push(`请求 ${formatNumber(today.requests)} 次`);
|
|
390
|
+
if (asNumber(today.total_tokens) !== null) head.push(`Token ${formatCompactNumber(today.total_tokens)}`);
|
|
391
|
+
if (asNumber(today.cost) !== null) head.push(`费用 ${formatCost(today.cost)}`);
|
|
392
|
+
if (head.length) console.log(` ${head.join(' | ')}`);
|
|
393
|
+
|
|
394
|
+
const detail = [];
|
|
395
|
+
if (asNumber(today.input_tokens) !== null) detail.push(`输入 ${formatCompactNumber(today.input_tokens)}`);
|
|
396
|
+
if (asNumber(today.output_tokens) !== null) detail.push(`输出 ${formatCompactNumber(today.output_tokens)}`);
|
|
397
|
+
const cacheRead = asNumber(today.cache_read_tokens);
|
|
398
|
+
const cacheWrite = asNumber(today.cache_creation_tokens) !== null
|
|
399
|
+
? asNumber(today.cache_creation_tokens) : asNumber(today.cache_write_tokens);
|
|
400
|
+
if (cacheRead !== null) detail.push(`缓存读 ${formatCompactNumber(cacheRead)}`);
|
|
401
|
+
if (cacheWrite !== null) detail.push(`缓存写 ${formatCompactNumber(cacheWrite)}`);
|
|
402
|
+
if (detail.length) console.log(` ${detail.join(' | ')}`);
|
|
431
403
|
}
|
|
432
404
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if (totalCost !== null) parts.push(`费用 ${formatCost(total.cost)}`);
|
|
443
|
-
if (parts.length) console.log(` ${parts.join(' | ')}`);
|
|
444
|
-
}
|
|
405
|
+
// 累计
|
|
406
|
+
if ((asNumber(total.requests) !== null || asNumber(total.cost) !== null) && total !== today) {
|
|
407
|
+
console.log('');
|
|
408
|
+
console.log('[累计]');
|
|
409
|
+
const parts = [];
|
|
410
|
+
if (asNumber(total.requests) !== null) parts.push(`请求 ${formatNumber(total.requests)} 次`);
|
|
411
|
+
if (asNumber(total.total_tokens) !== null) parts.push(`Token ${formatCompactNumber(total.total_tokens)}`);
|
|
412
|
+
if (asNumber(total.cost) !== null) parts.push(`费用 ${formatCost(total.cost)}`);
|
|
413
|
+
if (parts.length) console.log(` ${parts.join(' | ')}`);
|
|
445
414
|
}
|
|
446
415
|
|
|
447
|
-
|
|
416
|
+
// 实时
|
|
448
417
|
const rpm = asNumber(usage.rpm);
|
|
449
418
|
const tpm = asNumber(usage.tpm);
|
|
450
|
-
const
|
|
451
|
-
if (rpm !== null || tpm !== null ||
|
|
419
|
+
const avg = asNumber(usage.average_duration_ms);
|
|
420
|
+
if (rpm !== null || tpm !== null || avg !== null) {
|
|
421
|
+
console.log('');
|
|
422
|
+
console.log('[实时]');
|
|
452
423
|
const parts = [];
|
|
453
424
|
if (rpm !== null) parts.push(`RPM ${rpm}`);
|
|
454
|
-
if (tpm !== null) parts.push(`TPM ${tpm}`);
|
|
455
|
-
if (
|
|
456
|
-
console.log(
|
|
425
|
+
if (tpm !== null) parts.push(`TPM ${formatCompactNumber(tpm)}`);
|
|
426
|
+
if (avg !== null) parts.push(`平均 ${formatDuration(avg)}`);
|
|
427
|
+
console.log(` ${parts.join(' | ')}`);
|
|
457
428
|
}
|
|
458
429
|
|
|
459
|
-
|
|
430
|
+
// 模型用量(按费用降序,列对齐)
|
|
431
|
+
const models = Array.isArray(stats.model_stats) ? stats.model_stats.slice() : [];
|
|
432
|
+
if (models.length) {
|
|
433
|
+
models.sort((a, b) => (asNumber(b.cost) || 0) - (asNumber(a.cost) || 0));
|
|
434
|
+
const nameWidth = Math.max(...models.map(m => String(m.model || '-').length));
|
|
435
|
+
const reqWidth = Math.max(...models.map(m => formatNumber(m.requests).length));
|
|
436
|
+
const tokWidth = Math.max(...models.map(m => formatCompactNumber(m.total_tokens).length));
|
|
437
|
+
const costWidth = Math.max(...models.map(m => formatCost(m.cost).length));
|
|
460
438
|
console.log('');
|
|
461
|
-
console.log('
|
|
462
|
-
for (const m of
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
];
|
|
469
|
-
console.log(` - ${parts.join(' | ')}`);
|
|
439
|
+
console.log('[模型用量]');
|
|
440
|
+
for (const m of models) {
|
|
441
|
+
const name = String(m.model || '-').padEnd(nameWidth);
|
|
442
|
+
const req = formatNumber(m.requests).padStart(reqWidth);
|
|
443
|
+
const tok = formatCompactNumber(m.total_tokens).padStart(tokWidth);
|
|
444
|
+
const cost = formatCost(m.cost).padStart(costWidth);
|
|
445
|
+
console.log(` ${name} | ${req} 次 | ${tok} | ${cost}`);
|
|
470
446
|
}
|
|
471
447
|
}
|
|
472
448
|
|
|
@@ -554,11 +530,10 @@ async function usageCommand(args = []) {
|
|
|
554
530
|
if (service.configs.length === 0) continue;
|
|
555
531
|
|
|
556
532
|
hasAnyConfig = true;
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
console.log('');
|
|
533
|
+
if (!outputJson) {
|
|
534
|
+
console.log('');
|
|
535
|
+
console.log(`════════ ${service.name} 用量 ════════`);
|
|
536
|
+
}
|
|
562
537
|
|
|
563
538
|
allResults[service.name] = [];
|
|
564
539
|
|
|
@@ -569,9 +544,9 @@ async function usageCommand(args = []) {
|
|
|
569
544
|
const requests = configsToUse.map(config => {
|
|
570
545
|
const effectiveKey = overrideKey || config.authToken;
|
|
571
546
|
const sourceLabel = overrideKey ? `命令行 Key (${config.source})` : config.source;
|
|
572
|
-
console.log(`[查询中] ${sourceLabel} - Token: ${maskToken(effectiveKey)}`);
|
|
573
547
|
return {
|
|
574
548
|
sourceLabel,
|
|
549
|
+
tokenMask: maskToken(effectiveKey),
|
|
575
550
|
promise: queryUnifiedUsage(config.baseUrl, effectiveKey)
|
|
576
551
|
};
|
|
577
552
|
});
|
|
@@ -580,21 +555,25 @@ async function usageCommand(args = []) {
|
|
|
580
555
|
|
|
581
556
|
for (let i = 0; i < results.length; i += 1) {
|
|
582
557
|
const outcome = results[i];
|
|
583
|
-
const sourceLabel = requests[i]
|
|
558
|
+
const { sourceLabel, tokenMask } = requests[i];
|
|
584
559
|
const result = outcome.status === 'fulfilled'
|
|
585
560
|
? outcome.value
|
|
586
561
|
: { error: outcome.reason ? outcome.reason.message : '查询失败' };
|
|
587
562
|
|
|
588
563
|
if (result.error) {
|
|
589
|
-
|
|
590
|
-
|
|
564
|
+
if (!outputJson) {
|
|
565
|
+
console.log('');
|
|
566
|
+
console.log(`来源: ${sourceLabel} · ${tokenMask}`);
|
|
567
|
+
console.log(`[错误] ${result.error}`);
|
|
568
|
+
}
|
|
591
569
|
allResults[service.name].push({
|
|
592
570
|
source: sourceLabel,
|
|
593
571
|
error: result.error
|
|
594
572
|
});
|
|
595
573
|
} else {
|
|
596
574
|
if (!outputJson) {
|
|
597
|
-
|
|
575
|
+
console.log('');
|
|
576
|
+
displayUnifiedUsageStats(result.stats, result.origin, sourceLabel, tokenMask);
|
|
598
577
|
}
|
|
599
578
|
allResults[service.name].push({
|
|
600
579
|
source: sourceLabel,
|
package/lib/url.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// 剥离 base_url 末尾的 /api 或 /openai 后缀
|
|
2
|
+
// - Claude 历史 base_url 形如 https://xxx/api
|
|
3
|
+
// - Codex 历史 base_url 形如 https://xxx/openai
|
|
4
|
+
// - 新版后端统一不需要这些后缀
|
|
5
|
+
function stripBaseSuffix(baseUrl) {
|
|
6
|
+
if (!baseUrl) return baseUrl;
|
|
7
|
+
|
|
8
|
+
const trimSuffix = (pathname) => {
|
|
9
|
+
let p = pathname.replace(/\/+$/, '');
|
|
10
|
+
const lower = p.toLowerCase();
|
|
11
|
+
if (lower.endsWith('/openai')) {
|
|
12
|
+
p = p.slice(0, -'/openai'.length);
|
|
13
|
+
} else if (lower.endsWith('/api')) {
|
|
14
|
+
p = p.slice(0, -'/api'.length);
|
|
15
|
+
}
|
|
16
|
+
return p;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(baseUrl);
|
|
21
|
+
url.pathname = trimSuffix(url.pathname);
|
|
22
|
+
// URL 对象在 pathname 为空时会输出 "/",去掉末尾斜杠保持简洁
|
|
23
|
+
const result = url.toString().replace(/\/+$/, '');
|
|
24
|
+
return result;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return trimSuffix(String(baseUrl).replace(/\/+$/, ''));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = {
|
|
31
|
+
stripBaseSuffix,
|
|
32
|
+
};
|
package/package.json
CHANGED
package/services/claude.js
CHANGED
|
@@ -10,8 +10,7 @@ const settingsPath = path.join(configDir, 'settings.json');
|
|
|
10
10
|
module.exports = {
|
|
11
11
|
name: 'claude',
|
|
12
12
|
displayName: 'Claude Code',
|
|
13
|
-
defaultApiUrl: 'https://code.aihezu.top
|
|
14
|
-
apiSuffix: '/api',
|
|
13
|
+
defaultApiUrl: 'https://code.aihezu.top',
|
|
15
14
|
|
|
16
15
|
// Cache cleaning configuration
|
|
17
16
|
cacheConfig: {
|
package/services/codex.js
CHANGED
|
@@ -11,8 +11,7 @@ const authPath = path.join(configDir, 'auth.json');
|
|
|
11
11
|
module.exports = {
|
|
12
12
|
name: 'codex',
|
|
13
13
|
displayName: 'Codex',
|
|
14
|
-
defaultApiUrl: 'https://cc.aihezu.dev
|
|
15
|
-
apiSuffix: '/openai',
|
|
14
|
+
defaultApiUrl: 'https://cc.aihezu.dev',
|
|
16
15
|
|
|
17
16
|
// Cache cleaning configuration (minimal for Codex usually)
|
|
18
17
|
cacheConfig: {
|