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.
- package/bin/yymaxapi.js +424 -72
- 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
|
|
3161
|
-
const
|
|
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:
|
|
5550
|
+
error: readableError,
|
|
5551
|
+
rawError: (cleanStderr || fallbackOutput || plainCombined || error.message || 'CLI 执行失败').trim()
|
|
5200
5552
|
});
|
|
5201
5553
|
return;
|
|
5202
5554
|
}
|