@@ -3,22 +3,23 @@ import * as path from 'path'
33import { fileURLToPath } from 'url'
44import type { Provider } from '../tool-manager.js'
55import { getOpenClawConfigPath , getOpenClawDir , getOpenClawModelsPath } from '../paths.js'
6- import { ensureDir , writeJSON } from '../utils/file.js'
7- import { replaceVariables } from '../utils/template.js'
6+ import { ensureDir , fileExists , readJSON , writeJSON } from '../utils/file.js'
7+ import { deepMerge , replaceVariables } from '../utils/template.js'
88
99interface OpenClawModelsFile {
1010 providers ?: Record < string , unknown >
1111 [ key : string ] : unknown
1212}
1313
1414interface OpenClawConfigFile {
15- models ?: {
16- providers ?: Record < string , unknown >
17- [ key : string ] : unknown
18- }
15+ models ?: Record < string , unknown >
1916 agents ?: {
2017 defaults ?: {
2118 workspace ?: string
19+ model ?: {
20+ primary ?: string
21+ [ key : string ] : unknown
22+ }
2223 [ key : string ] : unknown
2324 }
2425 [ key : string ] : unknown
@@ -157,12 +158,22 @@ function resolveProviderName(provider: Provider): string {
157158 return DEFAULT_PROVIDER_NAME
158159}
159160
161+ function loadExistingJSON < T extends object > ( filePath : string ) : T | null {
162+ if ( ! fileExists ( filePath ) ) return null
163+ try {
164+ return readJSON < T > ( filePath )
165+ } catch {
166+ return null
167+ }
168+ }
169+
160170/**
161171 * 写入 OpenClaw 配置
162172 *
163173 * 策略:
164174 * 1. 模板优先 + 内置回退
165- * 2. 每次切换 provider 直接覆盖写入 openclaw.json / models.json
175+ * 2. openclaw.json: models 增量覆盖 + agents 智能合并(强制切换 primary)
176+ * 3. models.json: providers 增量补充(仅更新当前 provider)
166177 * 3. 路径基于 HOME_DIR(通过 paths.ts 统一管理)
167178 */
168179export function writeOpenClawConfig ( provider : Provider ) : void {
@@ -191,18 +202,52 @@ export function writeOpenClawConfig(provider: Provider): void {
191202
192203 const nextOpenClawConfig = replaceVariables ( rawConfigTemplate , variables ) as OpenClawConfigFile
193204 const nextModelsConfig = replaceVariables ( rawModelsTemplate , variables ) as OpenClawModelsFile
194- const currentAgents = nextOpenClawConfig . agents || { }
195- const defaults = currentAgents . defaults || { }
205+ const existingOpenClawConfig = loadExistingJSON < OpenClawConfigFile > ( configPath ) || { }
206+ const existingModelsConfig = loadExistingJSON < OpenClawModelsFile > ( modelsPath ) || { }
196207
197- nextOpenClawConfig . agents = {
198- ...currentAgents ,
199- defaults : {
200- ...defaults ,
201- workspace : homeDir ,
208+ const mergedConfigModels = deepMerge (
209+ existingOpenClawConfig . models || { } ,
210+ nextOpenClawConfig . models || { }
211+ )
212+
213+ const mergedAgents = deepMerge (
214+ nextOpenClawConfig . agents || { } ,
215+ existingOpenClawConfig . agents || { }
216+ )
217+ const mergedDefaults = mergedAgents . defaults || { }
218+ const mergedModel = mergedDefaults . model || { }
219+ const templatePrimary =
220+ nextOpenClawConfig . agents ?. defaults ?. model ?. primary || `${ providerName } /gpt-5.3-codex`
221+ const workspace =
222+ typeof mergedDefaults . workspace === 'string' && mergedDefaults . workspace . trim ( )
223+ ? mergedDefaults . workspace
224+ : homeDir
225+
226+ const finalOpenClawConfig : OpenClawConfigFile = {
227+ ...existingOpenClawConfig ,
228+ models : mergedConfigModels ,
229+ agents : {
230+ ...mergedAgents ,
231+ defaults : {
232+ ...mergedDefaults ,
233+ workspace,
234+ model : {
235+ ...mergedModel ,
236+ primary : templatePrimary ,
237+ } ,
238+ } ,
202239 } ,
203240 }
204241
205- // 直接覆盖写入(不读取/不合并现有文件)
206- writeJSON ( configPath , nextOpenClawConfig )
207- writeJSON ( modelsPath , nextModelsConfig )
242+ const mergedProviders = deepMerge (
243+ existingModelsConfig . providers || { } ,
244+ nextModelsConfig . providers || { }
245+ )
246+ const finalModelsConfig : OpenClawModelsFile = {
247+ ...existingModelsConfig ,
248+ providers : mergedProviders ,
249+ }
250+
251+ writeJSON ( configPath , finalOpenClawConfig )
252+ writeJSON ( modelsPath , finalModelsConfig )
208253}
0 commit comments