openclawapi 1.3.3 → 1.3.5
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/cli.js +199 -3
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -15,9 +15,7 @@ const ENDPOINTS = [
|
|
|
15
15
|
{ name: '国内节点2', url: 'https://yunyi.skem.cn' },
|
|
16
16
|
{ name: 'CF国外节点1', url: 'https://yunyi.cfd' },
|
|
17
17
|
{ name: 'CF国外节点2', url: 'https://cdn1.yunyi.cfd' },
|
|
18
|
-
{ name: 'CF国外节点3', url: 'https://cdn2.yunyi.cfd' }
|
|
19
|
-
{ name: '备用节点1', url: 'http://47.99.42.193' },
|
|
20
|
-
{ name: '备用节点2', url: 'http://47.97.100.10' }
|
|
18
|
+
{ name: 'CF国外节点3', url: 'https://cdn2.yunyi.cfd' }
|
|
21
19
|
];
|
|
22
20
|
|
|
23
21
|
// Claude 模型预设
|
|
@@ -191,6 +189,7 @@ async function main() {
|
|
|
191
189
|
{ name: '🟢 选择 Codex 节点', value: 'select_codex' },
|
|
192
190
|
{ name: '⚡ 激活 Claude', value: 'activate_claude' },
|
|
193
191
|
{ name: '⚡ 激活 Codex', value: 'activate_codex' },
|
|
192
|
+
{ name: '🧪 测试连接', value: 'test_connection' },
|
|
194
193
|
{ name: '📋 查看当前配置', value: 'view_config' },
|
|
195
194
|
{ name: '🔄 恢复默认配置', value: 'restore' },
|
|
196
195
|
{ name: '❌ 退出', value: 'exit' }
|
|
@@ -213,6 +212,9 @@ async function main() {
|
|
|
213
212
|
case 'activate_codex':
|
|
214
213
|
await activate(paths, 'codex');
|
|
215
214
|
break;
|
|
215
|
+
case 'test_connection':
|
|
216
|
+
await testConnection(paths);
|
|
217
|
+
break;
|
|
216
218
|
case 'view_config':
|
|
217
219
|
await viewConfig(paths);
|
|
218
220
|
break;
|
|
@@ -377,6 +379,200 @@ async function activate(paths, type) {
|
|
|
377
379
|
console.log(chalk.gray(` API Key: 已设置`));
|
|
378
380
|
}
|
|
379
381
|
|
|
382
|
+
// ============ 测试连接 ============
|
|
383
|
+
async function testConnection(paths) {
|
|
384
|
+
console.log(chalk.cyan('🧪 测试 API 连接\n'));
|
|
385
|
+
|
|
386
|
+
const config = readConfig(paths.openclawConfig);
|
|
387
|
+
|
|
388
|
+
if (!config) {
|
|
389
|
+
console.log(chalk.yellow('配置文件不存在,请先选择节点'));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// 检查当前激活的是哪个
|
|
394
|
+
const primary = config.agents?.defaults?.model?.primary || '';
|
|
395
|
+
const isClaudeActive = primary.startsWith('yunyi-claude');
|
|
396
|
+
const isCodexActive = primary.startsWith('yunyi-codex');
|
|
397
|
+
|
|
398
|
+
if (!isClaudeActive && !isCodexActive) {
|
|
399
|
+
console.log(chalk.yellow('⚠️ 请先激活 Claude 或 Codex'));
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const type = isClaudeActive ? 'claude' : 'codex';
|
|
404
|
+
const typeLabel = isClaudeActive ? 'Claude' : 'Codex';
|
|
405
|
+
const apiConfig = API_CONFIG[type];
|
|
406
|
+
const provider = config.models?.providers?.[apiConfig.providerName];
|
|
407
|
+
|
|
408
|
+
if (!provider) {
|
|
409
|
+
console.log(chalk.yellow(`⚠️ ${typeLabel} 节点未配置`));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (!provider.apiKey) {
|
|
414
|
+
console.log(chalk.yellow(`⚠️ ${typeLabel} API Key 未设置`));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
console.log(chalk.gray(`测试 ${typeLabel} API...`));
|
|
419
|
+
console.log(chalk.gray(`节点: ${provider.baseUrl}`));
|
|
420
|
+
console.log(chalk.gray(`模型: ${provider.models?.[0]?.id || 'N/A'}\n`));
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
const startTime = Date.now();
|
|
424
|
+
|
|
425
|
+
if (type === 'claude') {
|
|
426
|
+
// Claude API 测试
|
|
427
|
+
const result = await testClaudeApi(provider.baseUrl, provider.apiKey, provider.models?.[0]?.id);
|
|
428
|
+
const latency = Date.now() - startTime;
|
|
429
|
+
|
|
430
|
+
if (result.success) {
|
|
431
|
+
console.log(chalk.green(`✅ Claude API 连接成功!`));
|
|
432
|
+
console.log(chalk.cyan(` 响应时间: ${latency}ms`));
|
|
433
|
+
console.log(chalk.gray(` 回复: ${result.message}`));
|
|
434
|
+
} else {
|
|
435
|
+
console.log(chalk.red(`❌ Claude API 连接失败`));
|
|
436
|
+
console.log(chalk.red(` 错误: ${result.error}`));
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
// Codex API 测试
|
|
440
|
+
const result = await testCodexApi(provider.baseUrl, provider.apiKey, provider.models?.[0]?.id);
|
|
441
|
+
const latency = Date.now() - startTime;
|
|
442
|
+
|
|
443
|
+
if (result.success) {
|
|
444
|
+
console.log(chalk.green(`✅ Codex API 连接成功!`));
|
|
445
|
+
console.log(chalk.cyan(` 响应时间: ${latency}ms`));
|
|
446
|
+
console.log(chalk.gray(` 回复: ${result.message}`));
|
|
447
|
+
} else {
|
|
448
|
+
console.log(chalk.red(`❌ Codex API 连接失败`));
|
|
449
|
+
console.log(chalk.red(` 错误: ${result.error}`));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
} catch (error) {
|
|
453
|
+
console.log(chalk.red(`❌ 测试失败: ${error.message}`));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Claude API 测试
|
|
458
|
+
function testClaudeApi(baseUrl, apiKey, model) {
|
|
459
|
+
return new Promise((resolve) => {
|
|
460
|
+
const urlObj = new URL(baseUrl);
|
|
461
|
+
const protocol = urlObj.protocol === 'https:' ? https : http;
|
|
462
|
+
|
|
463
|
+
const postData = JSON.stringify({
|
|
464
|
+
model: model || 'claude-sonnet-4-5',
|
|
465
|
+
max_tokens: 50,
|
|
466
|
+
messages: [{ role: 'user', content: 'Hi, reply with "OK" only.' }]
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const options = {
|
|
470
|
+
hostname: urlObj.hostname,
|
|
471
|
+
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
472
|
+
path: urlObj.pathname,
|
|
473
|
+
method: 'POST',
|
|
474
|
+
timeout: 30000,
|
|
475
|
+
rejectUnauthorized: false,
|
|
476
|
+
headers: {
|
|
477
|
+
'Content-Type': 'application/json',
|
|
478
|
+
'x-api-key': apiKey,
|
|
479
|
+
'anthropic-version': '2023-06-01',
|
|
480
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const req = protocol.request(options, (res) => {
|
|
485
|
+
let data = '';
|
|
486
|
+
res.on('data', chunk => data += chunk);
|
|
487
|
+
res.on('end', () => {
|
|
488
|
+
try {
|
|
489
|
+
const json = JSON.parse(data);
|
|
490
|
+
if (json.content && json.content[0]) {
|
|
491
|
+
resolve({ success: true, message: json.content[0].text?.substring(0, 100) || 'OK' });
|
|
492
|
+
} else if (json.error) {
|
|
493
|
+
resolve({ success: false, error: json.error.message || JSON.stringify(json.error) });
|
|
494
|
+
} else {
|
|
495
|
+
resolve({ success: false, error: `HTTP ${res.statusCode}: ${data.substring(0, 200)}` });
|
|
496
|
+
}
|
|
497
|
+
} catch {
|
|
498
|
+
resolve({ success: false, error: `HTTP ${res.statusCode}: ${data.substring(0, 200)}` });
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
req.on('timeout', () => {
|
|
504
|
+
req.destroy();
|
|
505
|
+
resolve({ success: false, error: '请求超时 (30s)' });
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
req.on('error', (e) => {
|
|
509
|
+
resolve({ success: false, error: e.message });
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
req.write(postData);
|
|
513
|
+
req.end();
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Codex API 测试
|
|
518
|
+
function testCodexApi(baseUrl, apiKey, model) {
|
|
519
|
+
return new Promise((resolve) => {
|
|
520
|
+
const urlObj = new URL(baseUrl);
|
|
521
|
+
const protocol = urlObj.protocol === 'https:' ? https : http;
|
|
522
|
+
|
|
523
|
+
const postData = JSON.stringify({
|
|
524
|
+
model: model || 'gpt-5.2',
|
|
525
|
+
input: 'Hi, reply with "OK" only.'
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const options = {
|
|
529
|
+
hostname: urlObj.hostname,
|
|
530
|
+
port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
531
|
+
path: urlObj.pathname,
|
|
532
|
+
method: 'POST',
|
|
533
|
+
timeout: 30000,
|
|
534
|
+
rejectUnauthorized: false,
|
|
535
|
+
headers: {
|
|
536
|
+
'Content-Type': 'application/json',
|
|
537
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
538
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const req = protocol.request(options, (res) => {
|
|
543
|
+
let data = '';
|
|
544
|
+
res.on('data', chunk => data += chunk);
|
|
545
|
+
res.on('end', () => {
|
|
546
|
+
try {
|
|
547
|
+
const json = JSON.parse(data);
|
|
548
|
+
if (json.output && json.output[0]) {
|
|
549
|
+
const text = json.output[0].content?.[0]?.text || json.output[0].text || 'OK';
|
|
550
|
+
resolve({ success: true, message: text.substring(0, 100) });
|
|
551
|
+
} else if (json.error) {
|
|
552
|
+
resolve({ success: false, error: json.error.message || JSON.stringify(json.error) });
|
|
553
|
+
} else {
|
|
554
|
+
resolve({ success: false, error: `HTTP ${res.statusCode}: ${data.substring(0, 200)}` });
|
|
555
|
+
}
|
|
556
|
+
} catch {
|
|
557
|
+
resolve({ success: false, error: `HTTP ${res.statusCode}: ${data.substring(0, 200)}` });
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
req.on('timeout', () => {
|
|
563
|
+
req.destroy();
|
|
564
|
+
resolve({ success: false, error: '请求超时 (30s)' });
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
req.on('error', (e) => {
|
|
568
|
+
resolve({ success: false, error: e.message });
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
req.write(postData);
|
|
572
|
+
req.end();
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
380
576
|
// ============ 查看配置 ============
|
|
381
577
|
async function viewConfig(paths) {
|
|
382
578
|
console.log(chalk.cyan('📋 当前配置\n'));
|