aihezu 2.8.7 → 2.8.9
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/bin/aihezu.js +0 -1
- package/commands/config.js +3 -2
- package/commands/install.js +3 -2
- package/commands/usage.js +175 -75
- package/docs/plans/2026-01-30-cross-platform-compatibility-design.md +2 -2
- package/lib/http.js +57 -12
- package/package.json +1 -1
- package/services/claude.js +1 -1
package/bin/aihezu.js
CHANGED
|
@@ -40,7 +40,6 @@ function showHelp() {
|
|
|
40
40
|
console.log(' aihezu usage 显示所有已配置服务的用量');
|
|
41
41
|
console.log(' aihezu usage cc 只显示 Claude Code 用量');
|
|
42
42
|
console.log(' aihezu usage codex 只显示 Codex 用量');
|
|
43
|
-
console.log(' aihezu usage gemini 只显示 Google Gemini 用量');
|
|
44
43
|
console.log(' aihezu usage --key sk-xxx 使用指定 API Key 查询');
|
|
45
44
|
console.log('\n服务:');
|
|
46
45
|
console.log(' claude Claude Code');
|
package/commands/config.js
CHANGED
|
@@ -66,8 +66,9 @@ async function configCommand(service, args = []) {
|
|
|
66
66
|
const urlObj = new URL(apiUrl);
|
|
67
67
|
const hostname = urlObj.hostname;
|
|
68
68
|
|
|
69
|
-
// Only add suffix for aihezu.dev and
|
|
70
|
-
if (hostname === 'aihezu.dev' || hostname.endsWith('.aihezu.dev')
|
|
69
|
+
// Only add suffix for aihezu.dev/aihezu.top and their subdomains
|
|
70
|
+
if (hostname === 'aihezu.dev' || hostname.endsWith('.aihezu.dev') ||
|
|
71
|
+
hostname === 'aihezu.top' || hostname.endsWith('.aihezu.top')) {
|
|
71
72
|
const normalizedSuffix = suffix.startsWith('/') ? suffix : `/${suffix}`;
|
|
72
73
|
const trimmedUrl = apiUrl.replace(/\/+$/, '');
|
|
73
74
|
if (!trimmedUrl.endsWith(normalizedSuffix)) {
|
package/commands/install.js
CHANGED
|
@@ -67,8 +67,9 @@ async function installCommand(service, args = []) {
|
|
|
67
67
|
const urlObj = new URL(apiUrl);
|
|
68
68
|
const hostname = urlObj.hostname;
|
|
69
69
|
|
|
70
|
-
// Only add suffix for aihezu.dev and
|
|
71
|
-
if (hostname === 'aihezu.dev' || hostname.endsWith('.aihezu.dev')
|
|
70
|
+
// Only add suffix for aihezu.dev/aihezu.top and their subdomains
|
|
71
|
+
if (hostname === 'aihezu.dev' || hostname.endsWith('.aihezu.dev') ||
|
|
72
|
+
hostname === 'aihezu.top' || hostname.endsWith('.aihezu.top')) {
|
|
72
73
|
const normalizedSuffix = suffix.startsWith('/') ? suffix : `/${suffix}`;
|
|
73
74
|
const trimmedUrl = apiUrl.replace(/\/+$/, '');
|
|
74
75
|
if (!trimmedUrl.endsWith(normalizedSuffix)) {
|
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 { postJson } = require('../lib/http');
|
|
4
|
+
const { postJson, getJson } = require('../lib/http');
|
|
5
5
|
const platform = require('../lib/platform');
|
|
6
6
|
|
|
7
7
|
function normalizeHttpUrl(url) {
|
|
@@ -189,66 +189,6 @@ function readCodexConfig() {
|
|
|
189
189
|
return configs;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
// 读取 Gemini 配置
|
|
193
|
-
function readGeminiConfig() {
|
|
194
|
-
const configs = [];
|
|
195
|
-
|
|
196
|
-
// 读取环境变量
|
|
197
|
-
const envBaseUrl = process.env.GOOGLE_GEMINI_BASE_URL || '';
|
|
198
|
-
const envAuthToken = process.env.GEMINI_API_KEY || '';
|
|
199
|
-
|
|
200
|
-
if (envBaseUrl && envAuthToken) {
|
|
201
|
-
configs.push({
|
|
202
|
-
source: '环境变量',
|
|
203
|
-
baseUrl: normalizeHttpUrl(envBaseUrl),
|
|
204
|
-
authToken: envAuthToken.trim()
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// 读取配置文件
|
|
209
|
-
const envFilePath = path.join(os.homedir(), '.gemini', '.env');
|
|
210
|
-
if (fs.existsSync(envFilePath)) {
|
|
211
|
-
try {
|
|
212
|
-
const content = fs.readFileSync(envFilePath, 'utf8');
|
|
213
|
-
const lines = content.split('\n');
|
|
214
|
-
|
|
215
|
-
let fileBaseUrl = '';
|
|
216
|
-
let fileAuthToken = '';
|
|
217
|
-
|
|
218
|
-
for (const line of lines) {
|
|
219
|
-
const match = line.match(/^(\w+)="?([^"]+)"?$/);
|
|
220
|
-
if (match) {
|
|
221
|
-
const [, key, value] = match;
|
|
222
|
-
if (key === 'GOOGLE_GEMINI_BASE_URL') {
|
|
223
|
-
fileBaseUrl = value;
|
|
224
|
-
} else if (key === 'GEMINI_API_KEY') {
|
|
225
|
-
fileAuthToken = value;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (fileBaseUrl && fileAuthToken) {
|
|
231
|
-
const isDuplicate = configs.some(c =>
|
|
232
|
-
c.baseUrl === normalizeHttpUrl(fileBaseUrl) &&
|
|
233
|
-
c.authToken === fileAuthToken.trim()
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
if (!isDuplicate) {
|
|
237
|
-
configs.push({
|
|
238
|
-
source: '配置文件',
|
|
239
|
-
baseUrl: normalizeHttpUrl(fileBaseUrl),
|
|
240
|
-
authToken: fileAuthToken.trim()
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
} catch (error) {
|
|
245
|
-
// 忽略错误
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return configs;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
192
|
function asNumber(value) {
|
|
253
193
|
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
254
194
|
if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {
|
|
@@ -355,6 +295,167 @@ function usageHint(current, limit) {
|
|
|
355
295
|
return '';
|
|
356
296
|
}
|
|
357
297
|
|
|
298
|
+
function getCodexUsageBase(baseUrl) {
|
|
299
|
+
// 用户的 Codex base_url 形如 https://xxx.aihezu.dev/openai 或 https://xxx.aihezu.dev
|
|
300
|
+
// 新 usage 接口位于 /v1/usage,需要去掉可能存在的 /openai 后缀
|
|
301
|
+
try {
|
|
302
|
+
const url = new URL(baseUrl);
|
|
303
|
+
let pathname = url.pathname.replace(/\/+$/, '');
|
|
304
|
+
if (pathname.toLowerCase().endsWith('/openai')) {
|
|
305
|
+
pathname = pathname.slice(0, -'/openai'.length);
|
|
306
|
+
}
|
|
307
|
+
return `${url.origin}${pathname}`;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
return baseUrl.replace(/\/+$/, '').replace(/\/openai$/i, '');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function queryCodexUsage(baseUrl, authToken) {
|
|
314
|
+
try {
|
|
315
|
+
const apiBase = getCodexUsageBase(baseUrl);
|
|
316
|
+
const usageUrl = `${apiBase}/v1/usage`;
|
|
317
|
+
const res = await getJson(usageUrl, {
|
|
318
|
+
headers: { Authorization: `Bearer ${authToken}` }
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
322
|
+
const msg = res.json && (res.json.message || res.json.error || res.json.code);
|
|
323
|
+
return { error: msg ? `HTTP ${res.statusCode} - ${msg}` : `HTTP ${res.statusCode}` };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!res.json || typeof res.json !== 'object') {
|
|
327
|
+
return { error: '返回内容不是合法的 JSON' };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { stats: res.json, origin: new URL(usageUrl).origin };
|
|
331
|
+
} catch (error) {
|
|
332
|
+
return { error: error.message };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function displayCodexUsageStats(stats, origin, source) {
|
|
337
|
+
const subscription = stats.subscription || {};
|
|
338
|
+
const today = (stats.usage && stats.usage.today) || {};
|
|
339
|
+
const total = (stats.usage && stats.usage.total) || {};
|
|
340
|
+
|
|
341
|
+
const dailyUsage = asNumber(subscription.daily_usage_usd);
|
|
342
|
+
const dailyLimit = asNumber(subscription.daily_limit_usd);
|
|
343
|
+
const weeklyUsage = asNumber(subscription.weekly_usage_usd);
|
|
344
|
+
const weeklyLimit = asNumber(subscription.weekly_limit_usd);
|
|
345
|
+
const monthlyUsage = asNumber(subscription.monthly_usage_usd);
|
|
346
|
+
const monthlyLimit = asNumber(subscription.monthly_limit_usd);
|
|
347
|
+
|
|
348
|
+
const unit = stats.unit || 'USD';
|
|
349
|
+
const planName = stats.planName || '-';
|
|
350
|
+
const mode = stats.mode || '-';
|
|
351
|
+
const isValid = stats.isValid === false ? '否' : '是';
|
|
352
|
+
|
|
353
|
+
console.log(`域名: ${origin}`);
|
|
354
|
+
console.log(`来源: ${source}`);
|
|
355
|
+
console.log(`套餐: ${planName} | 模式: ${mode} | 有效: ${isValid} | 单位: ${unit}`);
|
|
356
|
+
console.log('');
|
|
357
|
+
|
|
358
|
+
if (dailyUsage !== null || dailyLimit !== null) {
|
|
359
|
+
const usageText = formatCost(dailyUsage);
|
|
360
|
+
if (dailyLimit !== null && dailyLimit > 0) {
|
|
361
|
+
console.log(
|
|
362
|
+
`日额度: ${usageText} / ${formatCost(dailyLimit)} (${formatPercent(dailyUsage, dailyLimit)}) ` +
|
|
363
|
+
`${renderBar(dailyUsage, dailyLimit)} ${usageHint(dailyUsage, dailyLimit)}`.trimEnd()
|
|
364
|
+
);
|
|
365
|
+
const dailyRemaining = asNumber(stats.remaining);
|
|
366
|
+
if (dailyRemaining !== null) {
|
|
367
|
+
console.log(`日剩余: ${formatCost(dailyRemaining)}`);
|
|
368
|
+
} else if (dailyUsage !== null) {
|
|
369
|
+
console.log(`日剩余: ${formatCost(dailyLimit - dailyUsage)}`);
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
console.log(`日用量: ${usageText} (无限制)`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (weeklyUsage !== null && weeklyLimit !== null && weeklyLimit > 0) {
|
|
377
|
+
console.log(
|
|
378
|
+
`周额度: ${formatCost(weeklyUsage)} / ${formatCost(weeklyLimit)} (${formatPercent(weeklyUsage, weeklyLimit)}) ` +
|
|
379
|
+
`${renderBar(weeklyUsage, weeklyLimit)} ${usageHint(weeklyUsage, weeklyLimit)}`.trimEnd()
|
|
380
|
+
);
|
|
381
|
+
} else if (weeklyUsage !== null && weeklyUsage > 0) {
|
|
382
|
+
console.log(`周用量: ${formatCost(weeklyUsage)}`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (monthlyUsage !== null && monthlyLimit !== null && monthlyLimit > 0) {
|
|
386
|
+
console.log(
|
|
387
|
+
`月额度: ${formatCost(monthlyUsage)} / ${formatCost(monthlyLimit)} (${formatPercent(monthlyUsage, monthlyLimit)}) ` +
|
|
388
|
+
`${renderBar(monthlyUsage, monthlyLimit)} ${usageHint(monthlyUsage, monthlyLimit)}`.trimEnd()
|
|
389
|
+
);
|
|
390
|
+
} else if (monthlyUsage !== null && monthlyUsage > 0) {
|
|
391
|
+
console.log(`月用量: ${formatCost(monthlyUsage)}`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const expiresAt = subscription.expires_at;
|
|
395
|
+
if (expiresAt) {
|
|
396
|
+
const expiresMs = Date.parse(expiresAt);
|
|
397
|
+
if (!Number.isNaN(expiresMs)) {
|
|
398
|
+
console.log(`套餐到期: ${formatDateTime(expiresMs)}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (Object.keys(today).length > 0) {
|
|
403
|
+
console.log('');
|
|
404
|
+
console.log('今日:');
|
|
405
|
+
const todayParts = [];
|
|
406
|
+
if (asNumber(today.requests) !== null) todayParts.push(`请求 ${formatNumber(today.requests)} 次`);
|
|
407
|
+
if (asNumber(today.total_tokens) !== null) todayParts.push(`Token ${formatCompactNumber(today.total_tokens)}`);
|
|
408
|
+
if (asNumber(today.input_tokens) !== null) todayParts.push(`输入 ${formatCompactNumber(today.input_tokens)}`);
|
|
409
|
+
if (asNumber(today.output_tokens) !== null) todayParts.push(`输出 ${formatCompactNumber(today.output_tokens)}`);
|
|
410
|
+
if (asNumber(today.cache_read_tokens) !== null) todayParts.push(`缓存读 ${formatCompactNumber(today.cache_read_tokens)}`);
|
|
411
|
+
if (asNumber(today.cache_creation_tokens) !== null) todayParts.push(`缓存写 ${formatCompactNumber(today.cache_creation_tokens)}`);
|
|
412
|
+
if (todayParts.length) console.log(` ${todayParts.join(' | ')}`);
|
|
413
|
+
if (asNumber(today.cost) !== null) console.log(` 费用: ${formatCost(today.cost)}`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (Object.keys(total).length > 0 && total !== today) {
|
|
417
|
+
const totalRequests = asNumber(total.requests);
|
|
418
|
+
const totalCost = asNumber(total.cost);
|
|
419
|
+
if (totalRequests !== null || totalCost !== null) {
|
|
420
|
+
console.log('');
|
|
421
|
+
console.log('累计:');
|
|
422
|
+
const parts = [];
|
|
423
|
+
if (totalRequests !== null) parts.push(`请求 ${formatNumber(total.requests)} 次`);
|
|
424
|
+
if (asNumber(total.total_tokens) !== null) parts.push(`Token ${formatCompactNumber(total.total_tokens)}`);
|
|
425
|
+
if (totalCost !== null) parts.push(`费用 ${formatCost(total.cost)}`);
|
|
426
|
+
if (parts.length) console.log(` ${parts.join(' | ')}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const usage = stats.usage || {};
|
|
431
|
+
const rpm = asNumber(usage.rpm);
|
|
432
|
+
const tpm = asNumber(usage.tpm);
|
|
433
|
+
const avgDuration = asNumber(usage.average_duration_ms);
|
|
434
|
+
if (rpm !== null || tpm !== null || avgDuration !== null) {
|
|
435
|
+
const parts = [];
|
|
436
|
+
if (rpm !== null) parts.push(`RPM ${rpm}`);
|
|
437
|
+
if (tpm !== null) parts.push(`TPM ${tpm}`);
|
|
438
|
+
if (avgDuration !== null) parts.push(`平均耗时 ${avgDuration} ms`);
|
|
439
|
+
console.log(`实时: ${parts.join(' | ')}`);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (Array.isArray(stats.model_stats) && stats.model_stats.length > 0) {
|
|
443
|
+
console.log('');
|
|
444
|
+
console.log('模型用量:');
|
|
445
|
+
for (const m of stats.model_stats) {
|
|
446
|
+
const parts = [
|
|
447
|
+
`${m.model || '-'}`,
|
|
448
|
+
`请求 ${formatNumber(m.requests)} 次`,
|
|
449
|
+
`Token ${formatCompactNumber(m.total_tokens)}`,
|
|
450
|
+
`费用 ${formatCost(m.cost)}`
|
|
451
|
+
];
|
|
452
|
+
console.log(` - ${parts.join(' | ')}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
console.log('');
|
|
457
|
+
}
|
|
458
|
+
|
|
358
459
|
async function queryUsage(baseUrl, authToken) {
|
|
359
460
|
try {
|
|
360
461
|
const origin = new URL(baseUrl).origin;
|
|
@@ -494,7 +595,7 @@ function showUsageHelp() {
|
|
|
494
595
|
console.log('用法: aihezu usage [service] [--json] [--key <apiKey>]');
|
|
495
596
|
console.log('');
|
|
496
597
|
console.log('说明:');
|
|
497
|
-
console.log(' - 自动检测 Claude Code、Codex
|
|
598
|
+
console.log(' - 自动检测 Claude Code、Codex 的配置');
|
|
498
599
|
console.log(' - 从环境变量和配置文件读取配置');
|
|
499
600
|
console.log(' - 如果两者都存在且不同,将分别查询两个账号的用量');
|
|
500
601
|
console.log(' - 使用 --key 可查询指定 API Key 的用量');
|
|
@@ -502,7 +603,6 @@ function showUsageHelp() {
|
|
|
502
603
|
console.log('可选服务参数:');
|
|
503
604
|
console.log(' cc, claude 只显示 Claude Code 用量');
|
|
504
605
|
console.log(' codex 只显示 Codex 用量');
|
|
505
|
-
console.log(' gemini 只显示 Google Gemini 用量');
|
|
506
606
|
console.log(' (不指定) 显示所有已配置服务的用量');
|
|
507
607
|
console.log('');
|
|
508
608
|
console.log('可选参数:');
|
|
@@ -513,7 +613,6 @@ function showUsageHelp() {
|
|
|
513
613
|
console.log(' npx aihezu usage # 显示所有服务');
|
|
514
614
|
console.log(' npx aihezu usage cc # 只显示 Claude Code');
|
|
515
615
|
console.log(' npx aihezu usage codex # 只显示 Codex');
|
|
516
|
-
console.log(' npx aihezu usage gemini # 只显示 Gemini');
|
|
517
616
|
console.log(' npx aihezu usage --json # JSON 格式输出所有服务');
|
|
518
617
|
console.log(' npx aihezu usage cc --json # JSON 格式输出 Claude Code');
|
|
519
618
|
console.log(' npx aihezu usage --key sk-xxx # 使用指定 Key 查询');
|
|
@@ -537,8 +636,7 @@ async function usageCommand(args = []) {
|
|
|
537
636
|
const serviceAliases = {
|
|
538
637
|
'cc': 'Claude Code',
|
|
539
638
|
'claude': 'Claude Code',
|
|
540
|
-
'codex': 'Codex'
|
|
541
|
-
'gemini': 'Google Gemini'
|
|
639
|
+
'codex': 'Codex'
|
|
542
640
|
};
|
|
543
641
|
|
|
544
642
|
let filterServiceName = null;
|
|
@@ -549,7 +647,7 @@ async function usageCommand(args = []) {
|
|
|
549
647
|
if (!filterServiceName) {
|
|
550
648
|
console.error(`[错误] 未知的服务: ${serviceFilter}`);
|
|
551
649
|
console.error('');
|
|
552
|
-
console.error('可用服务: cc, claude, codex
|
|
650
|
+
console.error('可用服务: cc, claude, codex');
|
|
553
651
|
console.error('');
|
|
554
652
|
console.error('使用 "aihezu usage --help" 查看帮助');
|
|
555
653
|
process.exit(1);
|
|
@@ -559,8 +657,7 @@ async function usageCommand(args = []) {
|
|
|
559
657
|
// 收集所有服务的配置
|
|
560
658
|
const allServices = [
|
|
561
659
|
{ name: 'Claude Code', configs: readClaudeConfig() },
|
|
562
|
-
{ name: 'Codex', configs: readCodexConfig() }
|
|
563
|
-
{ name: 'Google Gemini', configs: readGeminiConfig() }
|
|
660
|
+
{ name: 'Codex', configs: readCodexConfig() }
|
|
564
661
|
];
|
|
565
662
|
|
|
566
663
|
// 根据过滤参数筛选服务
|
|
@@ -591,10 +688,10 @@ async function usageCommand(args = []) {
|
|
|
591
688
|
const effectiveKey = overrideKey || config.authToken;
|
|
592
689
|
const sourceLabel = overrideKey ? `命令行 Key (${config.source})` : config.source;
|
|
593
690
|
console.log(`[查询中] ${sourceLabel} - Token: ${maskToken(effectiveKey)}`);
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
};
|
|
691
|
+
const promise = service.name === 'Codex'
|
|
692
|
+
? queryCodexUsage(config.baseUrl, effectiveKey)
|
|
693
|
+
: queryUsage(config.baseUrl, effectiveKey);
|
|
694
|
+
return { sourceLabel, promise };
|
|
598
695
|
});
|
|
599
696
|
|
|
600
697
|
const results = await Promise.allSettled(requests.map(item => item.promise));
|
|
@@ -615,7 +712,11 @@ async function usageCommand(args = []) {
|
|
|
615
712
|
});
|
|
616
713
|
} else {
|
|
617
714
|
if (!outputJson) {
|
|
618
|
-
|
|
715
|
+
if (service.name === 'Codex') {
|
|
716
|
+
displayCodexUsageStats(result.stats, result.origin, sourceLabel);
|
|
717
|
+
} else {
|
|
718
|
+
displayUsageStats(result.stats, result.origin, sourceLabel);
|
|
719
|
+
}
|
|
619
720
|
}
|
|
620
721
|
allResults[service.name].push({
|
|
621
722
|
source: sourceLabel,
|
|
@@ -637,7 +738,6 @@ async function usageCommand(args = []) {
|
|
|
637
738
|
console.log('请先运行以下命令进行配置:');
|
|
638
739
|
console.log(' npx aihezu install claude # 配置 Claude Code');
|
|
639
740
|
console.log(' npx aihezu install codex # 配置 Codex');
|
|
640
|
-
console.log(' npx aihezu install gemini # 配置 Google Gemini');
|
|
641
741
|
console.log('');
|
|
642
742
|
console.log('或者手动设置环境变量:');
|
|
643
743
|
console.log('');
|
|
@@ -243,7 +243,7 @@ function getTroubleshootingGuide(errorType)
|
|
|
243
243
|
|
|
244
244
|
2. 永久设置(需要重启终端):
|
|
245
245
|
setx ANTHROPIC_AUTH_TOKEN "sk-xxx"
|
|
246
|
-
setx ANTHROPIC_BASE_URL "https://
|
|
246
|
+
setx ANTHROPIC_BASE_URL "https://code.aihezu.top/api"
|
|
247
247
|
|
|
248
248
|
3. 推荐:使用配置文件而非环境变量
|
|
249
249
|
npx aihezu config claude
|
|
@@ -629,7 +629,7 @@ function getEnvVarGuide(varName) {
|
|
|
629
629
|
'',
|
|
630
630
|
'方式 2: 永久设置(需要重启终端)',
|
|
631
631
|
` setx ${varName} "your-value"`,
|
|
632
|
-
` setx ANTHROPIC_BASE_URL "https://
|
|
632
|
+
` setx ANTHROPIC_BASE_URL "https://code.aihezu.top/api"`,
|
|
633
633
|
'',
|
|
634
634
|
'⚠️ 注意: setx 后必须重启 PowerShell/CMD 才能生效',
|
|
635
635
|
'',
|
package/lib/http.js
CHANGED
|
@@ -8,11 +8,22 @@ function sleep(ms) {
|
|
|
8
8
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function sendJsonRequest(method, urlString, body, timeoutMs) {
|
|
11
|
+
function sendJsonRequest(method, urlString, body, timeoutMs, extraHeaders = {}) {
|
|
12
12
|
const url = new URL(urlString);
|
|
13
|
-
const
|
|
13
|
+
const hasBody = body !== undefined && body !== null;
|
|
14
|
+
const payload = hasBody ? JSON.stringify(body) : '';
|
|
14
15
|
const transport = url.protocol === 'https:' ? https : http;
|
|
15
16
|
|
|
17
|
+
const headers = {
|
|
18
|
+
'Accept': 'application/json',
|
|
19
|
+
'User-Agent': 'aihezu-cli',
|
|
20
|
+
...extraHeaders
|
|
21
|
+
};
|
|
22
|
+
if (hasBody) {
|
|
23
|
+
headers['Content-Type'] = 'application/json';
|
|
24
|
+
headers['Content-Length'] = Buffer.byteLength(payload);
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
return new Promise((resolve, reject) => {
|
|
17
28
|
const req = transport.request(
|
|
18
29
|
{
|
|
@@ -20,12 +31,7 @@ function sendJsonRequest(method, urlString, body, timeoutMs) {
|
|
|
20
31
|
hostname: url.hostname,
|
|
21
32
|
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
22
33
|
path: `${url.pathname}${url.search || ''}`,
|
|
23
|
-
headers
|
|
24
|
-
'Content-Type': 'application/json',
|
|
25
|
-
'Accept': 'application/json',
|
|
26
|
-
'Content-Length': Buffer.byteLength(payload),
|
|
27
|
-
'User-Agent': 'aihezu-cli'
|
|
28
|
-
}
|
|
34
|
+
headers
|
|
29
35
|
},
|
|
30
36
|
res => {
|
|
31
37
|
res.setEncoding('utf8');
|
|
@@ -57,7 +63,9 @@ function sendJsonRequest(method, urlString, body, timeoutMs) {
|
|
|
57
63
|
req.destroy(new Error(`Request timed out after ${timeoutMs}ms`));
|
|
58
64
|
});
|
|
59
65
|
|
|
60
|
-
|
|
66
|
+
if (hasBody) {
|
|
67
|
+
req.write(payload);
|
|
68
|
+
}
|
|
61
69
|
req.end();
|
|
62
70
|
});
|
|
63
71
|
}
|
|
@@ -67,7 +75,43 @@ async function postJson(urlString, body, options = {}) {
|
|
|
67
75
|
timeoutMs = HTTP_TIMEOUT,
|
|
68
76
|
retryCount = HTTP_RETRY_COUNT,
|
|
69
77
|
retryDelayMs = HTTP_RETRY_DELAY,
|
|
70
|
-
retryStatusCodes = DEFAULT_RETRY_STATUS_CODES
|
|
78
|
+
retryStatusCodes = DEFAULT_RETRY_STATUS_CODES,
|
|
79
|
+
headers = {}
|
|
80
|
+
} = options;
|
|
81
|
+
|
|
82
|
+
const retryCodes = retryStatusCodes instanceof Set ? retryStatusCodes : new Set(retryStatusCodes);
|
|
83
|
+
let attempt = 0;
|
|
84
|
+
let lastError = null;
|
|
85
|
+
|
|
86
|
+
while (attempt <= retryCount) {
|
|
87
|
+
try {
|
|
88
|
+
const response = await sendJsonRequest('POST', urlString, body, timeoutMs, headers);
|
|
89
|
+
if (retryCodes.has(response.statusCode) && attempt < retryCount) {
|
|
90
|
+
await sleep(retryDelayMs * Math.pow(2, attempt));
|
|
91
|
+
attempt += 1;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
return response;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
lastError = error;
|
|
97
|
+
if (attempt >= retryCount) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
await sleep(retryDelayMs * Math.pow(2, attempt));
|
|
101
|
+
attempt += 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw lastError || new Error('Request failed');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function getJson(urlString, options = {}) {
|
|
109
|
+
const {
|
|
110
|
+
timeoutMs = HTTP_TIMEOUT,
|
|
111
|
+
retryCount = HTTP_RETRY_COUNT,
|
|
112
|
+
retryDelayMs = HTTP_RETRY_DELAY,
|
|
113
|
+
retryStatusCodes = DEFAULT_RETRY_STATUS_CODES,
|
|
114
|
+
headers = {}
|
|
71
115
|
} = options;
|
|
72
116
|
|
|
73
117
|
const retryCodes = retryStatusCodes instanceof Set ? retryStatusCodes : new Set(retryStatusCodes);
|
|
@@ -76,7 +120,7 @@ async function postJson(urlString, body, options = {}) {
|
|
|
76
120
|
|
|
77
121
|
while (attempt <= retryCount) {
|
|
78
122
|
try {
|
|
79
|
-
const response = await sendJsonRequest('
|
|
123
|
+
const response = await sendJsonRequest('GET', urlString, null, timeoutMs, headers);
|
|
80
124
|
if (retryCodes.has(response.statusCode) && attempt < retryCount) {
|
|
81
125
|
await sleep(retryDelayMs * Math.pow(2, attempt));
|
|
82
126
|
attempt += 1;
|
|
@@ -97,5 +141,6 @@ async function postJson(urlString, body, options = {}) {
|
|
|
97
141
|
}
|
|
98
142
|
|
|
99
143
|
module.exports = {
|
|
100
|
-
postJson
|
|
144
|
+
postJson,
|
|
145
|
+
getJson
|
|
101
146
|
};
|
package/package.json
CHANGED
package/services/claude.js
CHANGED
|
@@ -10,7 +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://
|
|
13
|
+
defaultApiUrl: 'https://code.aihezu.top/api',
|
|
14
14
|
apiSuffix: '/api',
|
|
15
15
|
|
|
16
16
|
// Cache cleaning configuration
|