From 4cf0e4e948d5bc1e7383db9e51913f9cafbb6873 Mon Sep 17 00:00:00 2001 From: stswangzhiping <59632378+stswangzhiping@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:53:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E8=BF=9E=E6=97=B6=20MD5=20?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E6=A8=A1=E5=9E=8B=E5=88=97=E8=A1=A8=EF=BC=8C?= =?UTF-8?q?=E6=9C=89=E5=8F=98=E5=8C=96=E6=89=8D=E6=9B=B4=E6=96=B0=20opencl?= =?UTF-8?q?aw.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 computeModelsMd5():对模型 id 列表排序后取 MD5 - 新增 refreshModelsIfChanged():读现有 provider 配置拉新模型,MD5 不同才写盘 - client.js: 重连(active + 无完整 provider)时调用 refreshModelsIfChanged,而非直接跳过 Made-with: Cursor --- lib/client.js | 7 ++-- lib/openclaw-provider.js | 78 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/lib/client.js b/lib/client.js index ce2e0f1..1b47205 100644 --- a/lib/client.js +++ b/lib/client.js @@ -12,7 +12,7 @@ const { getDashboardInfo, resolveOpenclawConfigFile, startTtyd, FrpcManager } = const { ProvisionManager } = require('./provisioning'); const { BtMonitor } = require('./bt-monitor'); const { hasInternet, hasWiredInternetProbe, getLocalIps } = require('./network'); -const { applyFullProviderFromVps, removeProviderByName, isFullProvider } = require('./openclaw-provider'); +const { applyFullProviderFromVps, removeProviderByName, refreshModelsIfChanged, isFullProvider } = require('./openclaw-provider'); const led = require('./led'); const MAX_BACKOFF_MS = 60_000; @@ -469,7 +469,10 @@ class ClawClient { this._updateOpenClawOrigin(clawIdStr); }); } else { - this._updateOpenClawOrigin(clawIdStr); + // 重连场景:检查模型列表是否有变化,有变化才写盘 + refreshModelsIfChanged(() => { + this._updateOpenClawOrigin(clawIdStr); + }); } } } diff --git a/lib/openclaw-provider.js b/lib/openclaw-provider.js index 76b0492..81b65be 100644 --- a/lib/openclaw-provider.js +++ b/lib/openclaw-provider.js @@ -4,6 +4,7 @@ const fs = require('fs'); const path = require('path'); const http = require('http'); const https = require('https'); +const crypto = require('crypto'); const log = require('./logger'); const { resolveOpenclawConfigFile } = require('./frpc'); @@ -246,9 +247,86 @@ function isFullProvider(p) { || Object.prototype.hasOwnProperty.call(p, 'baseUrl'); } +/** + * 对模型列表计算 MD5(按 id 排序后 JSON 序列化),用于变更检测。 + */ +function computeModelsMd5(models) { + const ids = (models || []).map((m) => m.id).sort(); + return crypto.createHash('md5').update(JSON.stringify(ids)).digest('hex'); +} + +/** + * 重连时刷新模型列表:读取现有 openclaw.json 中第一个 provider 的 baseUrl/apiKey, + * 拉取最新模型,MD5 与现有模型对比,不一致才写盘(触发 gateway 自动重启)。 + * 若模型未变则跳过,不写盘,不触发 gateway 重启。 + * 完成后调用 onDone()(无论是否更新)。 + */ +function refreshModelsIfChanged(onDone) { + if (_busy) { + log.info('openclaw-provider', 'refreshModels: 有操作进行中,跳过'); + if (typeof onDone === 'function') onDone(); + return; + } + + const configFile = resolveOpenclawConfigFile(); + if (!configFile) { + if (typeof onDone === 'function') onDone(); + return; + } + + let config; + try { + config = readJsonFile(configFile); + } catch (e) { + log.warn('openclaw-provider', `refreshModels: 读取配置失败: ${e.message}`); + if (typeof onDone === 'function') onDone(); + return; + } + + const providers = config.models?.providers || {}; + const providerId = Object.keys(providers)[0]; + if (!providerId) { + log.info('openclaw-provider', 'refreshModels: 未找到已配置的 provider,跳过'); + if (typeof onDone === 'function') onDone(); + return; + } + + const providerCfg = providers[providerId]; + const baseUrl = providerCfg.baseUrl || ''; + const apiKey = providerCfg.apiKey || ''; + const currentModels = providerCfg.models || []; + + _busy = true; + fetchModels(baseUrl, apiKey, (err, newModels) => { + try { + if (err) { + log.warn('openclaw-provider', `refreshModels: 拉模型失败: ${err.message}`); + return; + } + + const currentMd5 = computeModelsMd5(currentModels); + const newMd5 = computeModelsMd5(newModels); + + if (currentMd5 === newMd5) { + log.info('openclaw-provider', `模型列表未变化(${newModels.length} 个),跳过更新`); + return; + } + + log.info('openclaw-provider', `模型列表已变化(${currentModels.length} → ${newModels.length} 个),更新 openclaw.json`); + addProviderSync(configFile, providerId, baseUrl, apiKey, newModels, null); + } catch (e) { + log.error('openclaw-provider', `refreshModels: ${e.message}`); + } finally { + _busy = false; + if (typeof onDone === 'function') onDone(); + } + }); +} + module.exports = { applyFullProviderFromVps, removeProviderByName, + refreshModelsIfChanged, isFullProvider, DEFAULT_BASE_URL, };