yymaxapi 1.0.88 → 1.0.90

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
@@ -134,7 +134,7 @@ const DEFAULT_API_CONFIG = {
134
134
  "codex": {
135
135
  "urlSuffix": "/codex",
136
136
  "api": "openai-completions",
137
- "contextWindow": 1050000,
137
+ "contextWindow": 1000000,
138
138
  "maxTokens": 128000,
139
139
  "providerName": "yunyi"
140
140
  }
@@ -881,6 +881,7 @@ function writeCodexConfig(baseUrl, apiKey, modelId = 'gpt-5.4') {
881
881
  if (!normalizedUrl.endsWith('/v1')) normalizedUrl += '/v1';
882
882
  try {
883
883
  let existing = '';
884
+ const escapedProviderKey = providerKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
884
885
  if (fs.existsSync(configPath)) {
885
886
  existing = fs.readFileSync(configPath, 'utf8');
886
887
  // 移除旧的 maxapi section(provider block)
@@ -889,13 +890,14 @@ function writeCodexConfig(baseUrl, apiKey, modelId = 'gpt-5.4') {
889
890
  // 移除旧的 maxapi top-level block
890
891
  const topRe = new RegExp(`${topMarker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${topMarkerEnd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n?`, 'g');
891
892
  existing = existing.replace(topRe, '');
892
- // 兼容旧版:移除散落的 model/model_provider、旧 openclaw-relay、旧 yunyi opencode 标记块
893
- existing = existing.replace(/^model\s*=\s*"[^"]*"\s*$/gm, '');
894
- existing = existing.replace(/^model_provider\s*=\s*"[^"]*"\s*$/gm, '');
893
+ // 兼容旧版:移除历史遗留的未标记 yunyi-codex provider、旧 openclaw-relay、旧 yunyi opencode 标记块
894
+ existing = existing.replace(new RegExp(`\\[model_providers\\.${escapedProviderKey}\\]\\n(?:(?!\\[)[^\\n]*\\n?)*`, 'g'), '');
895
895
  existing = existing.replace(/\[model_providers\.openclaw-relay\]\n(?:(?!\[)[^\n]*\n?)*/g, '');
896
896
  existing = existing.replace(/# >>> yunyi opencode >>>[\s\S]*?# <<< yunyi opencode <<<\n?/g, '');
897
897
  existing = existing.replace(/\n{3,}/g, '\n\n').trim();
898
898
  }
899
+ const hasExistingTopLevelModelConfig = /^model\s*=\s*"[^"]*"\s*$/m.test(existing)
900
+ || /^model_provider\s*=\s*"[^"]*"\s*$/m.test(existing);
899
901
 
900
902
  // 顶层设置(MUST 在所有 [section] 之前)
901
903
  const topBlock = [
@@ -919,7 +921,9 @@ function writeCodexConfig(baseUrl, apiKey, modelId = 'gpt-5.4') {
919
921
  // 组装:顶层设置 → 原有内容 → provider section
920
922
  let content;
921
923
  if (existing) {
922
- content = `${topBlock}\n\n${existing}\n\n${providerBlock}\n`;
924
+ content = hasExistingTopLevelModelConfig
925
+ ? `${existing}\n\n${providerBlock}\n`
926
+ : `${topBlock}\n\n${existing}\n\n${providerBlock}\n`;
923
927
  } else {
924
928
  content = `${topBlock}\n\n${providerBlock}\n`;
925
929
  }
@@ -1933,6 +1937,10 @@ const YYMAXAPI_OPENCLAW_GPT_PRIMARY = `${YYMAXAPI_OPENCLAW_GPT_PROVIDER}/gpt-5.4
1933
1937
  const YYMAXAPI_OPENCLAW_GPT_FALLBACK = `${YYMAXAPI_OPENCLAW_GPT_PROVIDER}/gpt-5.3-codex`;
1934
1938
  const YYMAXAPI_MANAGED_MAIN_NAMES = new Set(['Claude', 'claude', 'yunyi-claude', 'claude-yunyi']);
1935
1939
  const YYMAXAPI_MANAGED_GPT_NAMES = new Set(['GPT', 'Codex', 'gpt', 'yunyi-gpt', 'yunyi-codex']);
1940
+ const YYMAXAPI_MANAGED_MULTIMODAL_MODELS = {
1941
+ claude: new Set(['claude-sonnet-4-6', 'claude-opus-4-6']),
1942
+ codex: new Set(['gpt-5.4'])
1943
+ };
1936
1944
 
1937
1945
  function isYymaxapiOpenClawBuild() {
1938
1946
  return isLikelyYunyiBaseUrl(ENDPOINTS[0]?.url || '')
@@ -1982,14 +1990,21 @@ function resolveManagedYunyiModelName(type, modelId, fallbackName) {
1982
1990
  return catalog.find(model => model.id === modelId)?.name || fallbackName;
1983
1991
  }
1984
1992
 
1993
+ function getManagedYunyiModelInput(type, modelId) {
1994
+ return YYMAXAPI_MANAGED_MULTIMODAL_MODELS[type]?.has(modelId)
1995
+ ? ['text', 'image']
1996
+ : ['text'];
1997
+ }
1998
+
1985
1999
  function getManagedYunyiProviderModels(type) {
1986
2000
  const apiConfig = type === 'claude'
1987
2001
  ? { contextWindow: API_CONFIG.claude?.contextWindow || 200000, maxTokens: API_CONFIG.claude?.maxTokens || 8192 }
1988
- : { contextWindow: API_CONFIG.codex?.contextWindow || 1050000, maxTokens: API_CONFIG.codex?.maxTokens || 128000 };
2002
+ : { contextWindow: API_CONFIG.codex?.contextWindow || 1000000, maxTokens: API_CONFIG.codex?.maxTokens || 128000 };
1989
2003
 
1990
2004
  return getManagedYunyiModelCatalog(type).map(model => ({
1991
2005
  id: model.id,
1992
2006
  name: resolveManagedYunyiModelName(type, model.id, model.name),
2007
+ input: getManagedYunyiModelInput(type, model.id),
1993
2008
  contextWindow: apiConfig.contextWindow,
1994
2009
  maxTokens: apiConfig.maxTokens
1995
2010
  }));
@@ -2011,6 +2026,150 @@ function findAgentById(config, agentId) {
2011
2026
  return config.agents.list.find(agent => agent && typeof agent === 'object' && agent.id === agentId) || null;
2012
2027
  }
2013
2028
 
2029
+ function canonicalizeManagedYunyiModelKey(modelKey, preferredType = '') {
2030
+ const text = String(modelKey || '').trim();
2031
+ if (!text.includes('/')) return text;
2032
+ const [, modelId = ''] = text.split('/');
2033
+ if (!modelId) return text;
2034
+ if (preferredType === 'claude' || (!preferredType && isManagedYunyiClaudeModelKey(text))) {
2035
+ return `${YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER}/${modelId}`;
2036
+ }
2037
+ if (preferredType === 'codex' || preferredType === 'gpt' || (!preferredType && isManagedYunyiGptModelKey(text))) {
2038
+ return `${YYMAXAPI_OPENCLAW_GPT_PROVIDER}/${modelId}`;
2039
+ }
2040
+ return text;
2041
+ }
2042
+
2043
+ function getManagedYunyiKnownModelKeys(type) {
2044
+ return type === 'claude'
2045
+ ? [YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY, YYMAXAPI_OPENCLAW_CLAUDE_FALLBACK]
2046
+ : [YYMAXAPI_OPENCLAW_GPT_PRIMARY, YYMAXAPI_OPENCLAW_GPT_FALLBACK];
2047
+ }
2048
+
2049
+ function normalizeManagedYunyiModelState(type, modelState = {}, options = {}) {
2050
+ const knownKeys = getManagedYunyiKnownModelKeys(type);
2051
+ const fallbackPrimary = knownKeys[0];
2052
+ const currentPrimary = canonicalizeManagedYunyiModelKey(modelState.primary || '', type);
2053
+ const canPreserve = !options.force && currentPrimary && knownKeys.includes(currentPrimary);
2054
+ const primary = canPreserve ? currentPrimary : fallbackPrimary;
2055
+
2056
+ let fallbacks = Array.isArray(modelState.fallbacks) ? modelState.fallbacks : [];
2057
+ fallbacks = [...new Set(
2058
+ fallbacks
2059
+ .map(item => canonicalizeManagedYunyiModelKey(item, type))
2060
+ .filter(item => item && item !== primary && knownKeys.includes(item))
2061
+ )];
2062
+
2063
+ if (fallbacks.length === 0) {
2064
+ fallbacks = knownKeys.filter(item => item !== primary);
2065
+ }
2066
+
2067
+ return { primary, fallbacks };
2068
+ }
2069
+
2070
+ function normalizeManagedYunyiDefaultsModel(modelState = {}, options = {}) {
2071
+ const currentPrimary = String(modelState.primary || '').trim();
2072
+ const canPreserve = !options.force && currentPrimary && isManagedYunyiModelKey(currentPrimary);
2073
+ const primary = canPreserve
2074
+ ? canonicalizeManagedYunyiModelKey(currentPrimary)
2075
+ : YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY;
2076
+
2077
+ let fallbacks = Array.isArray(modelState.fallbacks) ? modelState.fallbacks : [];
2078
+ fallbacks = [...new Set(
2079
+ fallbacks
2080
+ .map(item => {
2081
+ const text = String(item || '').trim();
2082
+ return isManagedYunyiModelKey(text) ? canonicalizeManagedYunyiModelKey(text) : text;
2083
+ })
2084
+ .filter(item => item && item !== primary)
2085
+ )];
2086
+
2087
+ if (fallbacks.length === 0) {
2088
+ fallbacks = [isManagedYunyiClaudeModelKey(primary) ? YYMAXAPI_OPENCLAW_GPT_PRIMARY : YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY];
2089
+ }
2090
+
2091
+ return { primary, fallbacks };
2092
+ }
2093
+
2094
+ function getManagedClaudeAgentId(config) {
2095
+ const mainAgent = findAgentById(config, YYMAXAPI_OPENCLAW_MAIN_AGENT_ID);
2096
+ if (mainAgent && isManagedMainAgent(mainAgent)) {
2097
+ return YYMAXAPI_OPENCLAW_MAIN_AGENT_ID;
2098
+ }
2099
+
2100
+ const sideAgent = findAgentById(config, YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID);
2101
+ if (sideAgent && isManagedClaudeSideAgent(sideAgent)) {
2102
+ return YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID;
2103
+ }
2104
+
2105
+ return mainAgent ? YYMAXAPI_OPENCLAW_MAIN_AGENT_ID : (sideAgent ? YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID : null);
2106
+ }
2107
+
2108
+ function inferManagedYunyiAgentIdForModelKey(config, modelKey) {
2109
+ if (isManagedYunyiGptModelKey(modelKey)) return YYMAXAPI_OPENCLAW_GPT_AGENT_ID;
2110
+ if (isManagedYunyiClaudeModelKey(modelKey)) return getManagedClaudeAgentId(config);
2111
+ return null;
2112
+ }
2113
+
2114
+ function applyManagedYunyiModelSelection(config, selectedModelKey) {
2115
+ ensureConfigStructure(config);
2116
+
2117
+ const normalizedSelected = canonicalizeManagedYunyiModelKey(selectedModelKey);
2118
+ const selectedType = isManagedYunyiClaudeModelKey(normalizedSelected)
2119
+ ? 'claude'
2120
+ : (isManagedYunyiGptModelKey(normalizedSelected) ? 'codex' : '');
2121
+
2122
+ if (!selectedType) {
2123
+ return { changed: false, selectedModelKey: normalizedSelected, agentId: null };
2124
+ }
2125
+
2126
+ let changed = false;
2127
+ const previousPrimary = String(config.agents?.defaults?.model?.primary || '').trim();
2128
+ const previousCanonical = isManagedYunyiModelKey(previousPrimary)
2129
+ ? canonicalizeManagedYunyiModelKey(previousPrimary)
2130
+ : previousPrimary;
2131
+ const nextDefaultsModel = {
2132
+ primary: normalizedSelected,
2133
+ fallbacks: previousCanonical && previousCanonical !== normalizedSelected ? [previousCanonical] : []
2134
+ };
2135
+
2136
+ if (JSON.stringify(config.agents.defaults.model) !== JSON.stringify(nextDefaultsModel)) {
2137
+ config.agents.defaults.model = nextDefaultsModel;
2138
+ changed = true;
2139
+ }
2140
+
2141
+ const managedModelAliases = {
2142
+ [YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY]: YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER,
2143
+ [YYMAXAPI_OPENCLAW_CLAUDE_FALLBACK]: YYMAXAPI_OPENCLAW_CLAUDE_PROVIDER,
2144
+ [YYMAXAPI_OPENCLAW_GPT_PRIMARY]: YYMAXAPI_OPENCLAW_GPT_PROVIDER,
2145
+ [YYMAXAPI_OPENCLAW_GPT_FALLBACK]: YYMAXAPI_OPENCLAW_GPT_PROVIDER
2146
+ };
2147
+ for (const [modelKey, alias] of Object.entries(managedModelAliases)) {
2148
+ const nextEntry = { alias };
2149
+ if (JSON.stringify(config.agents.defaults.models[modelKey] || {}) !== JSON.stringify(nextEntry)) {
2150
+ config.agents.defaults.models[modelKey] = nextEntry;
2151
+ changed = true;
2152
+ }
2153
+ }
2154
+
2155
+ const agentId = inferManagedYunyiAgentIdForModelKey(config, normalizedSelected)
2156
+ || (selectedType === 'codex' ? YYMAXAPI_OPENCLAW_GPT_AGENT_ID : YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID);
2157
+ const agentList = ensureAgentList(config);
2158
+ const nextAgent = {
2159
+ id: agentId,
2160
+ default: agentId === YYMAXAPI_OPENCLAW_MAIN_AGENT_ID,
2161
+ name: selectedType === 'codex' ? 'yunyi-gpt' : 'yunyi-claude',
2162
+ model: normalizeManagedYunyiModelState(selectedType, { primary: normalizedSelected })
2163
+ };
2164
+ const shouldManage = selectedType === 'codex'
2165
+ ? isManagedGptAgent
2166
+ : (agentId === YYMAXAPI_OPENCLAW_MAIN_AGENT_ID ? isManagedMainAgent : isManagedClaudeSideAgent);
2167
+ const updateResult = upsertManagedAgent(agentList, nextAgent, shouldManage);
2168
+ if (updateResult.changed) changed = true;
2169
+
2170
+ return { changed, selectedModelKey: normalizedSelected, agentId };
2171
+ }
2172
+
2014
2173
  function resolveOpenClawTestTarget(config, args = {}) {
2015
2174
  const requestedAgentId = String(args.agent || args['agent-id'] || '').trim();
2016
2175
  const requestedAgent = requestedAgentId ? findAgentById(config, requestedAgentId) : null;
@@ -2027,11 +2186,23 @@ function resolveOpenClawTestTarget(config, args = {}) {
2027
2186
  if (requestedAgent) {
2028
2187
  const agentState = getAgentModelState(requestedAgent);
2029
2188
  if (agentState.primary) {
2030
- primary = agentState.primary;
2189
+ primary = canonicalizeManagedYunyiModelKey(agentState.primary);
2031
2190
  fallbacks = agentState.fallbacks;
2032
2191
  agentId = requestedAgent.id;
2033
2192
  agentName = String(requestedAgent.name || '').trim();
2034
2193
  }
2194
+ } else {
2195
+ const inferredAgentId = inferManagedYunyiAgentIdForModelKey(config, primary);
2196
+ const inferredAgent = inferredAgentId ? findAgentById(config, inferredAgentId) : null;
2197
+ if (inferredAgent) {
2198
+ const agentState = getAgentModelState(inferredAgent);
2199
+ if (agentState.primary) {
2200
+ primary = canonicalizeManagedYunyiModelKey(agentState.primary);
2201
+ fallbacks = agentState.fallbacks.map(item => isManagedYunyiModelKey(item) ? canonicalizeManagedYunyiModelKey(item) : item);
2202
+ }
2203
+ agentId = inferredAgent.id;
2204
+ agentName = String(inferredAgent.name || '').trim();
2205
+ }
2035
2206
  }
2036
2207
 
2037
2208
  const providerName = primary.includes('/') ? primary.split('/')[0] : '';
@@ -2233,6 +2404,39 @@ function shouldManageYunyiDefaults(config) {
2233
2404
  return fallbacks.some(isManagedYunyiModelKey);
2234
2405
  }
2235
2406
 
2407
+ function shouldManageYunyiImageDefaults(config) {
2408
+ const imageModelState = normalizeDefaultModelSelection(config?.agents?.defaults?.imageModel, config);
2409
+ const primary = typeof imageModelState.primary === 'string' ? imageModelState.primary.trim() : '';
2410
+ const fallbacks = Array.isArray(imageModelState.fallbacks) ? imageModelState.fallbacks : [];
2411
+ if (!primary) return true;
2412
+ if (isManagedYunyiModelKey(primary)) return true;
2413
+ return fallbacks.some(isManagedYunyiModelKey);
2414
+ }
2415
+
2416
+ function normalizeManagedYunyiImageModel(modelState = {}, options = {}) {
2417
+ const currentPrimary = String(modelState.primary || '').trim();
2418
+ const canPreserve = !options.force && currentPrimary && isManagedYunyiModelKey(currentPrimary);
2419
+ const primary = canPreserve
2420
+ ? canonicalizeManagedYunyiModelKey(currentPrimary)
2421
+ : YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY;
2422
+
2423
+ let fallbacks = Array.isArray(modelState.fallbacks) ? modelState.fallbacks : [];
2424
+ fallbacks = [...new Set(
2425
+ fallbacks
2426
+ .map(item => {
2427
+ const text = String(item || '').trim();
2428
+ return isManagedYunyiModelKey(text) ? canonicalizeManagedYunyiModelKey(text) : text;
2429
+ })
2430
+ .filter(item => item && item !== primary)
2431
+ )];
2432
+
2433
+ if (fallbacks.length === 0) {
2434
+ fallbacks = [isManagedYunyiClaudeModelKey(primary) ? YYMAXAPI_OPENCLAW_GPT_PRIMARY : YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY];
2435
+ }
2436
+
2437
+ return { primary, fallbacks };
2438
+ }
2439
+
2236
2440
  function applyManagedYunyiOpenClawLayout(config, options = {}) {
2237
2441
  if (!isYymaxapiOpenClawBuild()) {
2238
2442
  return { changed: false, applied: false, claudeAgentId: null, preservedMain: false };
@@ -2293,28 +2497,24 @@ function applyManagedYunyiOpenClawLayout(config, options = {}) {
2293
2497
  }
2294
2498
 
2295
2499
  const agentList = ensureAgentList(config);
2500
+ const currentMainAgentState = getAgentModelState(findAgentById(config, YYMAXAPI_OPENCLAW_MAIN_AGENT_ID));
2296
2501
  const mainAgentResult = upsertManagedAgent(agentList, {
2297
2502
  id: YYMAXAPI_OPENCLAW_MAIN_AGENT_ID,
2298
2503
  default: true,
2299
2504
  name: 'yunyi-claude',
2300
- model: {
2301
- primary: YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY,
2302
- fallbacks: [YYMAXAPI_OPENCLAW_CLAUDE_FALLBACK]
2303
- }
2505
+ model: normalizeManagedYunyiModelState('claude', currentMainAgentState, options)
2304
2506
  }, isManagedMainAgent);
2305
2507
  if (mainAgentResult.changed) changed = true;
2306
2508
 
2307
2509
  let claudeAgentId = YYMAXAPI_OPENCLAW_MAIN_AGENT_ID;
2308
2510
  let preservedMain = !mainAgentResult.managed;
2309
2511
  if (!mainAgentResult.managed) {
2512
+ const currentClaudeSideState = getAgentModelState(findAgentById(config, YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID));
2310
2513
  const fallbackClaudeAgentResult = upsertManagedAgent(agentList, {
2311
2514
  id: YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID,
2312
2515
  default: false,
2313
2516
  name: 'yunyi-claude',
2314
- model: {
2315
- primary: YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY,
2316
- fallbacks: [YYMAXAPI_OPENCLAW_CLAUDE_FALLBACK]
2317
- }
2517
+ model: normalizeManagedYunyiModelState('claude', currentClaudeSideState, options)
2318
2518
  }, isManagedClaudeSideAgent);
2319
2519
  if (fallbackClaudeAgentResult.changed) changed = true;
2320
2520
  claudeAgentId = fallbackClaudeAgentResult.managed ? YYMAXAPI_OPENCLAW_ALT_CLAUDE_AGENT_ID : null;
@@ -2322,28 +2522,32 @@ function applyManagedYunyiOpenClawLayout(config, options = {}) {
2322
2522
  changed = true;
2323
2523
  }
2324
2524
 
2525
+ const currentGptAgentState = getAgentModelState(findAgentById(config, YYMAXAPI_OPENCLAW_GPT_AGENT_ID));
2325
2526
  const gptAgentResult = upsertManagedAgent(agentList, {
2326
2527
  id: YYMAXAPI_OPENCLAW_GPT_AGENT_ID,
2327
2528
  default: false,
2328
2529
  name: 'yunyi-gpt',
2329
- model: {
2330
- primary: YYMAXAPI_OPENCLAW_GPT_PRIMARY,
2331
- fallbacks: [YYMAXAPI_OPENCLAW_GPT_FALLBACK]
2332
- }
2530
+ model: normalizeManagedYunyiModelState('codex', currentGptAgentState, options)
2333
2531
  }, isManagedGptAgent);
2334
2532
  if (gptAgentResult.changed) changed = true;
2335
2533
 
2336
2534
  if (shouldManageYunyiDefaults(config)) {
2337
- const nextDefaultsModel = {
2338
- primary: YYMAXAPI_OPENCLAW_CLAUDE_PRIMARY,
2339
- fallbacks: [YYMAXAPI_OPENCLAW_GPT_PRIMARY]
2340
- };
2535
+ const nextDefaultsModel = normalizeManagedYunyiDefaultsModel(config.agents.defaults.model, options);
2341
2536
  if (JSON.stringify(config.agents.defaults.model) !== JSON.stringify(nextDefaultsModel)) {
2342
2537
  config.agents.defaults.model = nextDefaultsModel;
2343
2538
  changed = true;
2344
2539
  }
2345
2540
  }
2346
2541
 
2542
+ if (shouldManageYunyiImageDefaults(config)) {
2543
+ const currentImageModel = normalizeDefaultModelSelection(config.agents.defaults.imageModel, config);
2544
+ const nextImageModel = normalizeManagedYunyiImageModel(currentImageModel, options);
2545
+ if (JSON.stringify(currentImageModel) !== JSON.stringify(nextImageModel)) {
2546
+ config.agents.defaults.imageModel = nextImageModel;
2547
+ changed = true;
2548
+ }
2549
+ }
2550
+
2347
2551
  return { changed, applied: true, claudeAgentId, preservedMain };
2348
2552
  }
2349
2553
 
@@ -5097,7 +5301,8 @@ async function switchModel(paths) {
5097
5301
 
5098
5302
  const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
5099
5303
  applyConfigRepairsWithSync(config, paths);
5100
- const primary = config.agents?.defaults?.model?.primary || '';
5304
+ applyManagedYunyiOpenClawLayout(config);
5305
+ let primary = config.agents?.defaults?.model?.primary || '';
5101
5306
  const providers = config.models?.providers || {};
5102
5307
 
5103
5308
  if (Object.keys(providers).length === 0) {
@@ -5195,6 +5400,7 @@ async function switchModel(paths) {
5195
5400
  }]);
5196
5401
 
5197
5402
  let finalSelected = selected;
5403
+ let selectedAgentId = '';
5198
5404
 
5199
5405
  if (selected === '__other__') {
5200
5406
  if (otherModels.length === 0) {
@@ -5225,42 +5431,51 @@ async function switchModel(paths) {
5225
5431
  return;
5226
5432
  }
5227
5433
 
5228
- const selectedProviderKey = finalSelected.split('/')[0];
5229
- const selectedModelId = finalSelected.split('/')[1];
5230
- config.agents.defaults.model.primary = finalSelected;
5231
- if (!config.agents.defaults.models[finalSelected]) {
5232
- config.agents.defaults.models[finalSelected] = { alias: selectedProviderKey };
5233
- }
5234
-
5235
- // 同步更新 provider.models
5236
- const providerConfig = providers[selectedProviderKey];
5237
- if (providerConfig && selectedModelId) {
5238
- const existingModel = (providerConfig.models || []).find(m => m.id === selectedModelId);
5239
- if (!existingModel) {
5240
- const allModels = [...CLAUDE_MODELS, ...CODEX_MODELS];
5241
- const knownModel = allModels.find(m => m.id === selectedModelId);
5242
- const apiType = providerConfig.api || '';
5243
- const isAnthropic = apiType.startsWith('anthropic');
5244
- const defaultCtx = isAnthropic ? (API_CONFIG.claude?.contextWindow || 200000) : (API_CONFIG.codex?.contextWindow || 200000);
5245
- const defaultMax = isAnthropic ? (API_CONFIG.claude?.maxTokens || 8192) : (API_CONFIG.codex?.maxTokens || 8192);
5246
- providerConfig.models = [{
5247
- id: selectedModelId,
5248
- name: knownModel ? knownModel.name : selectedModelId,
5249
- contextWindow: defaultCtx,
5250
- maxTokens: defaultMax
5251
- }];
5252
- } else {
5253
- providerConfig.models = [existingModel];
5434
+ if (isManagedYunyiModelKey(finalSelected)) {
5435
+ const selectionResult = applyManagedYunyiModelSelection(config, finalSelected);
5436
+ finalSelected = selectionResult.selectedModelKey;
5437
+ selectedAgentId = selectionResult.agentId || '';
5438
+ } else {
5439
+ const selectedProviderKey = finalSelected.split('/')[0];
5440
+ const selectedModelId = finalSelected.split('/')[1];
5441
+ config.agents.defaults.model.primary = finalSelected;
5442
+ if (!config.agents.defaults.models[finalSelected]) {
5443
+ config.agents.defaults.models[finalSelected] = { alias: selectedProviderKey };
5444
+ }
5445
+
5446
+ // 同步更新 provider.models
5447
+ const providerConfig = providers[selectedProviderKey];
5448
+ if (providerConfig && selectedModelId) {
5449
+ const existingModel = (providerConfig.models || []).find(m => m.id === selectedModelId);
5450
+ if (!existingModel) {
5451
+ const allModels = [...CLAUDE_MODELS, ...CODEX_MODELS];
5452
+ const knownModel = allModels.find(m => m.id === selectedModelId);
5453
+ const apiType = providerConfig.api || '';
5454
+ const isAnthropic = apiType.startsWith('anthropic');
5455
+ const defaultCtx = isAnthropic ? (API_CONFIG.claude?.contextWindow || 200000) : (API_CONFIG.codex?.contextWindow || 200000);
5456
+ const defaultMax = isAnthropic ? (API_CONFIG.claude?.maxTokens || 8192) : (API_CONFIG.codex?.maxTokens || 8192);
5457
+ providerConfig.models = [{
5458
+ id: selectedModelId,
5459
+ name: knownModel ? knownModel.name : selectedModelId,
5460
+ contextWindow: defaultCtx,
5461
+ maxTokens: defaultMax
5462
+ }];
5463
+ } else {
5464
+ providerConfig.models = [existingModel];
5465
+ }
5254
5466
  }
5255
- }
5256
5467
 
5257
- config.agents.defaults.model.fallbacks = (config.agents.defaults.model.fallbacks || []).filter(k => k !== finalSelected);
5258
- if (primary && primary !== finalSelected && !config.agents.defaults.model.fallbacks.includes(primary)) {
5259
- config.agents.defaults.model.fallbacks.push(primary);
5468
+ config.agents.defaults.model.fallbacks = (config.agents.defaults.model.fallbacks || []).filter(k => k !== finalSelected);
5469
+ if (primary && primary !== finalSelected && !config.agents.defaults.model.fallbacks.includes(primary)) {
5470
+ config.agents.defaults.model.fallbacks.push(primary);
5471
+ }
5260
5472
  }
5473
+
5474
+ primary = finalSelected;
5261
5475
  ensureGatewaySettings(config);
5262
5476
  writeConfigWithSync(paths, config);
5263
5477
 
5478
+ const selectedProviderKey = finalSelected.split('/')[0];
5264
5479
  const selectedProviderConfig = providers[selectedProviderKey];
5265
5480
  const selectedLabel = PROVIDER_LABELS[selectedProviderKey] || selectedProviderKey;
5266
5481
  console.log(chalk.green(`\n✅ 已切换到 ${finalSelected}`));
@@ -5286,7 +5501,7 @@ async function switchModel(paths) {
5286
5501
 
5287
5502
  // 重启后直接测试连接
5288
5503
  console.log('');
5289
- await testConnection(paths, {});
5504
+ await testConnection(paths, selectedAgentId ? { agent: selectedAgentId } : {});
5290
5505
  }
5291
5506
  // ============ 权限管理 (tools.profile) ============
5292
5507
  const TOOLS_PROFILES = [
@@ -43,7 +43,7 @@
43
43
  "codex": {
44
44
  "urlSuffix": "/codex",
45
45
  "api": "openai-completions",
46
- "contextWindow": 1050000,
46
+ "contextWindow": 1000000,
47
47
  "maxTokens": 128000,
48
48
  "providerName": "yunyi"
49
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.88",
3
+ "version": "1.0.90",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {