yymaxapi 1.0.32 → 1.0.34
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/yymaxapi.js +201 -56
- package/package.json +1 -1
package/bin/yymaxapi.js
CHANGED
|
@@ -640,6 +640,10 @@ function writeCodexConfig(baseUrl, apiKey) {
|
|
|
640
640
|
const configPath = path.join(codexDir, 'config.toml');
|
|
641
641
|
const marker = '# >>> maxapi codex >>>';
|
|
642
642
|
const markerEnd = '# <<< maxapi codex <<<';
|
|
643
|
+
const providerKey = 'openclaw-relay';
|
|
644
|
+
// 确保 base_url 以 /v1 结尾(Codex CLI 要求)
|
|
645
|
+
let normalizedUrl = baseUrl.replace(/\/+$/, '');
|
|
646
|
+
if (!normalizedUrl.endsWith('/v1')) normalizedUrl += '/v1';
|
|
643
647
|
try {
|
|
644
648
|
let existing = '';
|
|
645
649
|
if (fs.existsSync(configPath)) {
|
|
@@ -650,12 +654,14 @@ function writeCodexConfig(baseUrl, apiKey) {
|
|
|
650
654
|
}
|
|
651
655
|
const section = [
|
|
652
656
|
marker,
|
|
653
|
-
`model = "
|
|
654
|
-
`
|
|
657
|
+
`model = "gpt-5.3-codex"`,
|
|
658
|
+
`model_provider = "${providerKey}"`,
|
|
655
659
|
``,
|
|
656
|
-
`[
|
|
657
|
-
`
|
|
658
|
-
`base_url = "${
|
|
660
|
+
`[model_providers.${providerKey}]`,
|
|
661
|
+
`name = "OpenClaw Relay"`,
|
|
662
|
+
`base_url = "${normalizedUrl}"`,
|
|
663
|
+
`wire_api = "responses"`,
|
|
664
|
+
`env_key = "OPENAI_API_KEY"`,
|
|
659
665
|
markerEnd
|
|
660
666
|
].join('\n');
|
|
661
667
|
const content = existing ? `${existing}\n\n${section}\n` : `${section}\n`;
|
|
@@ -2507,60 +2513,207 @@ async function presetCodex(paths, args = {}) {
|
|
|
2507
2513
|
}
|
|
2508
2514
|
}
|
|
2509
2515
|
|
|
2510
|
-
// ============
|
|
2511
|
-
async function autoActivate(paths) {
|
|
2512
|
-
console.log(chalk.cyan.bold('\n🚀
|
|
2516
|
+
// ============ 一键激活(同时配置 Claude Code + Codex) ============
|
|
2517
|
+
async function autoActivate(paths, args = {}) {
|
|
2518
|
+
console.log(chalk.cyan.bold('\n🚀 一键激活(同时配置 Claude Code + Codex)\n'));
|
|
2513
2519
|
|
|
2514
|
-
const
|
|
2515
|
-
|
|
2520
|
+
const claudeApiConfig = API_CONFIG.claude;
|
|
2521
|
+
const codexApiConfig = API_CONFIG.codex;
|
|
2522
|
+
const claudeProviderName = claudeApiConfig.providerName;
|
|
2523
|
+
const codexProviderName = codexApiConfig.providerName;
|
|
2516
2524
|
|
|
2517
|
-
//
|
|
2518
|
-
const
|
|
2519
|
-
|
|
2525
|
+
// ---- 测速选节点 ----
|
|
2526
|
+
const shouldTest = !(args['no-test'] || args.noTest);
|
|
2527
|
+
let selectedEndpoint = ENDPOINTS[0];
|
|
2520
2528
|
|
|
2521
|
-
|
|
2522
|
-
|
|
2529
|
+
if (shouldTest) {
|
|
2530
|
+
console.log(chalk.cyan('📡 开始测速节点...\n'));
|
|
2531
|
+
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
2523
2532
|
|
|
2533
|
+
const sorted = speedResult.ranked || [];
|
|
2534
|
+
if (sorted.length > 0) {
|
|
2535
|
+
const defaultEp = ENDPOINTS[0];
|
|
2536
|
+
const { selectedIndex } = await inquirer.prompt([{
|
|
2537
|
+
type: 'list',
|
|
2538
|
+
name: 'selectedIndex',
|
|
2539
|
+
message: '选择节点:',
|
|
2540
|
+
choices: [
|
|
2541
|
+
{ name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
|
|
2542
|
+
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
2543
|
+
...sorted.map((e, i) => ({
|
|
2544
|
+
name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
|
|
2545
|
+
value: i
|
|
2546
|
+
}))
|
|
2547
|
+
]
|
|
2548
|
+
}]);
|
|
2549
|
+
selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
|
|
2550
|
+
if (speedResult.usedFallback) {
|
|
2551
|
+
console.log(chalk.yellow(`\n⚠ 当前使用备用节点\n`));
|
|
2552
|
+
}
|
|
2553
|
+
} else {
|
|
2554
|
+
console.log(chalk.red('\n⚠️ 所有节点(含备用)均不可达'));
|
|
2555
|
+
const { proceed } = await inquirer.prompt([{
|
|
2556
|
+
type: 'confirm', name: 'proceed',
|
|
2557
|
+
message: '仍要写入默认节点配置吗?', default: false
|
|
2558
|
+
}]);
|
|
2559
|
+
if (!proceed) { console.log(chalk.gray('已取消')); return; }
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
// ---- API Key ----
|
|
2564
|
+
const apiKeyEnvFallbacks = [
|
|
2565
|
+
'OPENCLAW_CLAUDE_KEY',
|
|
2566
|
+
'OPENCLAW_CODEX_KEY',
|
|
2567
|
+
'CLAUDE_API_KEY',
|
|
2568
|
+
'OPENAI_API_KEY',
|
|
2569
|
+
'OPENCLAW_API_KEY'
|
|
2570
|
+
];
|
|
2571
|
+
const directKey = (args['api-key'] || args.apiKey || args.key || '').toString().trim();
|
|
2572
|
+
let apiKey;
|
|
2573
|
+
if (directKey) {
|
|
2574
|
+
apiKey = directKey;
|
|
2575
|
+
} else {
|
|
2576
|
+
const envKey = getApiKeyFromArgs({}, apiKeyEnvFallbacks);
|
|
2577
|
+
apiKey = await promptApiKey('请输入 API Key(同时用于 Claude Code 和 Codex):', envKey || '');
|
|
2578
|
+
}
|
|
2579
|
+
if (!apiKey) { console.log(chalk.gray('已取消')); return; }
|
|
2580
|
+
|
|
2581
|
+
// ---- 验证 ----
|
|
2582
|
+
console.log('');
|
|
2583
|
+
const validation = await validateApiKey(selectedEndpoint.url, apiKey);
|
|
2524
2584
|
if (!validation.valid) {
|
|
2525
2585
|
const { continueAnyway } = await inquirer.prompt([{
|
|
2526
2586
|
type: 'confirm', name: 'continueAnyway',
|
|
2527
|
-
message: 'API Key
|
|
2587
|
+
message: 'API Key 验证失败,是否仍然继续写入配置?', default: false
|
|
2528
2588
|
}]);
|
|
2529
2589
|
if (!continueAnyway) { console.log(chalk.gray('已取消')); return; }
|
|
2530
2590
|
}
|
|
2531
2591
|
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
if (typeChoices.length === 0) { targetType = 'claude'; }
|
|
2551
|
-
else if (typeChoices.length === 1) { targetType = typeChoices[0].value; }
|
|
2552
|
-
else {
|
|
2553
|
-
const { picked } = await inquirer.prompt([{
|
|
2554
|
-
type: 'list', name: 'picked', message: '无法自动识别服务类型,请选择:', choices: typeChoices
|
|
2555
|
-
}]);
|
|
2556
|
-
targetType = picked;
|
|
2557
|
-
}
|
|
2592
|
+
// ---- 构建配置 ----
|
|
2593
|
+
const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
|
|
2594
|
+
|
|
2595
|
+
const existingProviders = Object.keys(config.models.providers || {});
|
|
2596
|
+
const toRemove = existingProviders.filter(n => n !== claudeProviderName && n !== codexProviderName);
|
|
2597
|
+
if (toRemove.length > 0 && !args.force) {
|
|
2598
|
+
const { overwrite } = await inquirer.prompt([{
|
|
2599
|
+
type: 'confirm',
|
|
2600
|
+
name: 'overwrite',
|
|
2601
|
+
message: `检测到已有中转配置: ${existingProviders.join(', ')},将替换为 ${claudeProviderName} + ${codexProviderName}。是否继续?`,
|
|
2602
|
+
default: false
|
|
2603
|
+
}]);
|
|
2604
|
+
if (!overwrite) { console.log(chalk.gray('已取消')); return; }
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
if (toRemove.length > 0) {
|
|
2608
|
+
pruneProvidersExcept(config, [claudeProviderName, codexProviderName]);
|
|
2609
|
+
pruneAuthProfilesExcept(paths.authProfiles, [claudeProviderName, codexProviderName]);
|
|
2558
2610
|
}
|
|
2559
2611
|
|
|
2560
|
-
|
|
2561
|
-
|
|
2612
|
+
// Claude 侧
|
|
2613
|
+
const claudeBaseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
|
|
2614
|
+
const claudeModelId = 'claude-opus-4-6';
|
|
2615
|
+
const claudeModel = CLAUDE_MODELS.find(m => m.id === claudeModelId) || { id: claudeModelId, name: 'Claude Opus 4.6' };
|
|
2616
|
+
const claudeModelKey = `${claudeProviderName}/${claudeModelId}`;
|
|
2617
|
+
|
|
2618
|
+
config.models.providers[claudeProviderName] = {
|
|
2619
|
+
baseUrl: claudeBaseUrl,
|
|
2620
|
+
auth: DEFAULT_AUTH_MODE,
|
|
2621
|
+
api: claudeApiConfig.api,
|
|
2622
|
+
headers: {},
|
|
2623
|
+
authHeader: false,
|
|
2624
|
+
apiKey: apiKey.trim(),
|
|
2625
|
+
models: [{
|
|
2626
|
+
id: claudeModel.id,
|
|
2627
|
+
name: claudeModel.name,
|
|
2628
|
+
contextWindow: claudeApiConfig.contextWindow,
|
|
2629
|
+
maxTokens: claudeApiConfig.maxTokens
|
|
2630
|
+
}]
|
|
2631
|
+
};
|
|
2632
|
+
config.auth.profiles[`${claudeProviderName}:default`] = { provider: claudeProviderName, mode: 'api_key' };
|
|
2633
|
+
config.agents.defaults.models[claudeModelKey] = { alias: claudeProviderName };
|
|
2634
|
+
|
|
2635
|
+
// Codex 侧
|
|
2636
|
+
const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
|
|
2637
|
+
const codexModelId = 'gpt-5.3-codex';
|
|
2638
|
+
const codexModel = CODEX_MODELS.find(m => m.id === codexModelId) || { id: codexModelId, name: 'GPT 5.3 Codex' };
|
|
2639
|
+
const codexModelKey = `${codexProviderName}/${codexModelId}`;
|
|
2640
|
+
|
|
2641
|
+
config.models.providers[codexProviderName] = {
|
|
2642
|
+
baseUrl: codexBaseUrl,
|
|
2643
|
+
auth: DEFAULT_AUTH_MODE,
|
|
2644
|
+
api: codexApiConfig.api,
|
|
2645
|
+
headers: {},
|
|
2646
|
+
authHeader: false,
|
|
2647
|
+
apiKey: apiKey.trim(),
|
|
2648
|
+
models: [{
|
|
2649
|
+
id: codexModel.id,
|
|
2650
|
+
name: codexModel.name,
|
|
2651
|
+
contextWindow: codexApiConfig.contextWindow,
|
|
2652
|
+
maxTokens: codexApiConfig.maxTokens
|
|
2653
|
+
}]
|
|
2654
|
+
};
|
|
2655
|
+
config.auth.profiles[`${codexProviderName}:default`] = { provider: codexProviderName, mode: 'api_key' };
|
|
2656
|
+
config.agents.defaults.models[codexModelKey] = { alias: codexProviderName };
|
|
2657
|
+
|
|
2658
|
+
// ---- 选择主力 ----
|
|
2659
|
+
let primaryType = 'codex';
|
|
2660
|
+
if (args.primary === 'claude') {
|
|
2661
|
+
primaryType = 'claude';
|
|
2662
|
+
} else if (args.primary === 'codex') {
|
|
2663
|
+
primaryType = 'codex';
|
|
2562
2664
|
} else {
|
|
2563
|
-
|
|
2665
|
+
const { picked } = await inquirer.prompt([{
|
|
2666
|
+
type: 'list',
|
|
2667
|
+
name: 'picked',
|
|
2668
|
+
message: '选择主力工具(默认启动哪个):',
|
|
2669
|
+
choices: [
|
|
2670
|
+
{ name: 'Codex (GPT)', value: 'codex' },
|
|
2671
|
+
{ name: 'Claude Code', value: 'claude' }
|
|
2672
|
+
]
|
|
2673
|
+
}]);
|
|
2674
|
+
primaryType = picked;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
const primaryModelKey = primaryType === 'claude' ? claudeModelKey : codexModelKey;
|
|
2678
|
+
config.agents.defaults.model.primary = primaryModelKey;
|
|
2679
|
+
const fallbackModelKey = primaryType === 'claude' ? codexModelKey : claudeModelKey;
|
|
2680
|
+
config.agents.defaults.model.fallbacks = [fallbackModelKey];
|
|
2681
|
+
|
|
2682
|
+
// ---- 写入配置 ----
|
|
2683
|
+
const writeSpinner = ora({ text: '正在写入配置...', spinner: 'dots' }).start();
|
|
2684
|
+
createTimestampedBackup(paths.openclawConfig, paths.configDir, 'unified');
|
|
2685
|
+
ensureGatewaySettings(config);
|
|
2686
|
+
writeConfigWithSync(paths, config);
|
|
2687
|
+
updateAuthProfiles(paths.authProfiles, claudeProviderName, apiKey);
|
|
2688
|
+
updateAuthProfiles(paths.authProfiles, codexProviderName, apiKey);
|
|
2689
|
+
const extSynced = [];
|
|
2690
|
+
try { syncExternalTools('claude', claudeBaseUrl, apiKey); extSynced.push('Claude Code settings'); } catch { /* ignore */ }
|
|
2691
|
+
try { syncExternalTools('codex', codexBaseUrl, apiKey); extSynced.push('Codex CLI config'); } catch { /* ignore */ }
|
|
2692
|
+
writeSpinner.succeed('配置写入完成');
|
|
2693
|
+
|
|
2694
|
+
// ---- 输出结果 ----
|
|
2695
|
+
const primaryTag = primaryType === 'claude' ? ' (主)' : '';
|
|
2696
|
+
const codexTag = primaryType === 'codex' ? ' (主)' : '';
|
|
2697
|
+
console.log(chalk.green('\n✅ 一键激活完成!'));
|
|
2698
|
+
console.log(chalk.cyan(` Claude Code${primaryTag}: ${claudeBaseUrl}`));
|
|
2699
|
+
console.log(chalk.gray(` 模型: ${claudeModel.name}`));
|
|
2700
|
+
console.log(chalk.cyan(` Codex${codexTag}: ${codexBaseUrl}`));
|
|
2701
|
+
console.log(chalk.gray(` 模型: ${codexModel.name}`));
|
|
2702
|
+
console.log(chalk.gray(' API Key: 已设置'));
|
|
2703
|
+
if (extSynced.length > 0) console.log(chalk.gray(` 同步: ${extSynced.join(', ')}`));
|
|
2704
|
+
|
|
2705
|
+
// ---- 测试连接 ----
|
|
2706
|
+
const shouldTestGateway = args.test !== undefined
|
|
2707
|
+
? !['false', '0', 'no'].includes(String(args.test).toLowerCase())
|
|
2708
|
+
: await inquirer.prompt([{
|
|
2709
|
+
type: 'confirm',
|
|
2710
|
+
name: 'testGateway',
|
|
2711
|
+
message: '是否立即通过 OpenClaw Gateway 测试?',
|
|
2712
|
+
default: true
|
|
2713
|
+
}]).then(r => r.testGateway);
|
|
2714
|
+
|
|
2715
|
+
if (shouldTestGateway) {
|
|
2716
|
+
await testConnection(paths, args);
|
|
2564
2717
|
}
|
|
2565
2718
|
}
|
|
2566
2719
|
|
|
@@ -2583,11 +2736,11 @@ async function main() {
|
|
|
2583
2736
|
return;
|
|
2584
2737
|
}
|
|
2585
2738
|
if (args.preset === 'claude' || args._.includes('preset-claude') || args._.includes('claude-preset')) {
|
|
2586
|
-
await
|
|
2739
|
+
await autoActivate(paths, { ...args, primary: 'claude' });
|
|
2587
2740
|
return;
|
|
2588
2741
|
}
|
|
2589
2742
|
if (args.preset === 'codex' || args._.includes('preset-codex') || args._.includes('codex-preset')) {
|
|
2590
|
-
await
|
|
2743
|
+
await autoActivate(paths, { ...args, primary: 'codex' });
|
|
2591
2744
|
return;
|
|
2592
2745
|
}
|
|
2593
2746
|
if (args._.includes('test')) {
|
|
@@ -2612,9 +2765,7 @@ async function main() {
|
|
|
2612
2765
|
loop: false,
|
|
2613
2766
|
choices: [
|
|
2614
2767
|
new inquirer.Separator(' -- 配置模型 --'),
|
|
2615
|
-
{ name: '
|
|
2616
|
-
{ name: ' 激活 Claude', value: 'activate_claude' },
|
|
2617
|
-
{ name: ' 激活 Codex (GPT)', value: 'activate_codex' },
|
|
2768
|
+
{ name: ' 一键激活', value: 'auto_activate' },
|
|
2618
2769
|
new inquirer.Separator(' -- 工具 --'),
|
|
2619
2770
|
{ name: ' 测试连接', value: 'test_connection' },
|
|
2620
2771
|
{ name: ' 查看配置', value: 'view_config' },
|
|
@@ -2638,12 +2789,6 @@ async function main() {
|
|
|
2638
2789
|
case 'auto_activate':
|
|
2639
2790
|
await autoActivate(paths);
|
|
2640
2791
|
break;
|
|
2641
|
-
case 'activate_claude':
|
|
2642
|
-
await presetClaude(paths, {});
|
|
2643
|
-
break;
|
|
2644
|
-
case 'activate_codex':
|
|
2645
|
-
await presetCodex(paths, {});
|
|
2646
|
-
break;
|
|
2647
2792
|
case 'test_connection':
|
|
2648
2793
|
await testConnection(paths, {});
|
|
2649
2794
|
break;
|