yymaxapi 1.0.81 → 1.0.82

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.
Files changed (2) hide show
  1. package/bin/yymaxapi.js +424 -72
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -1454,6 +1454,351 @@ function coerceModelsRecord(value) {
1454
1454
  return record;
1455
1455
  }
1456
1456
 
1457
+ const YUNYI_PROVIDER_ALIASES = {
1458
+ claude: new Set(['claude-yunyi', 'yunyi-claude', 'claude_yunyi']),
1459
+ codex: new Set(['yunyi', 'yunyi-codex', 'codex-yunyi', 'codex_yunyi'])
1460
+ };
1461
+
1462
+ function looksLikeApiKey(value) {
1463
+ const text = String(value || '').trim();
1464
+ if (!text || text.includes('/') || text.includes(' ')) return false;
1465
+ if (/^(sk-|STP|YK)/i.test(text)) return true;
1466
+ return /^[A-Za-z0-9._-]{16,}$/.test(text);
1467
+ }
1468
+
1469
+ function isLikelyYunyiBaseUrl(baseUrl) {
1470
+ const urlText = String(baseUrl || '').trim();
1471
+ if (!urlText) return false;
1472
+ try {
1473
+ const hostname = new URL(urlText).hostname.toLowerCase();
1474
+ return hostname.includes('yunyi')
1475
+ || hostname.includes('rdzhvip.com')
1476
+ || hostname === '47.99.42.193'
1477
+ || hostname === '47.97.100.10';
1478
+ } catch {
1479
+ return /yunyi|rdzhvip\.com|47\.99\.42\.193|47\.97\.100\.10/i.test(urlText);
1480
+ }
1481
+ }
1482
+
1483
+ function isYunyiProviderEntry(name, providerConfig, type) {
1484
+ const cfg = providerConfig || {};
1485
+ const providerName = String(name || '').trim();
1486
+ if (YUNYI_PROVIDER_ALIASES[type]?.has(providerName)) return true;
1487
+
1488
+ const baseUrl = String(cfg.baseUrl || cfg.base_url || '').trim();
1489
+ const api = String(cfg.api || '').trim();
1490
+ if (!isLikelyYunyiBaseUrl(baseUrl)) return false;
1491
+
1492
+ if (type === 'claude') {
1493
+ return api.startsWith('anthropic') || /\/claude(?:\/|$)/.test(baseUrl);
1494
+ }
1495
+ return api.startsWith('openai') || /\/codex(?:\/|$)/.test(baseUrl);
1496
+ }
1497
+
1498
+ function normalizeProviderModelEntry(model, fallbackContext = {}) {
1499
+ if (!model) return null;
1500
+ if (typeof model === 'string') {
1501
+ return {
1502
+ id: model,
1503
+ name: model,
1504
+ contextWindow: fallbackContext.contextWindow,
1505
+ maxTokens: fallbackContext.maxTokens
1506
+ };
1507
+ }
1508
+ if (typeof model !== 'object') return null;
1509
+
1510
+ const id = String(model.id || model.model || '').trim();
1511
+ if (!id) return null;
1512
+ return {
1513
+ ...model,
1514
+ id,
1515
+ name: model.name || id,
1516
+ contextWindow: model.contextWindow || fallbackContext.contextWindow,
1517
+ maxTokens: model.maxTokens || fallbackContext.maxTokens
1518
+ };
1519
+ }
1520
+
1521
+ function mergeProviderModels(existingModels = [], incomingModels = [], fallbackContext = {}) {
1522
+ const merged = [];
1523
+ const seen = new Set();
1524
+
1525
+ for (const item of [...existingModels, ...incomingModels]) {
1526
+ const normalized = normalizeProviderModelEntry(item, fallbackContext);
1527
+ if (!normalized || seen.has(normalized.id)) continue;
1528
+ seen.add(normalized.id);
1529
+ merged.push(normalized);
1530
+ }
1531
+
1532
+ return merged;
1533
+ }
1534
+
1535
+ function renameProviderReferencesInConfig(config, oldName, newName) {
1536
+ if (!oldName || !newName || oldName === newName) return false;
1537
+ let changed = false;
1538
+
1539
+ if (config?.agents?.defaults?.models) {
1540
+ for (const key of Object.keys(config.agents.defaults.models)) {
1541
+ if (!key.startsWith(`${oldName}/`)) continue;
1542
+ const nextKey = `${newName}/${key.slice(oldName.length + 1)}`;
1543
+ if (!config.agents.defaults.models[nextKey]) {
1544
+ const entry = { ...(config.agents.defaults.models[key] || {}) };
1545
+ if (!entry.alias || entry.alias === oldName) entry.alias = newName;
1546
+ config.agents.defaults.models[nextKey] = entry;
1547
+ }
1548
+ delete config.agents.defaults.models[key];
1549
+ changed = true;
1550
+ }
1551
+ }
1552
+
1553
+ if (config?.agents?.defaults?.model) {
1554
+ const modelState = config.agents.defaults.model;
1555
+ if (typeof modelState.primary === 'string' && modelState.primary.startsWith(`${oldName}/`)) {
1556
+ modelState.primary = `${newName}/${modelState.primary.slice(oldName.length + 1)}`;
1557
+ changed = true;
1558
+ }
1559
+ if (Array.isArray(modelState.fallbacks)) {
1560
+ const nextFallbacks = modelState.fallbacks.map(modelKey => (
1561
+ typeof modelKey === 'string' && modelKey.startsWith(`${oldName}/`)
1562
+ ? `${newName}/${modelKey.slice(oldName.length + 1)}`
1563
+ : modelKey
1564
+ ));
1565
+ if (JSON.stringify(nextFallbacks) !== JSON.stringify(modelState.fallbacks)) {
1566
+ modelState.fallbacks = nextFallbacks;
1567
+ changed = true;
1568
+ }
1569
+ }
1570
+ }
1571
+
1572
+ if (config?.auth?.profiles) {
1573
+ for (const key of Object.keys(config.auth.profiles)) {
1574
+ if (!key.startsWith(`${oldName}:`)) continue;
1575
+ const suffix = key.slice(oldName.length + 1);
1576
+ const nextKey = `${newName}:${suffix}`;
1577
+ const profile = { ...(config.auth.profiles[key] || {}) };
1578
+ profile.provider = newName;
1579
+ config.auth.profiles[nextKey] = profile;
1580
+ delete config.auth.profiles[key];
1581
+ changed = true;
1582
+ }
1583
+ }
1584
+
1585
+ return changed;
1586
+ }
1587
+
1588
+ function suggestConflictProviderName(config, baseName) {
1589
+ const providers = config?.models?.providers || {};
1590
+ let candidate = `${baseName}-legacy`;
1591
+ let index = 2;
1592
+ while (providers[candidate]) {
1593
+ candidate = `${baseName}-legacy-${index}`;
1594
+ index += 1;
1595
+ }
1596
+ return candidate;
1597
+ }
1598
+
1599
+ function sanitizeProviderConfig(name, providerConfig, typeHint = null) {
1600
+ if (!providerConfig || typeof providerConfig !== 'object') return false;
1601
+ let changed = false;
1602
+
1603
+ if (providerConfig.base_url && !providerConfig.baseUrl) {
1604
+ providerConfig.baseUrl = providerConfig.base_url;
1605
+ delete providerConfig.base_url;
1606
+ changed = true;
1607
+ }
1608
+
1609
+ if (providerConfig.model && !providerConfig.models) {
1610
+ providerConfig.models = [providerConfig.model];
1611
+ delete providerConfig.model;
1612
+ changed = true;
1613
+ }
1614
+
1615
+ const inferredType = typeHint
1616
+ || (String(providerConfig.api || '').startsWith('anthropic') ? 'claude'
1617
+ : String(providerConfig.api || '').startsWith('openai') ? 'codex'
1618
+ : null);
1619
+ const fallbackContext = inferredType === 'claude'
1620
+ ? { contextWindow: API_CONFIG.claude?.contextWindow, maxTokens: API_CONFIG.claude?.maxTokens }
1621
+ : inferredType === 'codex'
1622
+ ? { contextWindow: API_CONFIG.codex?.contextWindow, maxTokens: API_CONFIG.codex?.maxTokens }
1623
+ : {};
1624
+
1625
+ if (providerConfig.models) {
1626
+ const nextModels = mergeProviderModels(providerConfig.models, [], fallbackContext);
1627
+ if (JSON.stringify(nextModels) !== JSON.stringify(providerConfig.models)) {
1628
+ providerConfig.models = nextModels;
1629
+ changed = true;
1630
+ }
1631
+ }
1632
+
1633
+ if (!providerConfig.auth && providerConfig.apiKey) {
1634
+ providerConfig.auth = DEFAULT_AUTH_MODE;
1635
+ changed = true;
1636
+ } else if (typeof providerConfig.auth === 'string') {
1637
+ const authValue = providerConfig.auth.trim();
1638
+ if (authValue === 'api_key') {
1639
+ providerConfig.auth = DEFAULT_AUTH_MODE;
1640
+ changed = true;
1641
+ } else if (!['api-key', 'token', 'none'].includes(authValue)) {
1642
+ if (!providerConfig.apiKey && looksLikeApiKey(authValue)) {
1643
+ providerConfig.apiKey = authValue;
1644
+ providerConfig.auth = DEFAULT_AUTH_MODE;
1645
+ changed = true;
1646
+ } else if (providerConfig.apiKey) {
1647
+ providerConfig.auth = DEFAULT_AUTH_MODE;
1648
+ changed = true;
1649
+ }
1650
+ }
1651
+ }
1652
+
1653
+ if (!providerConfig.headers || Array.isArray(providerConfig.headers) || typeof providerConfig.headers !== 'object') {
1654
+ providerConfig.headers = {};
1655
+ changed = true;
1656
+ }
1657
+
1658
+ if (providerConfig.authHeader === undefined) {
1659
+ if (String(providerConfig.api || '').startsWith('openai')) {
1660
+ providerConfig.authHeader = true;
1661
+ changed = true;
1662
+ } else if (String(providerConfig.api || '').startsWith('anthropic')) {
1663
+ providerConfig.authHeader = false;
1664
+ changed = true;
1665
+ }
1666
+ }
1667
+
1668
+ return changed;
1669
+ }
1670
+
1671
+ function sanitizeConfigAuthProfiles(config) {
1672
+ if (!config.auth) config.auth = {};
1673
+ if (!config.auth.profiles || typeof config.auth.profiles !== 'object' || Array.isArray(config.auth.profiles)) {
1674
+ config.auth.profiles = {};
1675
+ return true;
1676
+ }
1677
+
1678
+ let changed = false;
1679
+ for (const [key, profile] of Object.entries(config.auth.profiles)) {
1680
+ const provider = (profile && typeof profile === 'object' && profile.provider) || key.split(':')[0];
1681
+ if (!provider) {
1682
+ delete config.auth.profiles[key];
1683
+ changed = true;
1684
+ continue;
1685
+ }
1686
+ const nextProfile = {
1687
+ provider,
1688
+ mode: (profile && typeof profile === 'object' && (profile.mode === 'api-key' ? 'api_key' : profile.mode)) || 'api_key'
1689
+ };
1690
+ if (JSON.stringify(profile) !== JSON.stringify(nextProfile)) {
1691
+ config.auth.profiles[key] = nextProfile;
1692
+ changed = true;
1693
+ }
1694
+ }
1695
+ return changed;
1696
+ }
1697
+
1698
+ function repairYunyiProviderAliases(config) {
1699
+ if (!config?.models?.providers) return { changed: false, renamedProviders: [] };
1700
+
1701
+ let changed = false;
1702
+ const renamedProviders = [];
1703
+ const providers = config.models.providers;
1704
+
1705
+ for (const type of ['claude', 'codex']) {
1706
+ const canonicalName = API_CONFIG[type]?.providerName;
1707
+ if (!canonicalName) continue;
1708
+
1709
+ const candidateEntries = Object.entries(providers).filter(([name, providerConfig]) => isYunyiProviderEntry(name, providerConfig, type));
1710
+ if (candidateEntries.length === 0) continue;
1711
+
1712
+ if (!providers[canonicalName]) {
1713
+ providers[canonicalName] = {};
1714
+ changed = true;
1715
+ }
1716
+
1717
+ sanitizeProviderConfig(canonicalName, providers[canonicalName], type);
1718
+
1719
+ for (const [name, providerConfig] of candidateEntries) {
1720
+ sanitizeProviderConfig(name, providerConfig, type);
1721
+ if (name === canonicalName) continue;
1722
+
1723
+ const target = providers[canonicalName];
1724
+ if (!target.baseUrl && providerConfig.baseUrl) target.baseUrl = providerConfig.baseUrl;
1725
+ if (!target.api && providerConfig.api) target.api = providerConfig.api;
1726
+ if (!target.apiKey && providerConfig.apiKey) target.apiKey = providerConfig.apiKey;
1727
+ if (!target.auth && providerConfig.auth) target.auth = providerConfig.auth;
1728
+ if (target.authHeader === undefined && providerConfig.authHeader !== undefined) target.authHeader = providerConfig.authHeader;
1729
+ if ((!target.headers || Object.keys(target.headers).length === 0) && providerConfig.headers && typeof providerConfig.headers === 'object') {
1730
+ target.headers = { ...providerConfig.headers };
1731
+ }
1732
+ target.models = mergeProviderModels(target.models, providerConfig.models, type === 'claude'
1733
+ ? { contextWindow: API_CONFIG.claude?.contextWindow, maxTokens: API_CONFIG.claude?.maxTokens }
1734
+ : { contextWindow: API_CONFIG.codex?.contextWindow, maxTokens: API_CONFIG.codex?.maxTokens });
1735
+
1736
+ delete providers[name];
1737
+ renamedProviders.push({ from: name, to: canonicalName });
1738
+ renameProviderReferencesInConfig(config, name, canonicalName);
1739
+ changed = true;
1740
+ }
1741
+
1742
+ sanitizeProviderConfig(canonicalName, providers[canonicalName], type);
1743
+ }
1744
+
1745
+ return { changed, renamedProviders };
1746
+ }
1747
+
1748
+ function reserveProviderName(config, desiredName, expectedType) {
1749
+ const providers = config?.models?.providers || {};
1750
+ const existing = providers[desiredName];
1751
+ if (!existing) return { changed: false, renamedProviders: [] };
1752
+ if (isYunyiProviderEntry(desiredName, existing, expectedType)) {
1753
+ return { changed: false, renamedProviders: [] };
1754
+ }
1755
+
1756
+ const nextName = suggestConflictProviderName(config, desiredName);
1757
+ providers[nextName] = existing;
1758
+ delete providers[desiredName];
1759
+ sanitizeProviderConfig(nextName, providers[nextName]);
1760
+ renameProviderReferencesInConfig(config, desiredName, nextName);
1761
+ return { changed: true, renamedProviders: [{ from: desiredName, to: nextName }] };
1762
+ }
1763
+
1764
+ function repairConfigProviders(config, options = {}) {
1765
+ if (!config.models) config.models = {};
1766
+ if (!config.models.providers || typeof config.models.providers !== 'object' || Array.isArray(config.models.providers)) {
1767
+ config.models.providers = {};
1768
+ }
1769
+
1770
+ let changed = false;
1771
+ const renamedProviders = [];
1772
+
1773
+ for (const type of ['claude', 'codex']) {
1774
+ const desiredName = API_CONFIG[type]?.providerName;
1775
+ if (!desiredName) continue;
1776
+ const reserved = reserveProviderName(config, desiredName, type);
1777
+ if (reserved.changed) {
1778
+ changed = true;
1779
+ renamedProviders.push(...reserved.renamedProviders);
1780
+ }
1781
+ }
1782
+
1783
+ const repairedAliases = repairYunyiProviderAliases(config);
1784
+ if (repairedAliases.changed) changed = true;
1785
+
1786
+ for (const [name, providerConfig] of Object.entries(config.models.providers)) {
1787
+ if (sanitizeProviderConfig(name, providerConfig)) {
1788
+ changed = true;
1789
+ }
1790
+ }
1791
+
1792
+ if (sanitizeConfigAuthProfiles(config)) {
1793
+ changed = true;
1794
+ }
1795
+
1796
+ return {
1797
+ changed,
1798
+ renamedProviders: [...renamedProviders, ...repairedAliases.renamedProviders]
1799
+ };
1800
+ }
1801
+
1457
1802
  function isValidModelRef(value) {
1458
1803
  return typeof value === 'string' && value.trim().includes('/');
1459
1804
  }
@@ -1702,6 +2047,32 @@ function syncMirroredAuthStores(paths) {
1702
2047
  }
1703
2048
  }
1704
2049
 
2050
+ function renameProviderInAuthStore(paths, oldProvider, newProvider) {
2051
+ if (!paths?.authProfiles || !oldProvider || !newProvider || oldProvider === newProvider) return false;
2052
+ const store = readAuthStore(paths.authProfiles);
2053
+ let changed = false;
2054
+
2055
+ for (const key of Object.keys(store.profiles)) {
2056
+ if (!key.startsWith(`${oldProvider}:`)) continue;
2057
+ const suffix = key.slice(oldProvider.length + 1);
2058
+ const nextKey = `${newProvider}:${suffix}`;
2059
+ const profile = { ...(store.profiles[key] || {}) };
2060
+ if (!store.profiles[nextKey]) {
2061
+ profile.provider = newProvider;
2062
+ store.profiles[nextKey] = profile;
2063
+ }
2064
+ delete store.profiles[key];
2065
+ changed = true;
2066
+ }
2067
+
2068
+ if (changed) {
2069
+ writeAuthStore(paths.authProfiles, store);
2070
+ syncMirroredAuthStores(paths);
2071
+ }
2072
+
2073
+ return changed;
2074
+ }
2075
+
1705
2076
  function pruneAuthProfilesExceptWithSync(paths, keepProviders = []) {
1706
2077
  const removed = pruneAuthProfilesExcept(paths.authProfiles, keepProviders);
1707
2078
  syncMirroredAuthStores(paths);
@@ -1713,6 +2084,16 @@ function updateAuthProfilesWithSync(paths, providerName, apiKey) {
1713
2084
  syncMirroredAuthStores(paths);
1714
2085
  }
1715
2086
 
2087
+ function applyConfigRepairsWithSync(config, paths) {
2088
+ const repairResult = repairConfigProviders(config);
2089
+ if (paths && repairResult.renamedProviders.length > 0) {
2090
+ for (const item of repairResult.renamedProviders) {
2091
+ renameProviderInAuthStore(paths, item.from, item.to);
2092
+ }
2093
+ }
2094
+ return repairResult;
2095
+ }
2096
+
1716
2097
  function pruneAuthProfilesByPrefix(authProfilesPath, prefixBase, keepProviders = []) {
1717
2098
  const keepSet = new Set(keepProviders);
1718
2099
  const store = readAuthStore(authProfilesPath);
@@ -2144,6 +2525,14 @@ function summarizeCliTestOutput(text) {
2144
2525
  return lines[0].slice(0, 160);
2145
2526
  }
2146
2527
 
2528
+ function buildReadableAgentError(text, fallback = '') {
2529
+ const cleaned = cleanCliTestOutput(text);
2530
+ if (!cleaned) return fallback;
2531
+ const lines = cleaned.split('\n').map(line => line.trim()).filter(Boolean);
2532
+ const preferred = lines.find(line => /agent failed before reply|all models failed|auth_permanent|auth issue|unauthorized|forbidden|missing environment variable|provider .* issue|invalid config/i.test(line));
2533
+ return (preferred || lines[0] || fallback).slice(0, 300);
2534
+ }
2535
+
2147
2536
  function looksLikeCliTestError(text) {
2148
2537
  const lower = cleanCliTestOutput(text).toLowerCase();
2149
2538
  if (!lower) return false;
@@ -2316,15 +2705,14 @@ function autoFixConfig(paths) {
2316
2705
  const config = readConfig(paths.openclawConfig);
2317
2706
  if (!config) return;
2318
2707
 
2319
- config.models = config.models || {};
2320
- config.models.providers = config.models.providers || {};
2321
-
2322
2708
  let changed = false;
2323
2709
  const codexProviderName = API_CONFIG.codex?.providerName;
2324
- const codexProvider = codexProviderName && config.models.providers[codexProviderName];
2325
2710
  const originalModelJson = JSON.stringify(config.agents?.defaults?.model ?? null);
2326
2711
  ensureConfigStructure(config);
2327
2712
  sanitizeDefaultModelSelection(config);
2713
+ const repairResult = applyConfigRepairsWithSync(config, paths);
2714
+ if (repairResult.changed) changed = true;
2715
+ const codexProvider = codexProviderName && config.models.providers?.[codexProviderName];
2328
2716
  const nextModelJson = JSON.stringify(config.agents?.defaults?.model ?? null);
2329
2717
  if (originalModelJson !== nextModelJson) {
2330
2718
  changed = true;
@@ -2847,26 +3235,7 @@ async function quickSetup(paths, args = {}) {
2847
3235
 
2848
3236
  let config = readConfig(paths.openclawConfig) || {};
2849
3237
  config = ensureConfigStructure(config);
2850
-
2851
- const existingProviders = Object.keys(config.models.providers || {});
2852
- const toRemove = existingProviders.filter(name => name !== providerName);
2853
- if (toRemove.length > 0 && !args.force) {
2854
- const { overwrite } = await inquirer.prompt([{
2855
- type: 'confirm',
2856
- name: 'overwrite',
2857
- message: `检测到已有中转配置: ${existingProviders.join(', ')},将仅保留 ${providerName}。是否继续?`,
2858
- default: false
2859
- }]);
2860
- if (!overwrite) {
2861
- console.log(chalk.gray('已取消'));
2862
- return;
2863
- }
2864
- }
2865
-
2866
- if (toRemove.length > 0) {
2867
- pruneProvidersExcept(config, [providerName]);
2868
- pruneAuthProfilesExceptWithSync(paths, [providerName]);
2869
- }
3238
+ const repairResult = applyConfigRepairsWithSync(config, paths);
2870
3239
 
2871
3240
  config.models.providers[providerName] = {
2872
3241
  baseUrl: normalizedBaseUrl,
@@ -2914,6 +3283,9 @@ async function quickSetup(paths, args = {}) {
2914
3283
  if (setPrimary) {
2915
3284
  console.log(chalk.yellow(` 主模型: ${modelKey}`));
2916
3285
  }
3286
+ if (repairResult.renamedProviders.length > 0) {
3287
+ console.log(chalk.gray(` 已保留并修复冲突 provider: ${repairResult.renamedProviders.map(item => `${item.from}→${item.to}`).join(', ')}`));
3288
+ }
2917
3289
  }
2918
3290
 
2919
3291
  async function presetClaude(paths, args = {}) {
@@ -2960,31 +3332,10 @@ async function presetClaude(paths, args = {}) {
2960
3332
  }
2961
3333
 
2962
3334
  const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
3335
+ const repairResult = applyConfigRepairsWithSync(config, paths);
2963
3336
 
2964
3337
  const providerName = (args['provider-name'] || args.provider || providerPrefix).toString().trim() || apiConfig.providerName;
2965
-
2966
- const existingProviders = Object.keys(config.models.providers || {});
2967
- const toRemove = existingProviders.filter(name => name !== providerName);
2968
-
2969
- if (toRemove.length > 0 && !args.force) {
2970
- const { overwrite } = await inquirer.prompt([{
2971
- type: 'confirm',
2972
- name: 'overwrite',
2973
- message: `检测到已有中转配置: ${existingProviders.join(', ')},将仅保留 ${providerName}。是否继续?`,
2974
- default: false
2975
- }]);
2976
- if (!overwrite) {
2977
- console.log(chalk.gray('已取消'));
2978
- return;
2979
- }
2980
- }
2981
-
2982
- const removedProviders = toRemove.length > 0
2983
- ? pruneProvidersExcept(config, [providerName])
2984
- : [];
2985
- if (removedProviders.length > 0) {
2986
- pruneAuthProfilesExceptWithSync(paths, [providerName]);
2987
- }
3338
+ const removedProviders = repairResult.renamedProviders.map(item => item.from);
2988
3339
 
2989
3340
  const baseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
2990
3341
 
@@ -3097,6 +3448,9 @@ async function presetClaude(paths, args = {}) {
3097
3448
  console.log(chalk.gray(` 模型: ${modelName}`));
3098
3449
  console.log(chalk.gray(' API Key: 已设置'));
3099
3450
  if (extSynced.length > 0) console.log(chalk.gray(` 同步: ${extSynced.join(', ')}`));
3451
+ if (repairResult.renamedProviders.length > 0) {
3452
+ console.log(chalk.gray(` 已保留并修复冲突 provider: ${repairResult.renamedProviders.map(item => `${item.from}→${item.to}`).join(', ')}`));
3453
+ }
3100
3454
 
3101
3455
  const shouldTestGateway = args.test !== undefined
3102
3456
  ? !['false', '0', 'no'].includes(String(args.test).toLowerCase())
@@ -3157,28 +3511,8 @@ async function presetCodex(paths, args = {}) {
3157
3511
  }
3158
3512
 
3159
3513
  const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
3160
- const existingProviders = Object.keys(config.models.providers || {});
3161
- const toRemove = existingProviders.filter(name => name !== providerName);
3162
-
3163
- if (toRemove.length > 0 && !args.force) {
3164
- const { overwrite } = await inquirer.prompt([{
3165
- type: 'confirm',
3166
- name: 'overwrite',
3167
- message: `检测到已有中转配置: ${existingProviders.join(', ')},将仅保留 ${providerName}。是否继续?`,
3168
- default: false
3169
- }]);
3170
- if (!overwrite) {
3171
- console.log(chalk.gray('已取消'));
3172
- return;
3173
- }
3174
- }
3175
-
3176
- const removedProviders = toRemove.length > 0
3177
- ? pruneProvidersExcept(config, [providerName])
3178
- : [];
3179
- if (removedProviders.length > 0) {
3180
- pruneAuthProfilesExceptWithSync(paths, [providerName]);
3181
- }
3514
+ const repairResult = applyConfigRepairsWithSync(config, paths);
3515
+ const removedProviders = repairResult.renamedProviders.map(item => item.from);
3182
3516
 
3183
3517
  const baseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
3184
3518
 
@@ -3291,6 +3625,9 @@ async function presetCodex(paths, args = {}) {
3291
3625
  console.log(chalk.gray(` 模型: ${modelName}`));
3292
3626
  console.log(chalk.gray(' API Key: 已设置'));
3293
3627
  if (extSynced2.length > 0) console.log(chalk.gray(` 同步: ${extSynced2.join(', ')}`));
3628
+ if (repairResult.renamedProviders.length > 0) {
3629
+ console.log(chalk.gray(` 已保留并修复冲突 provider: ${repairResult.renamedProviders.map(item => `${item.from}→${item.to}`).join(', ')}`));
3630
+ }
3294
3631
 
3295
3632
  const shouldTestGateway = args.test !== undefined
3296
3633
  ? !['false', '0', 'no'].includes(String(args.test).toLowerCase())
@@ -3399,6 +3736,7 @@ async function autoActivate(paths, args = {}) {
3399
3736
 
3400
3737
  // ---- 构建配置 ----
3401
3738
  const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
3739
+ applyConfigRepairsWithSync(config, paths);
3402
3740
 
3403
3741
  const claudeBaseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
3404
3742
  const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
@@ -4245,6 +4583,7 @@ async function switchModel(paths) {
4245
4583
  console.log(chalk.cyan('🔄 切换 OpenClaw 模型\n'));
4246
4584
 
4247
4585
  const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
4586
+ applyConfigRepairsWithSync(config, paths);
4248
4587
  const primary = config.agents?.defaults?.model?.primary || '';
4249
4588
  const providers = config.models?.providers || {};
4250
4589
 
@@ -4507,9 +4846,10 @@ async function testConnection(paths, args = {}) {
4507
4846
  console.log(chalk.cyan('🧪 测试 OpenClaw / 各 CLI 连接\n'));
4508
4847
  invalidateGatewayEnvCache();
4509
4848
 
4510
- const config = readConfig(paths.openclawConfig);
4849
+ const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
4850
+ applyConfigRepairsWithSync(config, paths);
4511
4851
 
4512
- if (!config) {
4852
+ if (!config || !config.models) {
4513
4853
  console.log(chalk.yellow('配置文件不存在,请先选择节点'));
4514
4854
  return;
4515
4855
  }
@@ -4688,6 +5028,10 @@ async function testConnection(paths, args = {}) {
4688
5028
  if (!cliResult.success) {
4689
5029
  console.log(chalk.red(`\n❌ Gateway CLI 测试失败`));
4690
5030
  console.log(chalk.red(` 错误: ${cliResult.error || '未知错误'}`));
5031
+ if (cliResult.rawError && cliResult.rawError !== cliResult.error) {
5032
+ console.log(chalk.gray(` 原始输出: ${cliResult.rawError.substring(0, 300)}`));
5033
+ }
5034
+ console.log(chalk.gray(' 可继续执行: openclaw logs --follow'));
4691
5035
  console.log(chalk.gray(` 将尝试使用 HTTP 端点测试...`));
4692
5036
  }
4693
5037
  }
@@ -5193,10 +5537,18 @@ function testGatewayViaAgent(model) {
5193
5537
  // stdout 有 JSON,走正常解析流程而非直接报错
5194
5538
  stdout = fallbackOutput;
5195
5539
  } else {
5540
+ const plainCmd = cmd.replace(/\s--json\b/, '');
5541
+ const plainResult = safeExec(plainCmd, execOpts);
5542
+ const plainCombined = `${plainResult.output || ''}\n${plainResult.stdout || ''}\n${plainResult.stderr || ''}`.trim();
5543
+ const readableError = buildReadableAgentError(
5544
+ `${cleanStderr}\n${fallbackOutput}\n${plainCombined}`,
5545
+ (error.message || 'CLI 执行失败').trim()
5546
+ );
5196
5547
  resolve({
5197
5548
  success: false,
5198
5549
  usedCli: true,
5199
- error: (cleanStderr || fallbackOutput || error.message || 'CLI 执行失败').trim()
5550
+ error: readableError,
5551
+ rawError: (cleanStderr || fallbackOutput || plainCombined || error.message || 'CLI 执行失败').trim()
5200
5552
  });
5201
5553
  return;
5202
5554
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.81",
3
+ "version": "1.0.82",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {