yymaxapi 1.0.49 → 1.0.51
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 +118 -95
- package/package.json +1 -1
package/bin/yymaxapi.js
CHANGED
|
@@ -138,6 +138,8 @@ const DEFAULT_API_CONFIG = {
|
|
|
138
138
|
}
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
+
const SKIP_API_VALIDATION = false;
|
|
142
|
+
|
|
141
143
|
function normalizeEndpoints(raw, fallback) {
|
|
142
144
|
if (!Array.isArray(raw)) return fallback;
|
|
143
145
|
const normalized = raw
|
|
@@ -371,6 +373,9 @@ function httpGetJson(url, headers = {}, timeout = 10000) {
|
|
|
371
373
|
}
|
|
372
374
|
|
|
373
375
|
async function validateApiKey(nodeUrl, apiKey) {
|
|
376
|
+
if (SKIP_API_VALIDATION) {
|
|
377
|
+
return { valid: true, skipped: true };
|
|
378
|
+
}
|
|
374
379
|
const verifyUrl = `${nodeUrl.replace(/\/+$/, '')}/user/api/v1/me`;
|
|
375
380
|
const maxRetries = 3;
|
|
376
381
|
const spinner = ora({ text: '正在验证 API Key...', spinner: 'dots' }).start();
|
|
@@ -1177,8 +1182,23 @@ function coerceModelsRecord(value) {
|
|
|
1177
1182
|
return record;
|
|
1178
1183
|
}
|
|
1179
1184
|
|
|
1185
|
+
const OPENCLAW_KNOWN_ROOT_KEYS = new Set([
|
|
1186
|
+
'models', 'agents', 'auth', 'gateway', 'tools',
|
|
1187
|
+
'channels', 'mcpServers', 'globalShortcut', 'customModes',
|
|
1188
|
+
'permissions', 'proxy', 'env', 'hooks', 'storage',
|
|
1189
|
+
]);
|
|
1190
|
+
|
|
1191
|
+
function sanitizeRootKeys(config) {
|
|
1192
|
+
for (const key of Object.keys(config)) {
|
|
1193
|
+
if (!OPENCLAW_KNOWN_ROOT_KEYS.has(key)) {
|
|
1194
|
+
delete config[key];
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1180
1199
|
function ensureConfigStructure(config) {
|
|
1181
1200
|
const next = config || {};
|
|
1201
|
+
sanitizeRootKeys(next);
|
|
1182
1202
|
if (!next.models) next.models = {};
|
|
1183
1203
|
if (!next.models.providers) next.models.providers = {};
|
|
1184
1204
|
if (!next.agents) next.agents = {};
|
|
@@ -2661,25 +2681,54 @@ async function autoActivate(paths, args = {}) {
|
|
|
2661
2681
|
// ---- 构建配置 ----
|
|
2662
2682
|
const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
|
|
2663
2683
|
|
|
2664
|
-
// 增量写入: 只添加/更新云翼 provider,保留其他已有配置
|
|
2665
|
-
|
|
2666
|
-
// Claude 侧 — 让用户选模型
|
|
2667
2684
|
const claudeBaseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2685
|
+
const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
|
|
2686
|
+
|
|
2687
|
+
// 检测单一模式:Claude 和 Codex 只有一个模型且 id 相同(如 maxapi 的 heibai)
|
|
2688
|
+
const isSingleMode = CLAUDE_MODELS.length === 1 && CODEX_MODELS.length === 1
|
|
2689
|
+
&& CLAUDE_MODELS[0].id === CODEX_MODELS[0].id;
|
|
2690
|
+
|
|
2691
|
+
let claudeModelId, codexModelId;
|
|
2692
|
+
|
|
2693
|
+
if (isSingleMode) {
|
|
2694
|
+
// 单一模式:固定模型,不选择,不分 Claude/Codex
|
|
2695
|
+
claudeModelId = CLAUDE_MODELS[0].id;
|
|
2696
|
+
codexModelId = CODEX_MODELS[0].id;
|
|
2697
|
+
} else {
|
|
2698
|
+
// 多模型模式:让用户选模型
|
|
2699
|
+
claudeModelId = (args['claude-model'] || '').toString().trim();
|
|
2700
|
+
if (!claudeModelId && CLAUDE_MODELS.length > 1) {
|
|
2701
|
+
const { picked } = await inquirer.prompt([{
|
|
2702
|
+
type: 'list',
|
|
2703
|
+
name: 'picked',
|
|
2704
|
+
message: '选择 Claude 模型:',
|
|
2705
|
+
choices: CLAUDE_MODELS.map(m => ({ name: m.name, value: m.id })),
|
|
2706
|
+
default: CLAUDE_MODELS[0].id
|
|
2707
|
+
}]);
|
|
2708
|
+
claudeModelId = picked;
|
|
2709
|
+
}
|
|
2710
|
+
if (!claudeModelId) claudeModelId = CLAUDE_MODELS[0]?.id || 'claude-sonnet-4-6';
|
|
2711
|
+
|
|
2712
|
+
codexModelId = (args['codex-model'] || '').toString().trim();
|
|
2713
|
+
if (!codexModelId && CODEX_MODELS.length > 1) {
|
|
2714
|
+
const { pickedCodex } = await inquirer.prompt([{
|
|
2715
|
+
type: 'list',
|
|
2716
|
+
name: 'pickedCodex',
|
|
2717
|
+
message: '选择 Codex 模型:',
|
|
2718
|
+
choices: CODEX_MODELS.map(m => ({ name: m.name, value: m.id })),
|
|
2719
|
+
default: CODEX_MODELS[0].id
|
|
2720
|
+
}]);
|
|
2721
|
+
codexModelId = pickedCodex;
|
|
2722
|
+
}
|
|
2723
|
+
if (!codexModelId) codexModelId = CODEX_MODELS[0]?.id || 'gpt-5.3-codex';
|
|
2678
2724
|
}
|
|
2679
|
-
|
|
2725
|
+
|
|
2680
2726
|
const claudeModel = CLAUDE_MODELS.find(m => m.id === claudeModelId) || { id: claudeModelId, name: claudeModelId };
|
|
2727
|
+
const codexModel = CODEX_MODELS.find(m => m.id === codexModelId) || { id: codexModelId, name: codexModelId };
|
|
2681
2728
|
const claudeModelKey = `${claudeProviderName}/${claudeModelId}`;
|
|
2729
|
+
const codexModelKey = `${codexProviderName}/${codexModelId}`;
|
|
2682
2730
|
|
|
2731
|
+
// 写入 Claude provider
|
|
2683
2732
|
config.models.providers[claudeProviderName] = {
|
|
2684
2733
|
baseUrl: claudeBaseUrl,
|
|
2685
2734
|
auth: DEFAULT_AUTH_MODE,
|
|
@@ -2697,23 +2746,7 @@ async function autoActivate(paths, args = {}) {
|
|
|
2697
2746
|
config.auth.profiles[`${claudeProviderName}:default`] = { provider: claudeProviderName, mode: 'api_key' };
|
|
2698
2747
|
config.agents.defaults.models[claudeModelKey] = { alias: claudeProviderName };
|
|
2699
2748
|
|
|
2700
|
-
// Codex
|
|
2701
|
-
const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
|
|
2702
|
-
let codexModelId = (args['codex-model'] || '').toString().trim();
|
|
2703
|
-
if (!codexModelId && CODEX_MODELS.length > 1) {
|
|
2704
|
-
const { pickedCodex } = await inquirer.prompt([{
|
|
2705
|
-
type: 'list',
|
|
2706
|
-
name: 'pickedCodex',
|
|
2707
|
-
message: '选择 Codex 模型:',
|
|
2708
|
-
choices: CODEX_MODELS.map(m => ({ name: m.name, value: m.id })),
|
|
2709
|
-
default: CODEX_MODELS[0].id
|
|
2710
|
-
}]);
|
|
2711
|
-
codexModelId = pickedCodex;
|
|
2712
|
-
}
|
|
2713
|
-
if (!codexModelId) codexModelId = CODEX_MODELS[0]?.id || 'gpt-5.3-codex';
|
|
2714
|
-
const codexModel = CODEX_MODELS.find(m => m.id === codexModelId) || { id: codexModelId, name: codexModelId };
|
|
2715
|
-
const codexModelKey = `${codexProviderName}/${codexModelId}`;
|
|
2716
|
-
|
|
2749
|
+
// 写入 Codex provider(单一模式下仍写入,保证配置完整)
|
|
2717
2750
|
config.models.providers[codexProviderName] = {
|
|
2718
2751
|
baseUrl: codexBaseUrl,
|
|
2719
2752
|
auth: DEFAULT_AUTH_MODE,
|
|
@@ -2732,27 +2765,34 @@ async function autoActivate(paths, args = {}) {
|
|
|
2732
2765
|
config.agents.defaults.models[codexModelKey] = { alias: codexProviderName };
|
|
2733
2766
|
|
|
2734
2767
|
// ---- 选择主力 ----
|
|
2735
|
-
let
|
|
2736
|
-
if (
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2768
|
+
let primaryModelKey, fallbackModelKey;
|
|
2769
|
+
if (isSingleMode) {
|
|
2770
|
+
// 单一模式:直接用 Claude provider 作为 primary,不需要用户选
|
|
2771
|
+
primaryModelKey = claudeModelKey;
|
|
2772
|
+
fallbackModelKey = codexModelKey;
|
|
2740
2773
|
} else {
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2774
|
+
let primaryType = 'claude';
|
|
2775
|
+
if (args.primary === 'claude') {
|
|
2776
|
+
primaryType = 'claude';
|
|
2777
|
+
} else if (args.primary === 'codex') {
|
|
2778
|
+
primaryType = 'codex';
|
|
2779
|
+
} else {
|
|
2780
|
+
const { picked } = await inquirer.prompt([{
|
|
2781
|
+
type: 'list',
|
|
2782
|
+
name: 'picked',
|
|
2783
|
+
message: '选择主力工具(默认启动哪个):',
|
|
2784
|
+
choices: [
|
|
2785
|
+
{ name: 'Claude Code', value: 'claude' },
|
|
2786
|
+
{ name: 'Codex (GPT)', value: 'codex' }
|
|
2787
|
+
]
|
|
2788
|
+
}]);
|
|
2789
|
+
primaryType = picked;
|
|
2790
|
+
}
|
|
2791
|
+
primaryModelKey = primaryType === 'claude' ? claudeModelKey : codexModelKey;
|
|
2792
|
+
fallbackModelKey = primaryType === 'claude' ? codexModelKey : claudeModelKey;
|
|
2751
2793
|
}
|
|
2752
2794
|
|
|
2753
|
-
const primaryModelKey = primaryType === 'claude' ? claudeModelKey : codexModelKey;
|
|
2754
2795
|
config.agents.defaults.model.primary = primaryModelKey;
|
|
2755
|
-
const fallbackModelKey = primaryType === 'claude' ? codexModelKey : claudeModelKey;
|
|
2756
2796
|
config.agents.defaults.model.fallbacks = [fallbackModelKey];
|
|
2757
2797
|
|
|
2758
2798
|
// ---- 写入配置 ----
|
|
@@ -2768,13 +2808,19 @@ async function autoActivate(paths, args = {}) {
|
|
|
2768
2808
|
writeSpinner.succeed('配置写入完成');
|
|
2769
2809
|
|
|
2770
2810
|
// ---- 输出结果 ----
|
|
2771
|
-
const primaryTag = primaryType === 'claude' ? ' (主)' : '';
|
|
2772
|
-
const codexTag = primaryType === 'codex' ? ' (主)' : '';
|
|
2773
2811
|
console.log(chalk.green('\n✅ 一键激活完成!'));
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2812
|
+
if (isSingleMode) {
|
|
2813
|
+
console.log(chalk.cyan(` 节点: ${selectedEndpoint.url}`));
|
|
2814
|
+
console.log(chalk.gray(` 模型: ${claudeModel.name}`));
|
|
2815
|
+
} else {
|
|
2816
|
+
const primaryType = primaryModelKey === claudeModelKey ? 'claude' : 'codex';
|
|
2817
|
+
const primaryTag = primaryType === 'claude' ? ' (主)' : '';
|
|
2818
|
+
const codexTag = primaryType === 'codex' ? ' (主)' : '';
|
|
2819
|
+
console.log(chalk.cyan(` Claude Code${primaryTag}: ${claudeBaseUrl}`));
|
|
2820
|
+
console.log(chalk.gray(` 模型: ${claudeModel.name}`));
|
|
2821
|
+
console.log(chalk.cyan(` Codex${codexTag}: ${codexBaseUrl}`));
|
|
2822
|
+
console.log(chalk.gray(` 模型: ${codexModel.name}`));
|
|
2823
|
+
}
|
|
2778
2824
|
console.log(chalk.gray(' API Key: 已设置'));
|
|
2779
2825
|
if (extSynced.length > 0) console.log(chalk.gray(` 同步: ${extSynced.join(', ')}`));
|
|
2780
2826
|
|
|
@@ -2782,7 +2828,7 @@ async function autoActivate(paths, args = {}) {
|
|
|
2782
2828
|
const gwToken = config.gateway?.auth?.token;
|
|
2783
2829
|
if (gwToken) {
|
|
2784
2830
|
console.log(chalk.green(`\n🌐 Web Dashboard:`));
|
|
2785
|
-
console.log(chalk.cyan(` http://
|
|
2831
|
+
console.log(chalk.cyan(` http://127.0.0.1:${gwPort}/?token=${gwToken}`));
|
|
2786
2832
|
}
|
|
2787
2833
|
|
|
2788
2834
|
// ---- 测试连接 ----
|
|
@@ -3567,7 +3613,7 @@ async function switchModel(paths) {
|
|
|
3567
3613
|
|
|
3568
3614
|
if (gwToken) {
|
|
3569
3615
|
console.log(chalk.green(`\n🌐 Web Dashboard:`));
|
|
3570
|
-
console.log(chalk.cyan(` http://
|
|
3616
|
+
console.log(chalk.cyan(` http://127.0.0.1:${gwPort}/?token=${gwToken}`));
|
|
3571
3617
|
}
|
|
3572
3618
|
}
|
|
3573
3619
|
// ============ 权限管理 (tools.profile) ============
|
|
@@ -3658,8 +3704,7 @@ async function testConnection(paths, args = {}) {
|
|
|
3658
3704
|
console.log(chalk.yellow('⚠️ 请先设置主模型'));
|
|
3659
3705
|
return;
|
|
3660
3706
|
}
|
|
3661
|
-
|
|
3662
|
-
const preferOrder = ['claude-yunyi', 'yunyi', 'heibai'];
|
|
3707
|
+
const preferOrder = ['maxapi', 'heibai', 'claude-yunyi', 'yunyi', 'maxapi-codex'];
|
|
3663
3708
|
const preferred = preferOrder.find(n => providerNames.includes(n));
|
|
3664
3709
|
const firstP = preferred || providerNames.find(n => n.includes('claude')) || providerNames[0];
|
|
3665
3710
|
const firstModels = providers[firstP]?.models || [];
|
|
@@ -3695,7 +3740,7 @@ async function testConnection(paths, args = {}) {
|
|
|
3695
3740
|
console.log(chalk.gray(`当前激活: ${typeLabel}`));
|
|
3696
3741
|
console.log(chalk.gray(`中转节点: ${provider.baseUrl}`));
|
|
3697
3742
|
console.log(chalk.gray(`模型: ${primary}`));
|
|
3698
|
-
console.log(chalk.gray(`Gateway: http://
|
|
3743
|
+
console.log(chalk.gray(`Gateway: http://127.0.0.1:${gatewayPort}\n`));
|
|
3699
3744
|
// 获取 Gateway token
|
|
3700
3745
|
const gatewayToken = config.gateway?.auth?.token;
|
|
3701
3746
|
if (!gatewayToken) {
|
|
@@ -3819,7 +3864,7 @@ async function testConnection(paths, args = {}) {
|
|
|
3819
3864
|
}
|
|
3820
3865
|
}
|
|
3821
3866
|
|
|
3822
|
-
console.log(chalk.gray(` 端点: http://
|
|
3867
|
+
console.log(chalk.gray(` 端点: http://127.0.0.1:${gatewayPort}/v1/responses`));
|
|
3823
3868
|
const startTime = Date.now();
|
|
3824
3869
|
let result = await testGatewayApi(gatewayPort, gatewayToken, primary);
|
|
3825
3870
|
const latency = Date.now() - startTime;
|
|
@@ -4017,15 +4062,11 @@ async function restartGatewayNative(silent = false) {
|
|
|
4017
4062
|
const gwConfig = readConfig(configPaths.openclawConfig);
|
|
4018
4063
|
const gatewayPort = gwConfig?.gateway?.port || 18789;
|
|
4019
4064
|
|
|
4020
|
-
// 策略 A
|
|
4021
|
-
// 注意:该命令通常会启动前台 daemon 不退出,超时是正常的
|
|
4065
|
+
// 策略 A:尝试 exec "gateway restart"(短超时),然后端口探测
|
|
4022
4066
|
const restartCmds = buildGatewayCommands(resolved, nodeInfo, useNode, 'restart');
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
execAttempted = true;
|
|
4027
|
-
const result = safeExec(cmd, { timeout: 8000, env });
|
|
4028
|
-
// 不管是否 "成功"(通常会超时),检查端口是否可达
|
|
4067
|
+
if (restartCmds.length > 0) {
|
|
4068
|
+
// 只尝试第一条(最精确的路径),避免逐条超时累积
|
|
4069
|
+
safeExec(restartCmds[0], { timeout: 8000, env });
|
|
4029
4070
|
if (await waitForGateway(gatewayPort, '127.0.0.1', 5000)) {
|
|
4030
4071
|
if (!silent) console.log(chalk.green(`✅ Gateway 已重启`));
|
|
4031
4072
|
return true;
|
|
@@ -4035,29 +4076,34 @@ async function restartGatewayNative(silent = false) {
|
|
|
4035
4076
|
// 策略 B:杀旧进程 → spawn 后台启动新 Gateway → 端口探测
|
|
4036
4077
|
if (!silent) console.log(chalk.gray(' 常规重启未生效,尝试杀进程后重新启动...'));
|
|
4037
4078
|
await killGatewayProcesses(gatewayPort);
|
|
4038
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
4079
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
4039
4080
|
|
|
4040
4081
|
const startCmds = buildGatewayCommands(resolved, nodeInfo, useNode, 'start');
|
|
4041
4082
|
for (const cmd of startCmds) {
|
|
4042
4083
|
if (spawnDetached(cmd, env)) {
|
|
4043
|
-
if (await waitForGateway(gatewayPort, '127.0.0.1',
|
|
4084
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 10000)) {
|
|
4044
4085
|
if (!silent) console.log(chalk.green('✅ Gateway 已重启'));
|
|
4045
4086
|
return true;
|
|
4046
4087
|
}
|
|
4088
|
+
break; // spawn 成功但端口未通,不再逐条重试
|
|
4047
4089
|
}
|
|
4048
4090
|
}
|
|
4049
4091
|
|
|
4050
4092
|
// 策略 C:用 login shell 启动(加载 nvm/fnm 等 PATH)
|
|
4051
4093
|
if (process.platform !== 'win32') {
|
|
4052
4094
|
if (!silent) console.log(chalk.gray(' 尝试通过 login shell 启动...'));
|
|
4095
|
+
let launched = false;
|
|
4053
4096
|
for (const name of ['openclaw', 'clawdbot', 'moltbot']) {
|
|
4097
|
+
if (launched) break;
|
|
4054
4098
|
for (const sh of ['/bin/zsh', '/bin/bash']) {
|
|
4055
4099
|
if (!fs.existsSync(sh)) continue;
|
|
4056
4100
|
if (spawnDetached(`${sh} -lc '${name} gateway'`, env)) {
|
|
4057
|
-
|
|
4101
|
+
launched = true;
|
|
4102
|
+
if (await waitForGateway(gatewayPort, '127.0.0.1', 10000)) {
|
|
4058
4103
|
if (!silent) console.log(chalk.green('✅ Gateway 已重启 (login shell)'));
|
|
4059
4104
|
return true;
|
|
4060
4105
|
}
|
|
4106
|
+
break;
|
|
4061
4107
|
}
|
|
4062
4108
|
}
|
|
4063
4109
|
}
|
|
@@ -4087,30 +4133,7 @@ function buildGatewayCommands(resolved, nodeInfo, useNode, action) {
|
|
|
4087
4133
|
}
|
|
4088
4134
|
|
|
4089
4135
|
const names = ['openclaw', 'clawdbot', 'moltbot'];
|
|
4090
|
-
|
|
4091
|
-
for (const name of names) commands.push(`${name} ${verb}`);
|
|
4092
|
-
if (isWslAvailable()) {
|
|
4093
|
-
const wslCli = getWslCliBinary();
|
|
4094
|
-
if (wslCli) commands.push(`wsl -- bash -lc "${wslCli} ${verb}"`);
|
|
4095
|
-
for (const name of names) commands.push(`wsl -- bash -lc "${name} ${verb}"`);
|
|
4096
|
-
}
|
|
4097
|
-
} else {
|
|
4098
|
-
for (const name of names) commands.push(`${name} ${verb}`);
|
|
4099
|
-
}
|
|
4100
|
-
|
|
4101
|
-
if (isDockerAvailable()) {
|
|
4102
|
-
const containers = findOpenclawDockerContainers();
|
|
4103
|
-
for (const c of containers) {
|
|
4104
|
-
if (c.cli === 'node') {
|
|
4105
|
-
commands.push(dockerCmd(`exec ${c.id} bash -lc "node ${c.cliPath} ${verb}"`));
|
|
4106
|
-
} else if (c.cli !== 'unknown') {
|
|
4107
|
-
commands.push(dockerCmd(`exec ${c.id} bash -lc "${c.cli} ${verb}"`));
|
|
4108
|
-
}
|
|
4109
|
-
for (const name of names) {
|
|
4110
|
-
commands.push(dockerCmd(`exec ${c.id} bash -lc "${name} ${verb}"`));
|
|
4111
|
-
}
|
|
4112
|
-
}
|
|
4113
|
-
}
|
|
4136
|
+
for (const name of names) commands.push(`${name} ${verb}`);
|
|
4114
4137
|
|
|
4115
4138
|
return [...new Set(commands)].filter(Boolean);
|
|
4116
4139
|
}
|