@@ -144,6 +144,61 @@ function tryLoadFile(filePath: string): string | null {
144
144
return null ;
145
145
}
146
146
}
147
+ function mergeCharacters ( base : Character , child : Character ) : Character {
148
+ const mergeObjects = ( baseObj : any , childObj : any ) => {
149
+ const result : any = { } ;
150
+ const keys = new Set ( [ ...Object . keys ( baseObj || { } ) , ...Object . keys ( childObj || { } ) ] ) ;
151
+ keys . forEach ( key => {
152
+ if ( typeof baseObj [ key ] === 'object' && typeof childObj [ key ] === 'object' && ! Array . isArray ( baseObj [ key ] ) && ! Array . isArray ( childObj [ key ] ) ) {
153
+ result [ key ] = mergeObjects ( baseObj [ key ] , childObj [ key ] ) ;
154
+ } else if ( Array . isArray ( baseObj [ key ] ) || Array . isArray ( childObj [ key ] ) ) {
155
+ result [ key ] = [ ...( baseObj [ key ] || [ ] ) , ...( childObj [ key ] || [ ] ) ] ;
156
+ } else {
157
+ result [ key ] = childObj [ key ] !== undefined ? childObj [ key ] : baseObj [ key ] ;
158
+ }
159
+ } ) ;
160
+ return result ;
161
+ } ;
162
+ return mergeObjects ( base , child ) ;
163
+ }
164
+ async function loadCharacter ( filePath : string ) : Promise < Character > {
165
+ const content = tryLoadFile ( filePath ) ;
166
+ if ( ! content ) {
167
+ throw new Error ( `Character file not found: ${ filePath } ` ) ;
168
+ }
169
+ let character = JSON . parse ( content ) ;
170
+ validateCharacterConfig ( character ) ;
171
+
172
+ // .id isn't really valid
173
+ const characterId = character . id || character . name ;
174
+ const characterPrefix = `CHARACTER.${ characterId . toUpperCase ( ) . replace ( / / g, "_" ) } .` ;
175
+ const characterSettings = Object . entries ( process . env )
176
+ . filter ( ( [ key ] ) => key . startsWith ( characterPrefix ) )
177
+ . reduce ( ( settings , [ key , value ] ) => {
178
+ const settingKey = key . slice ( characterPrefix . length ) ;
179
+ return { ...settings , [ settingKey ] : value } ;
180
+ } , { } ) ;
181
+ if ( Object . keys ( characterSettings ) . length > 0 ) {
182
+ character . settings = character . settings || { } ;
183
+ character . settings . secrets = {
184
+ ...characterSettings ,
185
+ ...character . settings . secrets ,
186
+ } ;
187
+ }
188
+ // Handle plugins
189
+ character . plugins = await handlePluginImporting (
190
+ character . plugins
191
+ ) ;
192
+ if ( character . extends ) {
193
+ elizaLogger . info ( `Merging ${ character . name } character with parent characters` ) ;
194
+ for ( const extendPath of character . extends ) {
195
+ const baseCharacter = await loadCharacter ( path . resolve ( path . dirname ( filePath ) , extendPath ) ) ;
196
+ character = mergeCharacters ( baseCharacter , character ) ;
197
+ elizaLogger . info ( `Merged ${ character . name } with ${ baseCharacter . name } ` ) ;
198
+ }
199
+ }
200
+ return character ;
201
+ }
147
202
148
203
export async function loadCharacters (
149
204
charactersArg : string
@@ -207,32 +262,7 @@ export async function loadCharacters(
207
262
}
208
263
209
264
try {
210
- const character = JSON . parse ( content ) ;
211
- validateCharacterConfig ( character ) ;
212
-
213
- // .id isn't really valid
214
- const characterId = character . id || character . name ;
215
- const characterPrefix = `CHARACTER.${ characterId . toUpperCase ( ) . replace ( / / g, "_" ) } .` ;
216
-
217
- const characterSettings = Object . entries ( process . env )
218
- . filter ( ( [ key ] ) => key . startsWith ( characterPrefix ) )
219
- . reduce ( ( settings , [ key , value ] ) => {
220
- const settingKey = key . slice ( characterPrefix . length ) ;
221
- return { ...settings , [ settingKey ] : value } ;
222
- } , { } ) ;
223
-
224
- if ( Object . keys ( characterSettings ) . length > 0 ) {
225
- character . settings = character . settings || { } ;
226
- character . settings . secrets = {
227
- ...characterSettings ,
228
- ...character . settings . secrets ,
229
- } ;
230
- }
231
-
232
- // Handle plugins
233
- character . plugins = await handlePluginImporting (
234
- character . plugins
235
- ) ;
265
+ const character : Character = await loadCharacter ( resolvedPath ) ;
236
266
237
267
loadedCharacters . push ( character ) ;
238
268
elizaLogger . info (
0 commit comments