yymaxapi 1.0.84 → 1.0.85

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 +467 -7
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -115,7 +115,7 @@ const DEFAULT_API_CONFIG = {
115
115
  "api": "anthropic-messages",
116
116
  "contextWindow": 200000,
117
117
  "maxTokens": 8192,
118
- "providerName": "claude-yunyi"
118
+ "providerName": "yunyi-claude"
119
119
  },
120
120
  "codex": {
121
121
  "urlSuffix": "/codex",
@@ -1617,6 +1617,27 @@ function renameProviderReferencesInConfig(config, oldName, newName) {
1617
1617
  }
1618
1618
  }
1619
1619
 
1620
+ if (Array.isArray(config?.agents?.list)) {
1621
+ for (const agent of config.agents.list) {
1622
+ if (!agent || typeof agent !== 'object' || !agent.model || typeof agent.model !== 'object') continue;
1623
+ if (typeof agent.model.primary === 'string' && agent.model.primary.startsWith(`${oldName}/`)) {
1624
+ agent.model.primary = `${newName}/${agent.model.primary.slice(oldName.length + 1)}`;
1625
+ changed = true;
1626
+ }
1627
+ if (Array.isArray(agent.model.fallbacks)) {
1628
+ const nextFallbacks = agent.model.fallbacks.map(modelKey => (
1629
+ typeof modelKey === 'string' && modelKey.startsWith(`${oldName}/`)
1630
+ ? `${newName}/${modelKey.slice(oldName.length + 1)}`
1631
+ : modelKey
1632
+ ));
1633
+ if (JSON.stringify(nextFallbacks) !== JSON.stringify(agent.model.fallbacks)) {
1634
+ agent.model.fallbacks = nextFallbacks;
1635
+ changed = true;
1636
+ }
1637
+ }
1638
+ }
1639
+ }
1640
+
1620
1641
  return changed;
1621
1642
  }
1622
1643
 
@@ -1920,6 +1941,383 @@ function ensureConfigStructure(config) {
1920
1941
  return next;
1921
1942
  }
1922
1943
 
1944
+ const YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER = 'yunyi-claude';
1945
+ const YYMAXAPI_OPENCLAW_GPT_PROVIDER = 'yunyi';
1946
+ const YYMAXAPI_OPENCLAW_MAIN_AGENT_ID = 'main';
1947
+ const YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID = 'yunyi-claude';
1948
+ const YYMAXAPI_OPENCLAW_GPT_AGENT_ID = 'yunyi-gpt';
1949
+ const YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY = `${YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER}/claude-sonnet-4-6`;
1950
+ const YYMAXAPI_OPENCLAW_CLAUDE_FALLBACK = `${YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER}/claude-opus-4-6`;
1951
+ const YYMAXAPI_OPENCLAW_GPT_PRIMARY = `${YYMAXAPI_OPENCLAW_GPT_PROVIDER}/gpt-5.4`;
1952
+ const YYMAXAPI_OPENCLAW_GPT_FALLBACK = `${YYMAXAPI_OPENCLAW_GPT_PROVIDER}/gpt-5.3-codex`;
1953
+ const YYMAXAPI_MANAGED_MAIN_NAMES = new Set(['Claude', 'claude', 'yunyi-claude', 'claude-yunyi']);
1954
+ const YYMAXAPI_MANAGED_GPT_NAMES = new Set(['GPT', 'Codex', 'gpt', 'yunyi-gpt', 'yunyi-codex']);
1955
+
1956
+ function isYymaxapiOpenClawBuild() {
1957
+ return isLikelyYunyiBaseUrl(ENDPOINTS[0]?.url || '')
1958
+ && API_CONFIG?.claude?.api === 'anthropic-messages'
1959
+ && API_CONFIG?.codex?.api === 'openai-completions';
1960
+ }
1961
+
1962
+ function startsWithProviderAliases(modelKey, aliases) {
1963
+ if (typeof modelKey !== 'string') return false;
1964
+ for (const alias of aliases || []) {
1965
+ if (modelKey.startsWith(`${alias}/`)) return true;
1966
+ }
1967
+ return false;
1968
+ }
1969
+
1970
+ function isManagedYunyiClaudeModelKey(modelKey) {
1971
+ return startsWithProviderAliases(modelKey, YUNYI_PROVIDER_ALIASES.claude);
1972
+ }
1973
+
1974
+ function isManagedYunyiGptModelKey(modelKey) {
1975
+ return startsWithProviderAliases(modelKey, YUNYI_PROVIDER_ALIASES.codex);
1976
+ }
1977
+
1978
+ function isManagedYunyiModelKey(modelKey) {
1979
+ return isManagedYunyiClaudeModelKey(modelKey) || isManagedYunyiGptModelKey(modelKey);
1980
+ }
1981
+
1982
+ function stripManagedYunyiSuffix(baseUrl) {
1983
+ return String(baseUrl || '').trim().replace(/\/(?:claude|codex)\/?$/i, '');
1984
+ }
1985
+
1986
+ function getManagedYunyiModelCatalog(type) {
1987
+ if (type === 'claude') {
1988
+ return [
1989
+ { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6' },
1990
+ { id: 'claude-opus-4-6', name: 'Claude Opus 4.6' }
1991
+ ];
1992
+ }
1993
+ return [
1994
+ { id: 'gpt-5.4', name: 'GPT 5.4' },
1995
+ { id: 'gpt-5.3-codex', name: 'GPT 5.3 Codex' }
1996
+ ];
1997
+ }
1998
+
1999
+ function resolveManagedYunyiModelName(type, modelId, fallbackName) {
2000
+ const catalog = type === 'claude' ? CLAUDE_MODELS : CODEX_MODELS;
2001
+ return catalog.find(model => model.id === modelId)?.name || fallbackName;
2002
+ }
2003
+
2004
+ function getManagedYunyiProviderModels(type) {
2005
+ const apiConfig = type === 'claude'
2006
+ ? { contextWindow: API_CONFIG.claude?.contextWindow || 200000, maxTokens: API_CONFIG.claude?.maxTokens || 8192 }
2007
+ : { contextWindow: API_CONFIG.codex?.contextWindow || 1050000, maxTokens: API_CONFIG.codex?.maxTokens || 128000 };
2008
+
2009
+ return getManagedYunyiModelCatalog(type).map(model => ({
2010
+ id: model.id,
2011
+ name: resolveManagedYunyiModelName(type, model.id, model.name),
2012
+ contextWindow: apiConfig.contextWindow,
2013
+ maxTokens: apiConfig.maxTokens
2014
+ }));
2015
+ }
2016
+
2017
+ function getAgentModelState(agent) {
2018
+ const model = agent && typeof agent === 'object' && agent.model && typeof agent.model === 'object'
2019
+ ? agent.model
2020
+ : {};
2021
+ const primary = typeof model.primary === 'string' ? model.primary.trim() : '';
2022
+ const fallbacks = Array.isArray(model.fallbacks)
2023
+ ? [...new Set(model.fallbacks.map(item => String(item || '').trim()).filter(Boolean))]
2024
+ : [];
2025
+ return { primary, fallbacks };
2026
+ }
2027
+
2028
+ function hasManagedYunyiFootprint(config) {
2029
+ const providers = config?.models?.providers || {};
2030
+ if (Object.entries(providers).some(([name, provider]) => isYunyiProviderEntry(name, provider, 'claude') || isYunyiProviderEntry(name, provider, 'codex'))) {
2031
+ return true;
2032
+ }
2033
+
2034
+ const defaultsModel = config?.agents?.defaults?.model || {};
2035
+ if (isManagedYunyiModelKey(defaultsModel.primary)) return true;
2036
+ if (Array.isArray(defaultsModel.fallbacks) && defaultsModel.fallbacks.some(isManagedYunyiModelKey)) return true;
2037
+
2038
+ if (Array.isArray(config?.agents?.list)) {
2039
+ return config.agents.list.some(agent => {
2040
+ if (!agent || typeof agent !== 'object') return false;
2041
+ const name = String(agent.name || '').trim();
2042
+ const { primary, fallbacks } = getAgentModelState(agent);
2043
+ if (agent.id === YYMAXAPI_OPENCLAW_GPT_AGENT_ID || agent.id === YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID) {
2044
+ return true;
2045
+ }
2046
+ if (agent.id === YYMAXAPI_OPENCLAW_MAIN_AGENT_ID && YYMAXAPI_MANAGED_MAIN_NAMES.has(name)) {
2047
+ return true;
2048
+ }
2049
+ return isManagedYunyiModelKey(primary) || fallbacks.some(isManagedYunyiModelKey);
2050
+ });
2051
+ }
2052
+
2053
+ return false;
2054
+ }
2055
+
2056
+ function inferManagedYunyiEndpointUrl(config, explicitEndpointUrl = '') {
2057
+ const direct = stripManagedYunyiSuffix(explicitEndpointUrl);
2058
+ if (direct && isLikelyYunyiBaseUrl(direct)) return direct;
2059
+
2060
+ const providers = config?.models?.providers || {};
2061
+ for (const [name, provider] of Object.entries(providers)) {
2062
+ if (!isYunyiProviderEntry(name, provider, 'claude') && !isYunyiProviderEntry(name, provider, 'codex')) continue;
2063
+ const candidate = stripManagedYunyiSuffix(provider?.baseUrl || provider?.base_url || '');
2064
+ if (candidate && isLikelyYunyiBaseUrl(candidate)) return candidate;
2065
+ }
2066
+
2067
+ return ENDPOINTS.find(item => isLikelyYunyiBaseUrl(item.url))?.url || ENDPOINTS[0]?.url || '';
2068
+ }
2069
+
2070
+ function inferManagedYunyiApiKey(config, explicitApiKey = '') {
2071
+ const direct = String(explicitApiKey || '').trim();
2072
+ if (direct) return direct;
2073
+
2074
+ const providers = config?.models?.providers || {};
2075
+ for (const [name, provider] of Object.entries(providers)) {
2076
+ if (!isYunyiProviderEntry(name, provider, 'claude') && !isYunyiProviderEntry(name, provider, 'codex')) continue;
2077
+ const apiKey = String(provider?.apiKey || '').trim();
2078
+ if (apiKey) return apiKey;
2079
+ }
2080
+
2081
+ return '';
2082
+ }
2083
+
2084
+ function buildManagedYunyiProviderConfig(type, endpointUrl, apiKey, existingProvider = {}) {
2085
+ const providerName = type === 'claude' ? YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER : YYMAXAPI_OPENCLAW_GPT_PROVIDER;
2086
+ const nextApiKey = String(apiKey || existingProvider.apiKey || '').trim();
2087
+ const next = {
2088
+ ...existingProvider,
2089
+ baseUrl: buildFullUrl(endpointUrl, type),
2090
+ auth: DEFAULT_AUTH_MODE,
2091
+ api: type === 'claude' ? 'anthropic-messages' : 'openai-completions',
2092
+ headers: {},
2093
+ authHeader: type !== 'claude',
2094
+ models: getManagedYunyiProviderModels(type)
2095
+ };
2096
+
2097
+ if (nextApiKey) next.apiKey = nextApiKey;
2098
+ if (!next.auth) next.auth = DEFAULT_AUTH_MODE;
2099
+ if (!next.baseUrl) next.baseUrl = buildFullUrl(endpointUrl, type);
2100
+ if (!next.api) next.api = type === 'claude' ? 'anthropic-messages' : 'openai-completions';
2101
+ if (!Array.isArray(next.models)) next.models = getManagedYunyiProviderModels(type);
2102
+ if (!providerName) return existingProvider;
2103
+ return next;
2104
+ }
2105
+
2106
+ function ensureAgentList(config) {
2107
+ if (!config.agents) config.agents = {};
2108
+ if (!Array.isArray(config.agents.list)) config.agents.list = [];
2109
+ return config.agents.list;
2110
+ }
2111
+
2112
+ function findAgentIndex(agentList, agentId) {
2113
+ return agentList.findIndex(agent => agent && typeof agent === 'object' && agent.id === agentId);
2114
+ }
2115
+
2116
+ function mergeManagedAgent(existingAgent, nextAgent) {
2117
+ const existing = existingAgent && typeof existingAgent === 'object' ? existingAgent : {};
2118
+ return {
2119
+ ...existing,
2120
+ ...nextAgent,
2121
+ model: {
2122
+ ...(existing.model && typeof existing.model === 'object' ? existing.model : {}),
2123
+ ...(nextAgent.model || {})
2124
+ }
2125
+ };
2126
+ }
2127
+
2128
+ function isManagedMainAgent(agent) {
2129
+ if (!agent || typeof agent !== 'object') return true;
2130
+ const name = String(agent.name || '').trim();
2131
+ const { primary, fallbacks } = getAgentModelState(agent);
2132
+ if (!primary) return true;
2133
+ if (YYMAXAPI_MANAGED_MAIN_NAMES.has(name)) return true;
2134
+ if (isManagedYunyiClaudeModelKey(primary) || isManagedYunyiGptModelKey(primary)) return true;
2135
+ return fallbacks.some(modelKey => isManagedYunyiClaudeModelKey(modelKey) || isManagedYunyiGptModelKey(modelKey));
2136
+ }
2137
+
2138
+ function isManagedClaudeSideAgent(agent) {
2139
+ if (!agent || typeof agent !== 'object') return true;
2140
+ const name = String(agent.name || '').trim();
2141
+ const { primary, fallbacks } = getAgentModelState(agent);
2142
+ if (!primary) return true;
2143
+ if (YYMAXAPI_MANAGED_MAIN_NAMES.has(name)) return true;
2144
+ if (isManagedYunyiClaudeModelKey(primary)) return true;
2145
+ return fallbacks.some(isManagedYunyiClaudeModelKey);
2146
+ }
2147
+
2148
+ function isManagedGptAgent(agent) {
2149
+ if (!agent || typeof agent !== 'object') return true;
2150
+ const name = String(agent.name || '').trim();
2151
+ const { primary, fallbacks } = getAgentModelState(agent);
2152
+ if (!primary) return true;
2153
+ if (YYMAXAPI_MANAGED_GPT_NAMES.has(name)) return true;
2154
+ if (isManagedYunyiGptModelKey(primary)) return true;
2155
+ return fallbacks.some(isManagedYunyiGptModelKey);
2156
+ }
2157
+
2158
+ function upsertManagedAgent(agentList, nextAgent, shouldManage) {
2159
+ const index = findAgentIndex(agentList, nextAgent.id);
2160
+ if (index === -1) {
2161
+ agentList.push(nextAgent);
2162
+ return { changed: true, managed: true, created: true };
2163
+ }
2164
+
2165
+ const currentAgent = agentList[index];
2166
+ if (!shouldManage(currentAgent)) {
2167
+ return { changed: false, managed: false, preserved: true };
2168
+ }
2169
+
2170
+ const mergedAgent = mergeManagedAgent(currentAgent, nextAgent);
2171
+ if (JSON.stringify(mergedAgent) === JSON.stringify(currentAgent)) {
2172
+ return { changed: false, managed: true };
2173
+ }
2174
+
2175
+ agentList[index] = mergedAgent;
2176
+ return { changed: true, managed: true };
2177
+ }
2178
+
2179
+ function removeManagedAgent(agentList, agentId, shouldManage) {
2180
+ const index = findAgentIndex(agentList, agentId);
2181
+ if (index === -1) return false;
2182
+ if (!shouldManage(agentList[index])) return false;
2183
+ agentList.splice(index, 1);
2184
+ return true;
2185
+ }
2186
+
2187
+ function shouldManageYunyiDefaults(config) {
2188
+ const modelState = config?.agents?.defaults?.model || {};
2189
+ const primary = typeof modelState.primary === 'string' ? modelState.primary.trim() : '';
2190
+ const fallbacks = Array.isArray(modelState.fallbacks) ? modelState.fallbacks : [];
2191
+ if (!primary) return true;
2192
+ if (isManagedYunyiModelKey(primary)) return true;
2193
+ return fallbacks.some(isManagedYunyiModelKey);
2194
+ }
2195
+
2196
+ function applyManagedYunyiOpenClawLayout(config, options = {}) {
2197
+ if (!isYymaxapiOpenClawBuild()) {
2198
+ return { changed: false, applied: false, claudeAgentId: null, preservedMain: false };
2199
+ }
2200
+
2201
+ if (!options.force && !hasManagedYunyiFootprint(config)) {
2202
+ return { changed: false, applied: false, claudeAgentId: null, preservedMain: false };
2203
+ }
2204
+
2205
+ ensureConfigStructure(config);
2206
+
2207
+ const endpointUrl = inferManagedYunyiEndpointUrl(config, options.endpointUrl);
2208
+ const apiKey = inferManagedYunyiApiKey(config, options.apiKey);
2209
+ if (!endpointUrl) {
2210
+ return { changed: false, applied: false, claudeAgentId: null, preservedMain: false };
2211
+ }
2212
+
2213
+ let changed = false;
2214
+ const providers = config.models.providers;
2215
+
2216
+ const nextClaudeProvider = buildManagedYunyiProviderConfig('claude', endpointUrl, apiKey, providers[YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER] || {});
2217
+ if (JSON.stringify(nextClaudeProvider) !== JSON.stringify(providers[YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER] || {})) {
2218
+ providers[YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER] = nextClaudeProvider;
2219
+ changed = true;
2220
+ }
2221
+
2222
+ const nextGptProvider = buildManagedYunyiProviderConfig('codex', endpointUrl, apiKey, providers[YYMAXAPI_OPENCLAW_GPT_PROVIDER] || {});
2223
+ if (JSON.stringify(nextGptProvider) !== JSON.stringify(providers[YYMAXAPI_OPENCLAW_GPT_PROVIDER] || {})) {
2224
+ providers[YYMAXAPI_OPENCLAW_GPT_PROVIDER] = nextGptProvider;
2225
+ changed = true;
2226
+ }
2227
+
2228
+ const nextClaudeAuthProfile = { provider: YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER, mode: 'api_key' };
2229
+ if (JSON.stringify(config.auth.profiles[`${YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER}:default`] || {}) !== JSON.stringify(nextClaudeAuthProfile)) {
2230
+ config.auth.profiles[`${YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER}:default`] = nextClaudeAuthProfile;
2231
+ changed = true;
2232
+ }
2233
+
2234
+ const nextGptAuthProfile = { provider: YYMAXAPI_OPENCLAW_GPT_PROVIDER, mode: 'api_key' };
2235
+ if (JSON.stringify(config.auth.profiles[`${YYMAXAPI_OPENCLAW_GPT_PROVIDER}:default`] || {}) !== JSON.stringify(nextGptAuthProfile)) {
2236
+ config.auth.profiles[`${YYMAXAPI_OPENCLAW_GPT_PROVIDER}:default`] = nextGptAuthProfile;
2237
+ changed = true;
2238
+ }
2239
+
2240
+ const defaultsModels = config.agents.defaults.models;
2241
+ const managedModelAliases = {
2242
+ [YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY]: YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER,
2243
+ [YYMAXAPI_OPENCLAW_CLAUDE_FALLBACK]: YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER,
2244
+ [YYMAXAPI_OPENCLAW_GPT_PRIMARY]: YYMAXAPI_OPENCLAW_GPT_PROVIDER,
2245
+ [YYMAXAPI_OPENCLAW_GPT_FALLBACK]: YYMAXAPI_OPENCLAW_GPT_PROVIDER
2246
+ };
2247
+ for (const [modelKey, alias] of Object.entries(managedModelAliases)) {
2248
+ const nextEntry = { alias };
2249
+ if (JSON.stringify(defaultsModels[modelKey] || {}) !== JSON.stringify(nextEntry)) {
2250
+ defaultsModels[modelKey] = nextEntry;
2251
+ changed = true;
2252
+ }
2253
+ }
2254
+
2255
+ const agentList = ensureAgentList(config);
2256
+ const mainAgentResult = upsertManagedAgent(agentList, {
2257
+ id: YYMAXAPI_OPENCLAW_MAIN_AGENT_ID,
2258
+ default: true,
2259
+ name: 'yunyi-claude',
2260
+ model: {
2261
+ primary: YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY,
2262
+ fallbacks: [YYMAXAPI_OPENCLAW_CLAUDE_FALLBACK]
2263
+ }
2264
+ }, isManagedMainAgent);
2265
+ if (mainAgentResult.changed) changed = true;
2266
+
2267
+ let claudeAgentId = YYMAXAPI_OPENCLAW_MAIN_AGENT_ID;
2268
+ let preservedMain = !mainAgentResult.managed;
2269
+ if (!mainAgentResult.managed) {
2270
+ const fallbackClaudeAgentResult = upsertManagedAgent(agentList, {
2271
+ id: YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID,
2272
+ default: false,
2273
+ name: 'yunyi-claude',
2274
+ model: {
2275
+ primary: YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY,
2276
+ fallbacks: [YYMAXAPI_OPENCLAW_CLAUDE_FALLBACK]
2277
+ }
2278
+ }, isManagedClaudeSideAgent);
2279
+ if (fallbackClaudeAgentResult.changed) changed = true;
2280
+ claudeAgentId = fallbackClaudeAgentResult.managed ? YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID : null;
2281
+ } else if (removeManagedAgent(agentList, YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID, isManagedClaudeSideAgent)) {
2282
+ changed = true;
2283
+ }
2284
+
2285
+ const gptAgentResult = upsertManagedAgent(agentList, {
2286
+ id: YYMAXAPI_OPENCLAW_GPT_AGENT_ID,
2287
+ default: false,
2288
+ name: 'yunyi-gpt',
2289
+ model: {
2290
+ primary: YYMAXAPI_OPENCLAW_GPT_PRIMARY,
2291
+ fallbacks: [YYMAXAPI_OPENCLAW_GPT_FALLBACK]
2292
+ }
2293
+ }, isManagedGptAgent);
2294
+ if (gptAgentResult.changed) changed = true;
2295
+
2296
+ if (shouldManageYunyiDefaults(config)) {
2297
+ const nextDefaultsModel = {
2298
+ primary: YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY,
2299
+ fallbacks: [YYMAXAPI_OPENCLAW_GPT_PRIMARY]
2300
+ };
2301
+ if (JSON.stringify(config.agents.defaults.model) !== JSON.stringify(nextDefaultsModel)) {
2302
+ config.agents.defaults.model = nextDefaultsModel;
2303
+ changed = true;
2304
+ }
2305
+ }
2306
+
2307
+ return { changed, applied: true, claudeAgentId, preservedMain };
2308
+ }
2309
+
2310
+ function printYunyiOpenClawSwitchHint(result = {}) {
2311
+ if (!result?.applied) return;
2312
+ if (result.preservedMain && result.claudeAgentId === YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID) {
2313
+ console.log(chalk.gray(' 已保留你现有的 main;Yunyi Claude 入口为 yunyi-claude'));
2314
+ } else {
2315
+ console.log(chalk.gray(' 默认主入口是 yunyi-claude'));
2316
+ }
2317
+ console.log(chalk.gray(' GPT 入口是 yunyi-gpt'));
2318
+ console.log(chalk.yellow(' 提示: 当前 OpenClaw Web 顶部模型下拉跨 provider 切换可能报错,建议通过左侧会话/agent 切换 Claude 和 GPT'));
2319
+ }
2320
+
1923
2321
  function pruneProvidersByPrefix(config, prefixBase, keepProviders = []) {
1924
2322
  if (!config?.models?.providers) return [];
1925
2323
  const removed = [];
@@ -2260,12 +2658,14 @@ function isNodeShebang(filePath) {
2260
2658
  return true;
2261
2659
  }
2262
2660
  const fd = fs.openSync(filePath, 'r');
2263
- const buffer = Buffer.alloc(128);
2661
+ const buffer = Buffer.alloc(256);
2264
2662
  const bytes = fs.readSync(fd, buffer, 0, buffer.length, 0);
2265
2663
  fs.closeSync(fd);
2266
2664
  if (bytes <= 0) return false;
2267
2665
  const header = buffer.toString('utf8', 0, bytes);
2268
- return header.startsWith('#!') && header.includes('node');
2666
+ const firstLine = header.split(/\r?\n/, 1)[0] || '';
2667
+ if (!firstLine.startsWith('#!')) return false;
2668
+ return /\bnode(?:js)?\b/.test(firstLine);
2269
2669
  } catch {
2270
2670
  return false;
2271
2671
  }
@@ -2747,6 +3147,8 @@ function autoFixConfig(paths) {
2747
3147
  sanitizeDefaultModelSelection(config);
2748
3148
  const repairResult = applyConfigRepairsWithSync(config, paths);
2749
3149
  if (repairResult.changed) changed = true;
3150
+ const yunyiLayoutResult = applyManagedYunyiOpenClawLayout(config);
3151
+ if (yunyiLayoutResult.changed) changed = true;
2750
3152
  const codexProvider = codexProviderName && config.models.providers?.[codexProviderName];
2751
3153
  const nextModelJson = JSON.stringify(config.agents?.defaults?.model ?? null);
2752
3154
  if (originalModelJson !== nextModelJson) {
@@ -2766,8 +3168,12 @@ function autoFixConfig(paths) {
2766
3168
  }
2767
3169
 
2768
3170
  if (changed) {
2769
- writeConfig(paths.openclawConfig, config);
2770
- console.log(chalk.green('✓ 已自动修复配置(模型结构 / API 协议)'));
3171
+ writeConfigWithSync(paths, config);
3172
+ const managedClaudeKey = String(config.models.providers?.[YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER]?.apiKey || '').trim();
3173
+ const managedGptKey = String(config.models.providers?.[YYMAXAPI_OPENCLAW_GPT_PROVIDER]?.apiKey || '').trim();
3174
+ if (managedClaudeKey) updateAuthProfilesWithSync(paths, YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER, managedClaudeKey);
3175
+ if (managedGptKey) updateAuthProfilesWithSync(paths, YYMAXAPI_OPENCLAW_GPT_PROVIDER, managedGptKey);
3176
+ console.log(chalk.green('✓ 已自动修复配置(Yunyi providers / agents / API 协议)'));
2771
3177
  }
2772
3178
  } catch { /* ignore */ }
2773
3179
  }
@@ -3468,12 +3874,21 @@ async function presetClaude(paths, args = {}) {
3468
3874
  config.agents.defaults.model.primary = modelKey;
3469
3875
  config.agents.defaults.model.fallbacks = [];
3470
3876
  }
3877
+ const yunyiLayoutResult = applyManagedYunyiOpenClawLayout(config, {
3878
+ force: true,
3879
+ endpointUrl: selectedEndpoint.url,
3880
+ apiKey
3881
+ });
3471
3882
 
3472
3883
  const writeSpinner = ora({ text: '正在写入配置...', spinner: 'dots' }).start();
3473
3884
  createTimestampedBackup(paths.openclawConfig, paths.configDir, 'claude');
3474
3885
  ensureGatewaySettings(config);
3475
3886
  writeConfigWithSync(paths, config);
3476
3887
  updateAuthProfilesWithSync(paths, providerName, apiKey);
3888
+ if (yunyiLayoutResult.applied) {
3889
+ updateAuthProfilesWithSync(paths, YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER, apiKey);
3890
+ updateAuthProfilesWithSync(paths, YYMAXAPI_OPENCLAW_GPT_PROVIDER, apiKey);
3891
+ }
3477
3892
  const extSynced = syncExternalTools('claude', baseUrl, apiKey, { claudeModelId: modelId, opencodeDefaultModelKey: `yunyi-claude/${modelId}` });
3478
3893
  writeSpinner.succeed('配置写入完成');
3479
3894
 
@@ -3486,6 +3901,7 @@ async function presetClaude(paths, args = {}) {
3486
3901
  if (repairResult.renamedProviders.length > 0) {
3487
3902
  console.log(chalk.gray(` 已保留并修复冲突 provider: ${repairResult.renamedProviders.map(item => `${item.from}→${item.to}`).join(', ')}`));
3488
3903
  }
3904
+ printYunyiOpenClawSwitchHint(yunyiLayoutResult);
3489
3905
 
3490
3906
  const shouldTestGateway = args.test !== undefined
3491
3907
  ? !['false', '0', 'no'].includes(String(args.test).toLowerCase())
@@ -3644,6 +4060,11 @@ async function presetCodex(paths, args = {}) {
3644
4060
  config.agents.defaults.model.primary = modelKey;
3645
4061
  config.agents.defaults.model.fallbacks = [];
3646
4062
  }
4063
+ const yunyiLayoutResult = applyManagedYunyiOpenClawLayout(config, {
4064
+ force: true,
4065
+ endpointUrl: selectedEndpoint.url,
4066
+ apiKey
4067
+ });
3647
4068
 
3648
4069
  const writeSpinner2 = ora({ text: '正在写入配置...', spinner: 'dots' }).start();
3649
4070
  createTimestampedBackup(paths.openclawConfig, paths.configDir, 'codex');
@@ -3651,6 +4072,10 @@ async function presetCodex(paths, args = {}) {
3651
4072
  cleanupConflictingEnvVars(config, baseUrl, apiKey);
3652
4073
  writeConfigWithSync(paths, config);
3653
4074
  updateAuthProfilesWithSync(paths, providerName, apiKey);
4075
+ if (yunyiLayoutResult.applied) {
4076
+ updateAuthProfilesWithSync(paths, YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER, apiKey);
4077
+ updateAuthProfilesWithSync(paths, YYMAXAPI_OPENCLAW_GPT_PROVIDER, apiKey);
4078
+ }
3654
4079
  const extSynced2 = syncExternalTools('codex', baseUrl, apiKey, { modelId });
3655
4080
  writeSpinner2.succeed('配置写入完成');
3656
4081
 
@@ -3663,6 +4088,7 @@ async function presetCodex(paths, args = {}) {
3663
4088
  if (repairResult.renamedProviders.length > 0) {
3664
4089
  console.log(chalk.gray(` 已保留并修复冲突 provider: ${repairResult.renamedProviders.map(item => `${item.from}→${item.to}`).join(', ')}`));
3665
4090
  }
4091
+ printYunyiOpenClawSwitchHint(yunyiLayoutResult);
3666
4092
 
3667
4093
  const shouldTestGateway = args.test !== undefined
3668
4094
  ? !['false', '0', 'no'].includes(String(args.test).toLowerCase())
@@ -3831,6 +4257,11 @@ async function autoActivate(paths, args = {}) {
3831
4257
  const fallbackModelKey = isClaudePrimary ? codexModelKey : claudeModelKey;
3832
4258
  config.agents.defaults.model.primary = primaryModelKey;
3833
4259
  config.agents.defaults.model.fallbacks = [fallbackModelKey];
4260
+ const yunyiLayoutResult = applyManagedYunyiOpenClawLayout(config, {
4261
+ force: true,
4262
+ endpointUrl: selectedEndpoint.url,
4263
+ apiKey
4264
+ });
3834
4265
 
3835
4266
  // ---- 写入配置 ----
3836
4267
  const writeSpinner = ora({ text: '正在写入配置...', spinner: 'dots' }).start();
@@ -3850,12 +4281,13 @@ async function autoActivate(paths, args = {}) {
3850
4281
  const primaryLabel = isClaudePrimary ? 'Claude' : 'GPT';
3851
4282
  const selectedModel = isClaudePrimary ? claudeModel : codexModel;
3852
4283
  console.log(chalk.green('\n✅ 配置完成!'));
3853
- console.log(chalk.cyan(` 模型: ${selectedModel.name} (${primaryLabel})`));
4284
+ console.log(chalk.cyan(` 外部工具默认: ${selectedModel.name} (${primaryLabel})`));
3854
4285
  console.log(chalk.gray(` 节点: ${selectedEndpoint.url} (${selectedEndpoint.name})`));
3855
4286
  console.log(chalk.gray(' API Key: 已设置'));
3856
4287
  if (extSynced.length > 0) console.log(chalk.gray(` 同步: ${extSynced.join(', ')}`));
3857
4288
  console.log(chalk.gray(' 若遇 certificate 报错,请新开终端或执行 source ~/.zshrc 后重试(已放宽 TLS 校验)'));
3858
4289
  console.log(chalk.gray(` 使用 OpenCode 时可在界面中切换 ${getOpencodeSwitchHint()};Codex 仅支持 GPT`));
4290
+ printYunyiOpenClawSwitchHint(yunyiLayoutResult);
3859
4291
 
3860
4292
  const gwPort = config.gateway?.port || 18789;
3861
4293
  const gwToken = config.gateway?.auth?.token;
@@ -4276,6 +4708,11 @@ async function yycodeQuickSetup(paths) {
4276
4708
  // 默认主力: Codex, 备用: Claude
4277
4709
  config.agents.defaults.model.primary = codexModelKey;
4278
4710
  config.agents.defaults.model.fallbacks = [claudeModelKey];
4711
+ const yunyiLayoutResult = applyManagedYunyiOpenClawLayout(config, {
4712
+ force: true,
4713
+ endpointUrl: selectedEndpoint.url,
4714
+ apiKey
4715
+ });
4279
4716
 
4280
4717
  // ---- 写入 ----
4281
4718
  const writeSpinner = ora({ text: '正在写入配置...', spinner: 'dots' }).start();
@@ -4293,10 +4730,11 @@ async function yycodeQuickSetup(paths) {
4293
4730
  console.log(chalk.green('\n✅ 配置完成!'));
4294
4731
  console.log(chalk.cyan(` Claude Code: ${claudeBaseUrl}`));
4295
4732
  console.log(chalk.gray(` 模型: ${claudeModel.name}`));
4296
- console.log(chalk.cyan(` Codex (主): ${codexBaseUrl}`));
4733
+ console.log(chalk.cyan(` Codex CLI: ${codexBaseUrl}`));
4297
4734
  console.log(chalk.gray(` 模型: ${codexModel.name}`));
4298
4735
  console.log(chalk.gray(' API Key: 已设置'));
4299
4736
  console.log(chalk.gray(' 同步: Claude Code settings, Opencode config, Codex CLI config'));
4737
+ printYunyiOpenClawSwitchHint(yunyiLayoutResult);
4300
4738
  console.log('');
4301
4739
  }
4302
4740
 
@@ -4436,6 +4874,7 @@ function getConfigStatusLine(paths) {
4436
4874
  const primaryProvider = primary.split('/')[0] || '';
4437
4875
 
4438
4876
  const KNOWN_PROVIDERS = {
4877
+ 'yunyi-claude': 'Claude(包月)',
4439
4878
  'claude-yunyi': 'Claude(包月)',
4440
4879
  'yunyi': 'Codex(包月)',
4441
4880
  'heibai': 'MAXAPI(按量)',
@@ -4555,14 +4994,24 @@ async function selectNode(paths, type) {
4555
4994
  config.agents.defaults.models[modelKey] = { alias: apiConfig.providerName };
4556
4995
  config.agents.defaults.model.primary = modelKey;
4557
4996
  config.agents.defaults.model.fallbacks = (config.agents.defaults.model.fallbacks || []).filter(k => k !== modelKey);
4997
+ const yunyiLayoutResult = applyManagedYunyiOpenClawLayout(config, {
4998
+ force: true,
4999
+ endpointUrl: selectedEndpoint.url,
5000
+ apiKey: oldApiKey || ''
5001
+ });
4558
5002
 
4559
5003
  ensureGatewaySettings(config);
4560
5004
  writeConfigWithSync(paths, config);
5005
+ const managedClaudeKey = String(config.models.providers?.[YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER]?.apiKey || '').trim();
5006
+ const managedGptKey = String(config.models.providers?.[YYMAXAPI_OPENCLAW_GPT_PROVIDER]?.apiKey || '').trim();
5007
+ if (managedClaudeKey) updateAuthProfilesWithSync(paths, YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER, managedClaudeKey);
5008
+ if (managedGptKey) updateAuthProfilesWithSync(paths, YYMAXAPI_OPENCLAW_GPT_PROVIDER, managedGptKey);
4561
5009
 
4562
5010
  console.log(chalk.green(`\n✅ ${typeLabel} 节点配置完成!`));
4563
5011
  console.log(chalk.cyan(` 节点: ${selectedEndpoint.name} (${selectedEndpoint.url})`));
4564
5012
  console.log(chalk.gray(` 模型: ${modelConfig.name} (主模型)`));
4565
5013
  console.log(chalk.gray(` API Key: ${oldApiKey ? '已设置' : '未设置'}`));
5014
+ printYunyiOpenClawSwitchHint(yunyiLayoutResult);
4566
5015
  }
4567
5016
 
4568
5017
  // ============ 激活 (Claude/Codex) ============
@@ -4593,6 +5042,11 @@ async function activate(paths, type) {
4593
5042
  const modelKey = `${apiConfig.providerName}/${modelConfig.id}`;
4594
5043
  config.agents.defaults.model.primary = modelKey;
4595
5044
  config.agents.defaults.model.fallbacks = [];
5045
+ const yunyiLayoutResult = applyManagedYunyiOpenClawLayout(config, {
5046
+ force: true,
5047
+ endpointUrl: stripManagedYunyiSuffix(provider.baseUrl),
5048
+ apiKey
5049
+ });
4596
5050
 
4597
5051
  writeConfigWithSync(paths, config);
4598
5052
 
@@ -4604,12 +5058,17 @@ async function activate(paths, type) {
4604
5058
 
4605
5059
  const authStore = readAuthStore(paths.authProfiles);
4606
5060
  authStore.profiles[`${apiConfig.providerName}:default`] = { type: 'api_key', key: apiKey.trim(), provider: apiConfig.providerName };
5061
+ if (yunyiLayoutResult.applied) {
5062
+ authStore.profiles[`${YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER}:default`] = { type: 'api_key', key: apiKey.trim(), provider: YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER };
5063
+ authStore.profiles[`${YYMAXAPI_OPENCLAW_GPT_PROVIDER}:default`] = { type: 'api_key', key: apiKey.trim(), provider: YYMAXAPI_OPENCLAW_GPT_PROVIDER };
5064
+ }
4607
5065
  writeAuthStore(paths.authProfiles, authStore);
4608
5066
 
4609
5067
  console.log(chalk.green(`\n✅ 已激活 ${typeLabel}`));
4610
5068
  console.log(chalk.cyan(` 节点: ${provider.baseUrl}`));
4611
5069
  console.log(chalk.gray(` 模型: ${modelConfig.name}`));
4612
5070
  console.log(chalk.gray(` API Key: 已设置`));
5071
+ printYunyiOpenClawSwitchHint(yunyiLayoutResult);
4613
5072
  }
4614
5073
 
4615
5074
 
@@ -4628,6 +5087,7 @@ async function switchModel(paths) {
4628
5087
  }
4629
5088
 
4630
5089
  const PROVIDER_LABELS = {
5090
+ 'yunyi-claude': '云翼 Claude (包月)',
4631
5091
  'claude-yunyi': '云翼 Claude (包月)',
4632
5092
  'yunyi': '云翼 Codex (包月)',
4633
5093
  'heibai': 'MAXAPI (按量)',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.84",
3
+ "version": "1.0.85",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {