openclawapi 1.3.4 → 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 +198 -0
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -189,6 +189,7 @@ async function main() {
|
|
|
189
189
|
{ name: '🟢 选择 Codex 节点', value: 'select_codex' },
|
|
190
190
|
{ name: '⚡ 激活 Claude', value: 'activate_claude' },
|
|
191
191
|
{ name: '⚡ 激活 Codex', value: 'activate_codex' },
|
|
192
|
+
{ name: '🧪 测试连接', value: 'test_connection' },
|
|
192
193
|
{ name: '📋 查看当前配置', value: 'view_config' },
|
|
193
194
|
{ name: '🔄 恢复默认配置', value: 'restore' },
|
|
194
195
|
{ name: '❌ 退出', value: 'exit' }
|
|
@@ -211,6 +212,9 @@ async function main() {
|
|
|
211
212
|
case 'activate_codex':
|
|
212
213
|
await activate(paths, 'codex');
|
|
213
214
|
break;
|
|
215
|
+
case 'test_connection':
|
|
216
|
+
await testConnection(paths);
|
|
217
|
+
break;
|
|
214
218
|
case 'view_config':
|
|
215
219
|
await viewConfig(paths);
|
|
216
220
|
break;
|
|
@@ -375,6 +379,200 @@ async function activate(paths, type) {
|
|
|
375
379
|
console.log(chalk.gray(` API Key: 已设置`));
|
|
376
380
|
}
|
|
377
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
|
+
|
|
378
576
|
// ============ 查看配置 ============
|
|
379
577
|
async function viewConfig(paths) {
|
|
380
578
|
console.log(chalk.cyan('📋 当前配置\n'));
|