yymaxapi 1.0.80 → 1.0.81
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
CHANGED
|
@@ -94,7 +94,7 @@ const DEFAULT_CLAUDE_MODELS = [
|
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
96
|
"id": "claude-opus-4-6",
|
|
97
|
-
"name": "Claude Opus 4.6 (
|
|
97
|
+
"name": "Claude Opus 4.6 (待稳定)"
|
|
98
98
|
}
|
|
99
99
|
];
|
|
100
100
|
|
|
@@ -228,6 +228,127 @@ const CLAUDE_MODELS = PRESETS.models.claude;
|
|
|
228
228
|
const CODEX_MODELS = PRESETS.models.codex;
|
|
229
229
|
const API_CONFIG = PRESETS.apiConfig;
|
|
230
230
|
|
|
231
|
+
function getDefaultClaudeModel() {
|
|
232
|
+
return CLAUDE_MODELS[0] || { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6' };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getDefaultCodexModel() {
|
|
236
|
+
return CODEX_MODELS[0] || { id: 'gpt-5.4', name: 'GPT 5.4' };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function buildProviderModelMap(models) {
|
|
240
|
+
const mapped = {};
|
|
241
|
+
for (const model of models || []) {
|
|
242
|
+
if (!model?.id) continue;
|
|
243
|
+
mapped[model.id] = { name: model.name || model.id };
|
|
244
|
+
}
|
|
245
|
+
return mapped;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function getClaudeSwitchHint() {
|
|
249
|
+
return CLAUDE_MODELS.map(model => model.name).join(' / ');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function getOpencodeSwitchHint() {
|
|
253
|
+
return [...CLAUDE_MODELS, ...CODEX_MODELS].map(model => model.name).join(' / ');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function promptClaudeModelSelection(args = {}, message = '选择 Claude 模型:') {
|
|
257
|
+
const requested = (args['claude-model'] || args.model || args['model-id'] || '').toString().trim();
|
|
258
|
+
const fallback = getDefaultClaudeModel();
|
|
259
|
+
if (requested) {
|
|
260
|
+
return CLAUDE_MODELS.find(model => model.id === requested) || { id: requested, name: requested };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (CLAUDE_MODELS.length <= 1) {
|
|
264
|
+
return fallback;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const { selected } = await inquirer.prompt([{
|
|
268
|
+
type: 'list',
|
|
269
|
+
name: 'selected',
|
|
270
|
+
message,
|
|
271
|
+
choices: CLAUDE_MODELS.map(model => ({ name: model.name, value: model.id })),
|
|
272
|
+
default: fallback.id
|
|
273
|
+
}]);
|
|
274
|
+
|
|
275
|
+
return CLAUDE_MODELS.find(model => model.id === selected) || fallback;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function promptOpencodeDefaultModelSelection(args = {}, message = '选择 Opencode 默认模型:') {
|
|
279
|
+
const fallbackClaude = getDefaultClaudeModel();
|
|
280
|
+
const fallbackCodex = getDefaultCodexModel();
|
|
281
|
+
const requested = (args['default-model'] || args.model || args['claude-model'] || args['codex-model'] || '').toString().trim();
|
|
282
|
+
|
|
283
|
+
if (requested) {
|
|
284
|
+
const inClaude = CLAUDE_MODELS.find(model => model.id === requested);
|
|
285
|
+
if (inClaude) {
|
|
286
|
+
return {
|
|
287
|
+
type: 'claude',
|
|
288
|
+
providerKey: 'yunyi-claude',
|
|
289
|
+
modelId: inClaude.id,
|
|
290
|
+
modelName: inClaude.name,
|
|
291
|
+
modelKey: `yunyi-claude/${inClaude.id}`
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const inCodex = CODEX_MODELS.find(model => model.id === requested);
|
|
296
|
+
if (inCodex) {
|
|
297
|
+
return {
|
|
298
|
+
type: 'codex',
|
|
299
|
+
providerKey: 'yunyi-codex',
|
|
300
|
+
modelId: inCodex.id,
|
|
301
|
+
modelName: inCodex.name,
|
|
302
|
+
modelKey: `yunyi-codex/${inCodex.id}`
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const defaultValue = `claude:${fallbackClaude.id}`;
|
|
308
|
+
const choices = [];
|
|
309
|
+
if (CLAUDE_MODELS.length > 0) {
|
|
310
|
+
choices.push(new inquirer.Separator(' -- Claude --'));
|
|
311
|
+
for (const model of CLAUDE_MODELS) {
|
|
312
|
+
choices.push({ name: model.name, value: `claude:${model.id}` });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (CODEX_MODELS.length > 0) {
|
|
316
|
+
choices.push(new inquirer.Separator(' -- GPT --'));
|
|
317
|
+
for (const model of CODEX_MODELS) {
|
|
318
|
+
choices.push({ name: model.name, value: `codex:${model.id}` });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const { picked } = await inquirer.prompt([{
|
|
323
|
+
type: 'list',
|
|
324
|
+
name: 'picked',
|
|
325
|
+
message,
|
|
326
|
+
choices,
|
|
327
|
+
default: defaultValue
|
|
328
|
+
}]);
|
|
329
|
+
|
|
330
|
+
const [pickedType, pickedId] = picked.split(':');
|
|
331
|
+
if (pickedType === 'codex') {
|
|
332
|
+
const model = CODEX_MODELS.find(item => item.id === pickedId) || fallbackCodex;
|
|
333
|
+
return {
|
|
334
|
+
type: 'codex',
|
|
335
|
+
providerKey: 'yunyi-codex',
|
|
336
|
+
modelId: model.id,
|
|
337
|
+
modelName: model.name,
|
|
338
|
+
modelKey: `yunyi-codex/${model.id}`
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const model = CLAUDE_MODELS.find(item => item.id === pickedId) || fallbackClaude;
|
|
343
|
+
return {
|
|
344
|
+
type: 'claude',
|
|
345
|
+
providerKey: 'yunyi-claude',
|
|
346
|
+
modelId: model.id,
|
|
347
|
+
modelName: model.name,
|
|
348
|
+
modelKey: `yunyi-claude/${model.id}`
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
231
352
|
// 备份文件名(兼容旧版单文件备份)
|
|
232
353
|
const BACKUP_FILENAME = 'openclaw-default.json.bak';
|
|
233
354
|
const BACKUP_DIR_NAME = 'backups';
|
|
@@ -636,6 +757,7 @@ function readConfig(configPath) {
|
|
|
636
757
|
}
|
|
637
758
|
|
|
638
759
|
function writeConfig(configPath, config) {
|
|
760
|
+
sanitizeDefaultModelSelection(config);
|
|
639
761
|
const dir = path.dirname(configPath);
|
|
640
762
|
if (!fs.existsSync(dir)) {
|
|
641
763
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -645,7 +767,7 @@ function writeConfig(configPath, config) {
|
|
|
645
767
|
|
|
646
768
|
// ============ 多工具配置同步 ============
|
|
647
769
|
|
|
648
|
-
function writeClaudeCodeSettings(baseUrl, apiKey) {
|
|
770
|
+
function writeClaudeCodeSettings(baseUrl, apiKey, modelId = getDefaultClaudeModel().id) {
|
|
649
771
|
const home = os.homedir();
|
|
650
772
|
// ~/.claude/settings.json
|
|
651
773
|
const claudeDir = path.join(home, '.claude');
|
|
@@ -656,9 +778,12 @@ function writeClaudeCodeSettings(baseUrl, apiKey) {
|
|
|
656
778
|
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch { settings = {}; }
|
|
657
779
|
}
|
|
658
780
|
settings.apiBaseUrl = baseUrl.replace(/\/+$/, '');
|
|
781
|
+
settings.model = modelId;
|
|
782
|
+
settings.availableModels = CLAUDE_MODELS.map(model => model.id);
|
|
659
783
|
if (!settings.env) settings.env = {};
|
|
660
784
|
settings.env.ANTHROPIC_BASE_URL = baseUrl.replace(/\/+$/, '');
|
|
661
785
|
settings.env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
786
|
+
settings.env.ANTHROPIC_MODEL = modelId;
|
|
662
787
|
if (!fs.existsSync(claudeDir)) fs.mkdirSync(claudeDir, { recursive: true });
|
|
663
788
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
664
789
|
} catch { /* 非关键,静默失败 */ }
|
|
@@ -681,7 +806,7 @@ function writeClaudeCodeSettings(baseUrl, apiKey) {
|
|
|
681
806
|
try {
|
|
682
807
|
const cleanUrl = baseUrl.replace(/\/+$/, '');
|
|
683
808
|
execSync(
|
|
684
|
-
`powershell -NoProfile -Command "[Environment]::SetEnvironmentVariable('ANTHROPIC_BASE_URL','${cleanUrl}','User'); [Environment]::SetEnvironmentVariable('ANTHROPIC_AUTH_TOKEN','${apiKey}','User'); [Environment]::SetEnvironmentVariable('NODE_TLS_REJECT_UNAUTHORIZED','0','User')"`,
|
|
809
|
+
`powershell -NoProfile -Command "[Environment]::SetEnvironmentVariable('ANTHROPIC_BASE_URL','${cleanUrl}','User'); [Environment]::SetEnvironmentVariable('ANTHROPIC_AUTH_TOKEN','${apiKey}','User'); [Environment]::SetEnvironmentVariable('ANTHROPIC_MODEL','${modelId}','User'); [Environment]::SetEnvironmentVariable('NODE_TLS_REJECT_UNAUTHORIZED','0','User')"`,
|
|
685
810
|
{ stdio: 'pipe' }
|
|
686
811
|
);
|
|
687
812
|
} catch { /* best-effort */ }
|
|
@@ -693,6 +818,7 @@ function writeClaudeCodeSettings(baseUrl, apiKey) {
|
|
|
693
818
|
marker,
|
|
694
819
|
`export ANTHROPIC_BASE_URL="${cleanUrl}"`,
|
|
695
820
|
`export ANTHROPIC_AUTH_TOKEN="${apiKey}"`,
|
|
821
|
+
`export ANTHROPIC_MODEL="${modelId}"`,
|
|
696
822
|
'# 中转站证书校验放宽,避免 unknown certificate verification error',
|
|
697
823
|
'export NODE_TLS_REJECT_UNAUTHORIZED=0',
|
|
698
824
|
markerEnd
|
|
@@ -796,7 +922,7 @@ function writeCodexConfig(baseUrl, apiKey, modelId = 'gpt-5.4') {
|
|
|
796
922
|
} catch { /* 非关键,静默失败 */ }
|
|
797
923
|
}
|
|
798
924
|
|
|
799
|
-
function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey,
|
|
925
|
+
function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, defaultModelKey = `yunyi-claude/${getDefaultClaudeModel().id}`) {
|
|
800
926
|
const home = os.homedir();
|
|
801
927
|
const claudeUrl = claudeBaseUrl.replace(/\/+$/, '');
|
|
802
928
|
const codexUrl = (codexBaseUrl || '').replace(/\/+$/, '');
|
|
@@ -818,7 +944,7 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, modelId) {
|
|
|
818
944
|
existing.provider['yunyi-claude'] = {
|
|
819
945
|
name: '云翼 Claude',
|
|
820
946
|
npm: '@ai-sdk/anthropic',
|
|
821
|
-
models:
|
|
947
|
+
models: buildProviderModelMap(CLAUDE_MODELS),
|
|
822
948
|
options: { apiKey, baseURL: `${claudeUrl}/v1` }
|
|
823
949
|
};
|
|
824
950
|
|
|
@@ -827,7 +953,7 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, modelId) {
|
|
|
827
953
|
existing.provider['yunyi-codex'] = {
|
|
828
954
|
name: '云翼 Codex',
|
|
829
955
|
npm: '@ai-sdk/openai',
|
|
830
|
-
models:
|
|
956
|
+
models: buildProviderModelMap(CODEX_MODELS),
|
|
831
957
|
options: { apiKey, baseURL: codexUrl }
|
|
832
958
|
};
|
|
833
959
|
}
|
|
@@ -839,8 +965,7 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, modelId) {
|
|
|
839
965
|
}
|
|
840
966
|
|
|
841
967
|
// 设置默认模型
|
|
842
|
-
|
|
843
|
-
existing.model = `yunyi-claude/${rawModelId}`;
|
|
968
|
+
existing.model = defaultModelKey;
|
|
844
969
|
|
|
845
970
|
// 从 disabled_providers 中移除我们的 provider
|
|
846
971
|
if (Array.isArray(existing.disabled_providers)) {
|
|
@@ -895,14 +1020,14 @@ function syncExternalTools(type, baseUrl, apiKey, extra = {}) {
|
|
|
895
1020
|
const synced = [];
|
|
896
1021
|
try {
|
|
897
1022
|
if (type === 'claude') {
|
|
898
|
-
writeClaudeCodeSettings(baseUrl, apiKey);
|
|
1023
|
+
writeClaudeCodeSettings(baseUrl, apiKey, extra.claudeModelId || getDefaultClaudeModel().id);
|
|
899
1024
|
synced.push('Claude Code settings');
|
|
900
1025
|
} else if (type === 'codex') {
|
|
901
|
-
writeCodexConfig(baseUrl, apiKey);
|
|
1026
|
+
writeCodexConfig(baseUrl, apiKey, extra.modelId || getDefaultCodexModel().id);
|
|
902
1027
|
synced.push('Codex CLI config');
|
|
903
1028
|
}
|
|
904
1029
|
if (type === 'claude' && extra.codexBaseUrl) {
|
|
905
|
-
writeOpencodeConfig(baseUrl, extra.codexBaseUrl, apiKey);
|
|
1030
|
+
writeOpencodeConfig(baseUrl, extra.codexBaseUrl, apiKey, extra.opencodeDefaultModelKey || `yunyi-claude/${extra.claudeModelId || getDefaultClaudeModel().id}`);
|
|
906
1031
|
synced.push('Opencode config');
|
|
907
1032
|
}
|
|
908
1033
|
} catch { /* ignore */ }
|
|
@@ -1329,6 +1454,68 @@ function coerceModelsRecord(value) {
|
|
|
1329
1454
|
return record;
|
|
1330
1455
|
}
|
|
1331
1456
|
|
|
1457
|
+
function isValidModelRef(value) {
|
|
1458
|
+
return typeof value === 'string' && value.trim().includes('/');
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
function collectConfiguredModelKeys(config) {
|
|
1462
|
+
const keys = [];
|
|
1463
|
+
|
|
1464
|
+
const registered = Object.keys(config?.agents?.defaults?.models || {}).filter(isValidModelRef);
|
|
1465
|
+
keys.push(...registered);
|
|
1466
|
+
|
|
1467
|
+
const providers = config?.models?.providers || {};
|
|
1468
|
+
for (const [providerName, providerConfig] of Object.entries(providers)) {
|
|
1469
|
+
for (const model of providerConfig.models || []) {
|
|
1470
|
+
if (!model?.id) continue;
|
|
1471
|
+
const modelKey = `${providerName}/${model.id}`;
|
|
1472
|
+
if (isValidModelRef(modelKey)) keys.push(modelKey);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
return [...new Set(keys)];
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
function inferPrimaryModelKey(config) {
|
|
1480
|
+
return collectConfiguredModelKeys(config)[0] || '';
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
function normalizeDefaultModelSelection(value, config) {
|
|
1484
|
+
if (typeof value === 'string') {
|
|
1485
|
+
const primary = value.trim();
|
|
1486
|
+
return primary ? { primary, fallbacks: [] } : { fallbacks: [] };
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
1490
|
+
let primary = typeof value.primary === 'string' ? value.primary.trim() : '';
|
|
1491
|
+
const fallbacks = Array.isArray(value.fallbacks)
|
|
1492
|
+
? [...new Set(value.fallbacks.map(item => String(item || '').trim()).filter(isValidModelRef))]
|
|
1493
|
+
: [];
|
|
1494
|
+
|
|
1495
|
+
if (!primary) {
|
|
1496
|
+
primary = inferPrimaryModelKey(config) || fallbacks[0] || '';
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
const normalizedFallbacks = fallbacks.filter(modelKey => modelKey !== primary);
|
|
1500
|
+
const normalized = { fallbacks: normalizedFallbacks };
|
|
1501
|
+
if (primary) normalized.primary = primary;
|
|
1502
|
+
return normalized;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
const inferred = inferPrimaryModelKey(config);
|
|
1506
|
+
return inferred ? { primary: inferred, fallbacks: [] } : { fallbacks: [] };
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
function sanitizeDefaultModelSelection(config) {
|
|
1510
|
+
if (!config?.agents?.defaults) return;
|
|
1511
|
+
const normalized = normalizeDefaultModelSelection(config.agents.defaults.model, config);
|
|
1512
|
+
if (normalized.primary) {
|
|
1513
|
+
config.agents.defaults.model = normalized;
|
|
1514
|
+
} else {
|
|
1515
|
+
delete config.agents.defaults.model;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1332
1519
|
const OPENCLAW_INVALID_ROOT_KEYS = ['model'];
|
|
1333
1520
|
|
|
1334
1521
|
function sanitizeRootKeys(config) {
|
|
@@ -1344,7 +1531,7 @@ function ensureConfigStructure(config) {
|
|
|
1344
1531
|
if (!next.models.providers) next.models.providers = {};
|
|
1345
1532
|
if (!next.agents) next.agents = {};
|
|
1346
1533
|
if (!next.agents.defaults) next.agents.defaults = {};
|
|
1347
|
-
|
|
1534
|
+
next.agents.defaults.model = normalizeDefaultModelSelection(next.agents.defaults.model, next);
|
|
1348
1535
|
if (!next.agents.defaults.models || Array.isArray(next.agents.defaults.models) || typeof next.agents.defaults.models !== 'object') {
|
|
1349
1536
|
next.agents.defaults.models = coerceModelsRecord(next.agents.defaults.models);
|
|
1350
1537
|
}
|
|
@@ -1619,6 +1806,20 @@ async function promptApiKey(message, defaultValue) {
|
|
|
1619
1806
|
return '';
|
|
1620
1807
|
}
|
|
1621
1808
|
|
|
1809
|
+
async function confirmImmediateTest(args = {}, message = '是否立即测试连接?', defaultValue = true) {
|
|
1810
|
+
if (args.test !== undefined) {
|
|
1811
|
+
return !['false', '0', 'no'].includes(String(args.test).toLowerCase());
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
const { shouldTest } = await inquirer.prompt([{
|
|
1815
|
+
type: 'confirm',
|
|
1816
|
+
name: 'shouldTest',
|
|
1817
|
+
message,
|
|
1818
|
+
default: defaultValue
|
|
1819
|
+
}]);
|
|
1820
|
+
return shouldTest;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1622
1823
|
function extendPathEnv(preferredNodePath) {
|
|
1623
1824
|
const current = process.env.PATH || '';
|
|
1624
1825
|
const parts = current.split(path.delimiter).filter(Boolean);
|
|
@@ -1816,6 +2017,251 @@ function getCliMeta() {
|
|
|
1816
2017
|
return { cliBinary, cliName, nodeMajor };
|
|
1817
2018
|
}
|
|
1818
2019
|
|
|
2020
|
+
function resolveCommandBinary(names) {
|
|
2021
|
+
const candidates = Array.isArray(names) ? names : [names];
|
|
2022
|
+
const isWin = process.platform === 'win32';
|
|
2023
|
+
const searchDirs = (process.env.PATH || '').split(path.delimiter).concat(EXTRA_BIN_DIRS).filter(Boolean);
|
|
2024
|
+
|
|
2025
|
+
for (const name of candidates) {
|
|
2026
|
+
const variants = isWin ? [`${name}.cmd`, `${name}.exe`, name] : [name];
|
|
2027
|
+
for (const variant of variants) {
|
|
2028
|
+
for (const dir of searchDirs) {
|
|
2029
|
+
const full = path.join(dir, variant);
|
|
2030
|
+
try {
|
|
2031
|
+
if (fs.existsSync(full) && fs.statSync(full).isFile()) {
|
|
2032
|
+
return full;
|
|
2033
|
+
}
|
|
2034
|
+
} catch { }
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
if (isWin) {
|
|
2039
|
+
const result = safeExec(`where ${name}`);
|
|
2040
|
+
if (result.ok && result.output) {
|
|
2041
|
+
return result.output.split('\n')[0].trim();
|
|
2042
|
+
}
|
|
2043
|
+
} else {
|
|
2044
|
+
for (const sh of ['/bin/zsh', '/bin/bash', '/bin/sh']) {
|
|
2045
|
+
if (!fs.existsSync(sh)) continue;
|
|
2046
|
+
const result = safeExec(`${sh} -lc "command -v ${name}"`);
|
|
2047
|
+
if (result.ok && result.output) {
|
|
2048
|
+
return result.output.split('\n')[0].trim();
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
return null;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function readJsonIfExists(filePath) {
|
|
2058
|
+
if (!filePath || !fs.existsSync(filePath)) return null;
|
|
2059
|
+
try {
|
|
2060
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
2061
|
+
} catch {
|
|
2062
|
+
return null;
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
function getClaudeCodeSettingsPath() {
|
|
2067
|
+
return path.join(os.homedir(), '.claude', 'settings.json');
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
function getOpencodeConfigPath() {
|
|
2071
|
+
const home = os.homedir();
|
|
2072
|
+
return process.platform === 'win32'
|
|
2073
|
+
? path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'opencode', 'opencode.json')
|
|
2074
|
+
: path.join(home, '.config', 'opencode', 'opencode.json');
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
function getCodexCliPaths() {
|
|
2078
|
+
const codexDir = path.join(os.homedir(), '.codex');
|
|
2079
|
+
return {
|
|
2080
|
+
configPath: path.join(codexDir, 'config.toml'),
|
|
2081
|
+
authPath: path.join(codexDir, 'auth.json')
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
function readClaudeCodeCliConfig() {
|
|
2086
|
+
const settingsPath = getClaudeCodeSettingsPath();
|
|
2087
|
+
const settings = readJsonIfExists(settingsPath) || {};
|
|
2088
|
+
return {
|
|
2089
|
+
settingsPath,
|
|
2090
|
+
modelId: settings.model || settings.env?.ANTHROPIC_MODEL || process.env.ANTHROPIC_MODEL || getDefaultClaudeModel().id,
|
|
2091
|
+
baseUrl: settings.env?.ANTHROPIC_BASE_URL || settings.apiBaseUrl || process.env.ANTHROPIC_BASE_URL || '',
|
|
2092
|
+
apiKey: settings.env?.ANTHROPIC_AUTH_TOKEN || process.env.ANTHROPIC_AUTH_TOKEN || '',
|
|
2093
|
+
configured: fs.existsSync(settingsPath)
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
function readOpencodeCliConfig() {
|
|
2098
|
+
const configPath = getOpencodeConfigPath();
|
|
2099
|
+
const config = readJsonIfExists(configPath) || {};
|
|
2100
|
+
return {
|
|
2101
|
+
configPath,
|
|
2102
|
+
modelKey: config.model || `yunyi-claude/${getDefaultClaudeModel().id}`,
|
|
2103
|
+
configured: fs.existsSync(configPath),
|
|
2104
|
+
config
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
function readCodexCliConfig() {
|
|
2109
|
+
const { configPath, authPath } = getCodexCliPaths();
|
|
2110
|
+
const configRaw = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
|
|
2111
|
+
const auth = readJsonIfExists(authPath) || {};
|
|
2112
|
+
const model = (configRaw.match(/^model\s*=\s*"([^"]+)"\s*$/m) || [])[1] || getDefaultCodexModel().id;
|
|
2113
|
+
const provider = (configRaw.match(/^model_provider\s*=\s*"([^"]+)"\s*$/m) || [])[1] || 'yunyi-codex';
|
|
2114
|
+
const providerBlockRegex = new RegExp(`\\[model_providers\\.${escapeRegExp(provider)}\\]([\\s\\S]*?)(?=\\n\\[|$)`, 'm');
|
|
2115
|
+
const providerBlock = (configRaw.match(providerBlockRegex) || [])[1] || '';
|
|
2116
|
+
const baseUrl = (providerBlock.match(/base_url\s*=\s*"([^"]+)"/) || [])[1] || '';
|
|
2117
|
+
const apiKey = (providerBlock.match(/experimental_bearer_token\s*=\s*"([^"]+)"/) || [])[1] || auth.OPENAI_API_KEY || '';
|
|
2118
|
+
return {
|
|
2119
|
+
configPath,
|
|
2120
|
+
authPath,
|
|
2121
|
+
modelId: model,
|
|
2122
|
+
provider,
|
|
2123
|
+
baseUrl,
|
|
2124
|
+
apiKey,
|
|
2125
|
+
configured: fs.existsSync(configPath)
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
function cleanCliTestOutput(text) {
|
|
2130
|
+
return String(text || '')
|
|
2131
|
+
.replace(/\u001b\[[0-9;]*m/g, '')
|
|
2132
|
+
.replace(/\(node:\d+\) Warning: Setting the NODE_TLS_REJECT_UNAUTHORIZED[\s\S]*?(?=\n\S|$)/g, '')
|
|
2133
|
+
.replace(/\s+$/g, '')
|
|
2134
|
+
.trim();
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
function summarizeCliTestOutput(text) {
|
|
2138
|
+
const lines = cleanCliTestOutput(text)
|
|
2139
|
+
.split('\n')
|
|
2140
|
+
.map(line => line.trim())
|
|
2141
|
+
.filter(Boolean)
|
|
2142
|
+
.filter(line => !/^logs?:/i.test(line));
|
|
2143
|
+
if (lines.length === 0) return '';
|
|
2144
|
+
return lines[0].slice(0, 160);
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
function looksLikeCliTestError(text) {
|
|
2148
|
+
const lower = cleanCliTestOutput(text).toLowerCase();
|
|
2149
|
+
if (!lower) return false;
|
|
2150
|
+
return [
|
|
2151
|
+
'all models failed',
|
|
2152
|
+
'auth_permanent',
|
|
2153
|
+
'auth issue',
|
|
2154
|
+
'unauthorized',
|
|
2155
|
+
'forbidden',
|
|
2156
|
+
'missing environment variable',
|
|
2157
|
+
'gateway agent failed',
|
|
2158
|
+
'error:'
|
|
2159
|
+
].some(pattern => lower.includes(pattern));
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
function runCliTestCandidates(name, commands, env) {
|
|
2163
|
+
let lastError = '命令执行失败';
|
|
2164
|
+
for (const command of commands) {
|
|
2165
|
+
const result = safeExec(command, { timeout: 120000, env, maxBuffer: 1024 * 1024 });
|
|
2166
|
+
const combined = cleanCliTestOutput(`${result.output || ''}\n${result.stdout || ''}\n${result.stderr || ''}`);
|
|
2167
|
+
if (result.ok && !looksLikeCliTestError(combined)) {
|
|
2168
|
+
return { name, status: 'success', detail: summarizeCliTestOutput(combined) || '连接成功' };
|
|
2169
|
+
}
|
|
2170
|
+
lastError = summarizeCliTestOutput(combined) || result.error || lastError;
|
|
2171
|
+
}
|
|
2172
|
+
return { name, status: 'failed', detail: lastError };
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
function testClaudeCodeCliConnection() {
|
|
2176
|
+
const cliBinary = resolveCommandBinary('claude');
|
|
2177
|
+
if (!cliBinary) return { name: 'Claude Code CLI', status: 'skipped', detail: '未安装 claude 命令' };
|
|
2178
|
+
|
|
2179
|
+
const config = readClaudeCodeCliConfig();
|
|
2180
|
+
if (!config.configured) return { name: 'Claude Code CLI', status: 'skipped', detail: '未检测到 ~/.claude/settings.json' };
|
|
2181
|
+
if (!config.baseUrl || !config.apiKey) return { name: 'Claude Code CLI', status: 'failed', detail: 'Claude Code 配置缺少 Base URL 或 API Key' };
|
|
2182
|
+
|
|
2183
|
+
const env = {
|
|
2184
|
+
...process.env,
|
|
2185
|
+
PATH: extendPathEnv(null),
|
|
2186
|
+
ANTHROPIC_BASE_URL: config.baseUrl,
|
|
2187
|
+
ANTHROPIC_AUTH_TOKEN: config.apiKey,
|
|
2188
|
+
ANTHROPIC_MODEL: config.modelId,
|
|
2189
|
+
NODE_TLS_REJECT_UNAUTHORIZED: '0',
|
|
2190
|
+
NODE_NO_WARNINGS: '1'
|
|
2191
|
+
};
|
|
2192
|
+
|
|
2193
|
+
return runCliTestCandidates('Claude Code CLI', [
|
|
2194
|
+
`${shellQuote(cliBinary)} -p --model ${shellQuote(config.modelId)} --output-format text ${shellQuote('请只回复 OK')}`,
|
|
2195
|
+
`${shellQuote(cliBinary)} -p --model ${shellQuote(config.modelId)} ${shellQuote('请只回复 OK')}`,
|
|
2196
|
+
`${shellQuote(cliBinary)} -p ${shellQuote('请只回复 OK')}`
|
|
2197
|
+
], env);
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
function testOpencodeCliConnection() {
|
|
2201
|
+
const cliBinary = resolveCommandBinary('opencode');
|
|
2202
|
+
if (!cliBinary) return { name: 'Opencode CLI', status: 'skipped', detail: '未安装 opencode 命令' };
|
|
2203
|
+
|
|
2204
|
+
const config = readOpencodeCliConfig();
|
|
2205
|
+
if (!config.configured) return { name: 'Opencode CLI', status: 'skipped', detail: '未检测到 opencode 配置文件' };
|
|
2206
|
+
|
|
2207
|
+
const env = {
|
|
2208
|
+
...process.env,
|
|
2209
|
+
PATH: extendPathEnv(null),
|
|
2210
|
+
NODE_TLS_REJECT_UNAUTHORIZED: '0',
|
|
2211
|
+
NODE_NO_WARNINGS: '1'
|
|
2212
|
+
};
|
|
2213
|
+
|
|
2214
|
+
return runCliTestCandidates('Opencode CLI', [
|
|
2215
|
+
`${shellQuote(cliBinary)} run --model ${shellQuote(config.modelKey)} ${shellQuote('请只回复 OK')}`,
|
|
2216
|
+
`${shellQuote(cliBinary)} run ${shellQuote('请只回复 OK')} --model ${shellQuote(config.modelKey)}`,
|
|
2217
|
+
`${shellQuote(cliBinary)} -m ${shellQuote(config.modelKey)} run ${shellQuote('请只回复 OK')}`
|
|
2218
|
+
], env);
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
function testCodexCliConnection() {
|
|
2222
|
+
const cliBinary = resolveCommandBinary('codex');
|
|
2223
|
+
if (!cliBinary) return { name: 'Codex CLI', status: 'skipped', detail: '未安装 codex 命令' };
|
|
2224
|
+
|
|
2225
|
+
const config = readCodexCliConfig();
|
|
2226
|
+
if (!config.configured) return { name: 'Codex CLI', status: 'skipped', detail: '未检测到 ~/.codex/config.toml' };
|
|
2227
|
+
if (!config.apiKey) return { name: 'Codex CLI', status: 'failed', detail: 'Codex CLI 配置缺少 API Key' };
|
|
2228
|
+
|
|
2229
|
+
const env = {
|
|
2230
|
+
...process.env,
|
|
2231
|
+
PATH: extendPathEnv(null),
|
|
2232
|
+
OPENAI_API_KEY: config.apiKey,
|
|
2233
|
+
NODE_TLS_REJECT_UNAUTHORIZED: '0',
|
|
2234
|
+
NODE_NO_WARNINGS: '1'
|
|
2235
|
+
};
|
|
2236
|
+
|
|
2237
|
+
return runCliTestCandidates('Codex CLI', [
|
|
2238
|
+
`${shellQuote(cliBinary)} exec --skip-git-repo-check ${shellQuote('请只回复 OK')}`,
|
|
2239
|
+
`${shellQuote(cliBinary)} exec ${shellQuote('请只回复 OK')}`
|
|
2240
|
+
], env);
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
async function testAdditionalCliConnections(args = {}, options = {}) {
|
|
2244
|
+
if (args['no-app-test'] || args.noAppTest) return;
|
|
2245
|
+
|
|
2246
|
+
const requested = new Set((options.only || ['claude', 'opencode', 'codex']).map(item => String(item)));
|
|
2247
|
+
const results = [];
|
|
2248
|
+
if (requested.has('claude')) results.push(testClaudeCodeCliConnection());
|
|
2249
|
+
if (requested.has('opencode')) results.push(testOpencodeCliConnection());
|
|
2250
|
+
if (requested.has('codex')) results.push(testCodexCliConnection());
|
|
2251
|
+
if (results.length === 0) return;
|
|
2252
|
+
|
|
2253
|
+
console.log(chalk.cyan('\n附加测试: 其他 CLI 连接...'));
|
|
2254
|
+
for (const result of results) {
|
|
2255
|
+
if (result.status === 'success') {
|
|
2256
|
+
console.log(chalk.green(`✅ ${result.name}: ${result.detail}`));
|
|
2257
|
+
} else if (result.status === 'skipped') {
|
|
2258
|
+
console.log(chalk.gray(`○ ${result.name}: ${result.detail}`));
|
|
2259
|
+
} else {
|
|
2260
|
+
console.log(chalk.red(`❌ ${result.name}: ${result.detail}`));
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
|
|
1819
2265
|
function getNodeMajor(versionOutput) {
|
|
1820
2266
|
const match = String(versionOutput || '').trim().match(/^v?(\d+)/);
|
|
1821
2267
|
return match ? Number(match[1]) : null;
|
|
@@ -1868,11 +2314,21 @@ function findCompatibleNode(minMajor = 22) {
|
|
|
1868
2314
|
function autoFixConfig(paths) {
|
|
1869
2315
|
try {
|
|
1870
2316
|
const config = readConfig(paths.openclawConfig);
|
|
1871
|
-
if (!config
|
|
2317
|
+
if (!config) return;
|
|
2318
|
+
|
|
2319
|
+
config.models = config.models || {};
|
|
2320
|
+
config.models.providers = config.models.providers || {};
|
|
1872
2321
|
|
|
1873
2322
|
let changed = false;
|
|
1874
2323
|
const codexProviderName = API_CONFIG.codex?.providerName;
|
|
1875
2324
|
const codexProvider = codexProviderName && config.models.providers[codexProviderName];
|
|
2325
|
+
const originalModelJson = JSON.stringify(config.agents?.defaults?.model ?? null);
|
|
2326
|
+
ensureConfigStructure(config);
|
|
2327
|
+
sanitizeDefaultModelSelection(config);
|
|
2328
|
+
const nextModelJson = JSON.stringify(config.agents?.defaults?.model ?? null);
|
|
2329
|
+
if (originalModelJson !== nextModelJson) {
|
|
2330
|
+
changed = true;
|
|
2331
|
+
}
|
|
1876
2332
|
|
|
1877
2333
|
// 修复: openai-responses → openai-completions(云翼服务器不支持 /v1/responses)
|
|
1878
2334
|
if (codexProvider && codexProvider.api === 'openai-responses') {
|
|
@@ -1888,7 +2344,7 @@ function autoFixConfig(paths) {
|
|
|
1888
2344
|
|
|
1889
2345
|
if (changed) {
|
|
1890
2346
|
writeConfig(paths.openclawConfig, config);
|
|
1891
|
-
console.log(chalk.green('✓
|
|
2347
|
+
console.log(chalk.green('✓ 已自动修复配置(模型结构 / API 协议)'));
|
|
1892
2348
|
}
|
|
1893
2349
|
} catch { /* ignore */ }
|
|
1894
2350
|
}
|
|
@@ -2632,7 +3088,7 @@ async function presetClaude(paths, args = {}) {
|
|
|
2632
3088
|
ensureGatewaySettings(config);
|
|
2633
3089
|
writeConfigWithSync(paths, config);
|
|
2634
3090
|
updateAuthProfilesWithSync(paths, providerName, apiKey);
|
|
2635
|
-
const extSynced = syncExternalTools('claude', baseUrl, apiKey);
|
|
3091
|
+
const extSynced = syncExternalTools('claude', baseUrl, apiKey, { claudeModelId: modelId, opencodeDefaultModelKey: `yunyi-claude/${modelId}` });
|
|
2636
3092
|
writeSpinner.succeed('配置写入完成');
|
|
2637
3093
|
|
|
2638
3094
|
console.log(chalk.green('\n✅ Claude 节点配置完成!'));
|
|
@@ -2826,7 +3282,7 @@ async function presetCodex(paths, args = {}) {
|
|
|
2826
3282
|
cleanupConflictingEnvVars(config, baseUrl, apiKey);
|
|
2827
3283
|
writeConfigWithSync(paths, config);
|
|
2828
3284
|
updateAuthProfilesWithSync(paths, providerName, apiKey);
|
|
2829
|
-
const extSynced2 = syncExternalTools('codex', baseUrl, apiKey);
|
|
3285
|
+
const extSynced2 = syncExternalTools('codex', baseUrl, apiKey, { modelId });
|
|
2830
3286
|
writeSpinner2.succeed('配置写入完成');
|
|
2831
3287
|
|
|
2832
3288
|
console.log(chalk.green('\n✅ Codex 节点配置完成!'));
|
|
@@ -3012,8 +3468,9 @@ async function autoActivate(paths, args = {}) {
|
|
|
3012
3468
|
updateAuthProfilesWithSync(paths, claudeProviderName, apiKey);
|
|
3013
3469
|
updateAuthProfilesWithSync(paths, codexProviderName, apiKey);
|
|
3014
3470
|
const extSynced = [];
|
|
3015
|
-
|
|
3016
|
-
try { syncExternalTools('
|
|
3471
|
+
const opencodeDefaultModelKey = isClaudePrimary ? `yunyi-claude/${claudeModelId}` : `yunyi-codex/${codexModelId}`;
|
|
3472
|
+
try { extSynced.push(...syncExternalTools('claude', claudeBaseUrl, apiKey, { codexBaseUrl, claudeModelId, opencodeDefaultModelKey })); } catch { /* ignore */ }
|
|
3473
|
+
try { extSynced.push(...syncExternalTools('codex', codexBaseUrl, apiKey, { modelId: codexModelId })); } catch { /* ignore */ }
|
|
3017
3474
|
writeSpinner.succeed('配置写入完成');
|
|
3018
3475
|
|
|
3019
3476
|
// ---- 输出结果 ----
|
|
@@ -3025,7 +3482,7 @@ async function autoActivate(paths, args = {}) {
|
|
|
3025
3482
|
console.log(chalk.gray(' API Key: 已设置'));
|
|
3026
3483
|
if (extSynced.length > 0) console.log(chalk.gray(` 同步: ${extSynced.join(', ')}`));
|
|
3027
3484
|
console.log(chalk.gray(' 若遇 certificate 报错,请新开终端或执行 source ~/.zshrc 后重试(已放宽 TLS 校验)'));
|
|
3028
|
-
console.log(chalk.gray(
|
|
3485
|
+
console.log(chalk.gray(` 使用 OpenCode 时可在界面中切换 ${getOpencodeSwitchHint()};Codex 仅支持 GPT`));
|
|
3029
3486
|
|
|
3030
3487
|
const gwPort = config.gateway?.port || 18789;
|
|
3031
3488
|
const gwToken = config.gateway?.auth?.token;
|
|
@@ -3056,8 +3513,7 @@ async function autoActivate(paths, args = {}) {
|
|
|
3056
3513
|
// ============ 单独配置 Claude Code CLI ============
|
|
3057
3514
|
async function activateClaudeCode(paths, args = {}) {
|
|
3058
3515
|
console.log(chalk.cyan.bold('\n🔧 配置 Claude Code CLI\n'));
|
|
3059
|
-
|
|
3060
|
-
const claudeApiConfig = API_CONFIG.claude;
|
|
3516
|
+
const selectedModel = await promptClaudeModelSelection(args);
|
|
3061
3517
|
|
|
3062
3518
|
// ---- 测速选节点 ----
|
|
3063
3519
|
const shouldTest = !(args['no-test'] || args.noTest);
|
|
@@ -3118,25 +3574,28 @@ async function activateClaudeCode(paths, args = {}) {
|
|
|
3118
3574
|
// ---- 写入配置 ----
|
|
3119
3575
|
const claudeBaseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
|
|
3120
3576
|
const writeSpinner = ora({ text: '正在写入 Claude Code 配置...', spinner: 'dots' }).start();
|
|
3121
|
-
writeClaudeCodeSettings(claudeBaseUrl, apiKey);
|
|
3577
|
+
writeClaudeCodeSettings(claudeBaseUrl, apiKey, selectedModel.id);
|
|
3122
3578
|
writeSpinner.succeed('Claude Code 配置写入完成');
|
|
3123
3579
|
|
|
3124
3580
|
console.log(chalk.green('\n✅ Claude Code CLI 配置完成!'));
|
|
3125
3581
|
console.log(chalk.cyan(` Base URL: ${claudeBaseUrl}`));
|
|
3126
|
-
console.log(chalk.gray(` 模型:
|
|
3582
|
+
console.log(chalk.gray(` 模型: ${selectedModel.name} (${selectedModel.id})`));
|
|
3127
3583
|
console.log(chalk.gray(' API Key: 已设置'));
|
|
3128
3584
|
console.log(chalk.gray('\n 已写入:'));
|
|
3129
3585
|
console.log(chalk.gray(' • ~/.claude/settings.json'));
|
|
3130
3586
|
console.log(chalk.gray(' • ~/.claude.json (跳过 onboarding)'));
|
|
3131
|
-
console.log(chalk.gray(' • shell 环境变量 (ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN)'));
|
|
3587
|
+
console.log(chalk.gray(' • shell 环境变量 (ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_MODEL)'));
|
|
3132
3588
|
console.log(chalk.yellow('\n 提示: 请重新打开终端或执行 source ~/.zshrc 使环境变量生效'));
|
|
3589
|
+
|
|
3590
|
+
if (await confirmImmediateTest(args, '是否立即测试 Claude Code CLI 连接?')) {
|
|
3591
|
+
await testAdditionalCliConnections(args, { only: ['claude'] });
|
|
3592
|
+
}
|
|
3133
3593
|
}
|
|
3134
3594
|
|
|
3135
3595
|
// ============ 单独配置 Opencode ============
|
|
3136
3596
|
async function activateOpencode(paths, args = {}) {
|
|
3137
3597
|
console.log(chalk.cyan.bold('\n🔧 配置 Opencode\n'));
|
|
3138
|
-
|
|
3139
|
-
const claudeApiConfig = API_CONFIG.claude;
|
|
3598
|
+
const defaultModel = await promptOpencodeDefaultModelSelection(args);
|
|
3140
3599
|
|
|
3141
3600
|
// ---- 测速选节点 ----
|
|
3142
3601
|
const shouldTest = !(args['no-test'] || args.noTest);
|
|
@@ -3197,21 +3656,24 @@ async function activateOpencode(paths, args = {}) {
|
|
|
3197
3656
|
// ---- 写入配置 ----
|
|
3198
3657
|
const claudeBaseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
|
|
3199
3658
|
const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
|
|
3200
|
-
const modelId = 'claude-sonnet-4-6';
|
|
3201
3659
|
const writeSpinner = ora({ text: '正在写入 Opencode 配置...', spinner: 'dots' }).start();
|
|
3202
|
-
|
|
3660
|
+
writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, defaultModel.modelKey);
|
|
3203
3661
|
writeSpinner.succeed('Opencode 配置写入完成');
|
|
3204
3662
|
|
|
3205
3663
|
console.log(chalk.green('\n✅ Opencode 配置完成!'));
|
|
3206
|
-
console.log(chalk.cyan(` Claude: ${claudeBaseUrl} →
|
|
3207
|
-
console.log(chalk.cyan(` Codex: ${codexBaseUrl} →
|
|
3208
|
-
console.log(chalk.gray(` 默认模型:
|
|
3664
|
+
console.log(chalk.cyan(` Claude: ${claudeBaseUrl} → ${getClaudeSwitchHint()}`));
|
|
3665
|
+
console.log(chalk.cyan(` Codex: ${codexBaseUrl} → ${CODEX_MODELS.map(model => model.name).join(' / ')}`));
|
|
3666
|
+
console.log(chalk.gray(` 默认模型: ${defaultModel.modelKey}`));
|
|
3209
3667
|
console.log(chalk.gray(' API Key: 已设置'));
|
|
3210
3668
|
console.log(chalk.gray('\n 已写入:'));
|
|
3211
3669
|
console.log(chalk.gray(' • ~/.config/opencode/opencode.json (CLI + 桌面版)'));
|
|
3212
3670
|
console.log(chalk.gray(' • ~/.codex/config.toml (model_providers)'));
|
|
3213
3671
|
console.log(chalk.gray(' • ~/.codex/auth.json (API Keys)'));
|
|
3214
|
-
console.log(chalk.yellow(
|
|
3672
|
+
console.log(chalk.yellow(`\n 切换模型: 在 opencode 内使用 /model 命令切换 (${getOpencodeSwitchHint()})`));
|
|
3673
|
+
|
|
3674
|
+
if (await confirmImmediateTest(args, '是否立即测试 Opencode CLI 连接?')) {
|
|
3675
|
+
await testAdditionalCliConnections(args, { only: ['opencode'] });
|
|
3676
|
+
}
|
|
3215
3677
|
}
|
|
3216
3678
|
|
|
3217
3679
|
// ============ 单独配置 Codex CLI ============
|
|
@@ -3219,7 +3681,7 @@ async function activateCodex(paths, args = {}) {
|
|
|
3219
3681
|
console.log(chalk.cyan.bold('\n🔧 配置 Codex CLI\n'));
|
|
3220
3682
|
|
|
3221
3683
|
// ---- 选模型 ----
|
|
3222
|
-
let modelId =
|
|
3684
|
+
let modelId = getDefaultCodexModel().id;
|
|
3223
3685
|
if (CODEX_MODELS.length > 1) {
|
|
3224
3686
|
const { selected } = await inquirer.prompt([{
|
|
3225
3687
|
type: 'list',
|
|
@@ -3301,6 +3763,10 @@ async function activateCodex(paths, args = {}) {
|
|
|
3301
3763
|
console.log(chalk.gray(' • ~/.codex/config.toml (model + model_providers)'));
|
|
3302
3764
|
console.log(chalk.gray(' • ~/.codex/auth.json (OPENAI_API_KEY)'));
|
|
3303
3765
|
console.log(chalk.yellow('\n 提示: 请重新打开终端使配置生效'));
|
|
3766
|
+
|
|
3767
|
+
if (await confirmImmediateTest(args, '是否立即测试 Codex CLI 连接?')) {
|
|
3768
|
+
await testAdditionalCliConnections(args, { only: ['codex'] });
|
|
3769
|
+
}
|
|
3304
3770
|
}
|
|
3305
3771
|
|
|
3306
3772
|
// ============ yycode 精简模式(零交互一键配置) ============
|
|
@@ -3446,8 +3912,8 @@ async function yycodeQuickSetup(paths) {
|
|
|
3446
3912
|
writeConfigWithSync(paths, config);
|
|
3447
3913
|
updateAuthProfilesWithSync(paths, claudeProviderName, apiKey);
|
|
3448
3914
|
updateAuthProfilesWithSync(paths, codexProviderName, apiKey);
|
|
3449
|
-
try { syncExternalTools('claude', claudeBaseUrl, apiKey, { codexBaseUrl }); } catch { /* ignore */ }
|
|
3450
|
-
try { syncExternalTools('codex', codexBaseUrl, apiKey); } catch { /* ignore */ }
|
|
3915
|
+
try { syncExternalTools('claude', claudeBaseUrl, apiKey, { codexBaseUrl, claudeModelId, opencodeDefaultModelKey: `yunyi-codex/${codexModelId}` }); } catch { /* ignore */ }
|
|
3916
|
+
try { syncExternalTools('codex', codexBaseUrl, apiKey, { modelId: codexModelId }); } catch { /* ignore */ }
|
|
3451
3917
|
writeSpinner.succeed('配置写入完成');
|
|
3452
3918
|
|
|
3453
3919
|
// ---- 结果 ----
|
|
@@ -3457,7 +3923,7 @@ async function yycodeQuickSetup(paths) {
|
|
|
3457
3923
|
console.log(chalk.cyan(` Codex (主): ${codexBaseUrl}`));
|
|
3458
3924
|
console.log(chalk.gray(` 模型: ${codexModel.name}`));
|
|
3459
3925
|
console.log(chalk.gray(' API Key: 已设置'));
|
|
3460
|
-
console.log(chalk.gray(' 同步: Claude Code settings, Codex CLI config'));
|
|
3926
|
+
console.log(chalk.gray(' 同步: Claude Code settings, Opencode config, Codex CLI config'));
|
|
3461
3927
|
console.log('');
|
|
3462
3928
|
}
|
|
3463
3929
|
|
|
@@ -3529,7 +3995,7 @@ async function main() {
|
|
|
3529
3995
|
{ name: ' 配置 Opencode', value: 'activate_opencode' },
|
|
3530
3996
|
{ name: ' 配置 Codex CLI', value: 'activate_codex' },
|
|
3531
3997
|
new inquirer.Separator(' -- 工具 --'),
|
|
3532
|
-
{ name: '
|
|
3998
|
+
{ name: ' 切换 OpenClaw 模型', value: 'switch_model' },
|
|
3533
3999
|
{ name: ` 权限管理${getToolsProfileTag(paths)}`, value: 'tools_profile' },
|
|
3534
4000
|
{ name: ' 测试连接', value: 'test_connection' },
|
|
3535
4001
|
{ name: ' 查看配置', value: 'view_config' },
|
|
@@ -3774,9 +4240,9 @@ async function activate(paths, type) {
|
|
|
3774
4240
|
}
|
|
3775
4241
|
|
|
3776
4242
|
|
|
3777
|
-
// ============
|
|
4243
|
+
// ============ 切换 OpenClaw 模型 ============
|
|
3778
4244
|
async function switchModel(paths) {
|
|
3779
|
-
console.log(chalk.cyan('🔄
|
|
4245
|
+
console.log(chalk.cyan('🔄 切换 OpenClaw 模型\n'));
|
|
3780
4246
|
|
|
3781
4247
|
const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
|
|
3782
4248
|
const primary = config.agents?.defaults?.model?.primary || '';
|
|
@@ -3869,7 +4335,7 @@ async function switchModel(paths) {
|
|
|
3869
4335
|
const { selected } = await inquirer.prompt([{
|
|
3870
4336
|
type: 'list',
|
|
3871
4337
|
name: 'selected',
|
|
3872
|
-
message: '
|
|
4338
|
+
message: '选择 OpenClaw 模型:',
|
|
3873
4339
|
default: primaryInPreset ? primary : undefined,
|
|
3874
4340
|
pageSize: 15,
|
|
3875
4341
|
choices,
|
|
@@ -3879,7 +4345,7 @@ async function switchModel(paths) {
|
|
|
3879
4345
|
|
|
3880
4346
|
if (selected === '__other__') {
|
|
3881
4347
|
if (otherModels.length === 0) {
|
|
3882
|
-
|
|
4348
|
+
console.log(chalk.gray('\n当前 OpenClaw 配置中没有其他模型,仅有预设模型可用'));
|
|
3883
4349
|
return;
|
|
3884
4350
|
}
|
|
3885
4351
|
const otherChoices = otherModels.map(o => {
|
|
@@ -4038,7 +4504,7 @@ async function manageToolsProfile(paths) {
|
|
|
4038
4504
|
|
|
4039
4505
|
// ============ 测试连接 ============
|
|
4040
4506
|
async function testConnection(paths, args = {}) {
|
|
4041
|
-
console.log(chalk.cyan('🧪 测试 OpenClaw
|
|
4507
|
+
console.log(chalk.cyan('🧪 测试 OpenClaw / 各 CLI 连接\n'));
|
|
4042
4508
|
invalidateGatewayEnvCache();
|
|
4043
4509
|
|
|
4044
4510
|
const config = readConfig(paths.openclawConfig);
|
|
@@ -4051,7 +4517,7 @@ async function testConnection(paths, args = {}) {
|
|
|
4051
4517
|
// 检查当前激活的是哪个
|
|
4052
4518
|
let primary = config.agents?.defaults?.model?.primary || '';
|
|
4053
4519
|
if (!primary.includes('/')) {
|
|
4054
|
-
console.log(chalk.yellow('⚠️
|
|
4520
|
+
console.log(chalk.yellow('⚠️ 主模型未设置,请先通过「切换 OpenClaw 模型」或「一键配置」设置主模型'));
|
|
4055
4521
|
return;
|
|
4056
4522
|
}
|
|
4057
4523
|
|
|
@@ -4300,6 +4766,8 @@ async function testConnection(paths, args = {}) {
|
|
|
4300
4766
|
}
|
|
4301
4767
|
} catch (error) {
|
|
4302
4768
|
console.log(chalk.red(`❌ 测试失败: ${error.message}`));
|
|
4769
|
+
} finally {
|
|
4770
|
+
await testAdditionalCliConnections(args);
|
|
4303
4771
|
}
|
|
4304
4772
|
}
|
|
4305
4773
|
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"models": {
|
|
26
26
|
"claude": [
|
|
27
27
|
{ "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6" },
|
|
28
|
-
{ "id": "claude-opus-4-6", "name": "Claude Opus 4.6 (
|
|
28
|
+
{ "id": "claude-opus-4-6", "name": "Claude Opus 4.6 (待稳定)" }
|
|
29
29
|
],
|
|
30
30
|
"codex": [
|
|
31
31
|
{ "id": "gpt-5.4", "name": "GPT 5.4" },
|