Skip to content

Commit ea154a0

Browse files
authored
Merge pull request #2159 from bark-ruffalo/inheritance
inheritance of character from parent using extends key
2 parents 1ca639e + 8d831fa commit ea154a0

File tree

4 files changed

+60
-26
lines changed

4 files changed

+60
-26
lines changed

agent/src/index.ts

+56-26
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,61 @@ function tryLoadFile(filePath: string): string | null {
148148
return null;
149149
}
150150
}
151+
function mergeCharacters(base: Character, child: Character): Character {
152+
const mergeObjects = (baseObj: any, childObj: any) => {
153+
const result: any = {};
154+
const keys = new Set([...Object.keys(baseObj || {}), ...Object.keys(childObj || {})]);
155+
keys.forEach(key => {
156+
if (typeof baseObj[key] === 'object' && typeof childObj[key] === 'object' && !Array.isArray(baseObj[key]) && !Array.isArray(childObj[key])) {
157+
result[key] = mergeObjects(baseObj[key], childObj[key]);
158+
} else if (Array.isArray(baseObj[key]) || Array.isArray(childObj[key])) {
159+
result[key] = [...(baseObj[key] || []), ...(childObj[key] || [])];
160+
} else {
161+
result[key] = childObj[key] !== undefined ? childObj[key] : baseObj[key];
162+
}
163+
});
164+
return result;
165+
};
166+
return mergeObjects(base, child);
167+
}
168+
async function loadCharacter(filePath: string): Promise<Character> {
169+
const content = tryLoadFile(filePath);
170+
if (!content) {
171+
throw new Error(`Character file not found: ${filePath}`);
172+
}
173+
let character = JSON.parse(content);
174+
validateCharacterConfig(character);
175+
176+
// .id isn't really valid
177+
const characterId = character.id || character.name;
178+
const characterPrefix = `CHARACTER.${characterId.toUpperCase().replace(/ /g, "_")}.`;
179+
const characterSettings = Object.entries(process.env)
180+
.filter(([key]) => key.startsWith(characterPrefix))
181+
.reduce((settings, [key, value]) => {
182+
const settingKey = key.slice(characterPrefix.length);
183+
return { ...settings, [settingKey]: value };
184+
}, {});
185+
if (Object.keys(characterSettings).length > 0) {
186+
character.settings = character.settings || {};
187+
character.settings.secrets = {
188+
...characterSettings,
189+
...character.settings.secrets,
190+
};
191+
}
192+
// Handle plugins
193+
character.plugins = await handlePluginImporting(
194+
character.plugins
195+
);
196+
if (character.extends) {
197+
elizaLogger.info(`Merging ${character.name} character with parent characters`);
198+
for (const extendPath of character.extends) {
199+
const baseCharacter = await loadCharacter(path.resolve(path.dirname(filePath), extendPath));
200+
character = mergeCharacters(baseCharacter, character);
201+
elizaLogger.info(`Merged ${character.name} with ${baseCharacter.name}`);
202+
}
203+
}
204+
return character;
205+
}
151206

152207
export async function loadCharacters(
153208
charactersArg: string
@@ -211,32 +266,7 @@ export async function loadCharacters(
211266
}
212267

213268
try {
214-
const character = JSON.parse(content);
215-
validateCharacterConfig(character);
216-
217-
// .id isn't really valid
218-
const characterId = character.id || character.name;
219-
const characterPrefix = `CHARACTER.${characterId.toUpperCase().replace(/ /g, "_")}.`;
220-
221-
const characterSettings = Object.entries(process.env)
222-
.filter(([key]) => key.startsWith(characterPrefix))
223-
.reduce((settings, [key, value]) => {
224-
const settingKey = key.slice(characterPrefix.length);
225-
return { ...settings, [settingKey]: value };
226-
}, {});
227-
228-
if (Object.keys(characterSettings).length > 0) {
229-
character.settings = character.settings || {};
230-
character.settings.secrets = {
231-
...characterSettings,
232-
...character.settings.secrets,
233-
};
234-
}
235-
236-
// Handle plugins
237-
character.plugins = await handlePluginImporting(
238-
character.plugins
239-
);
269+
const character: Character = await loadCharacter(resolvedPath);
240270

241271
loadedCharacters.push(character);
242272
elizaLogger.info(

packages/core/src/defaultCharacter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -527,4 +527,5 @@ export const defaultCharacter: Character = {
527527
"meticulous",
528528
"provocative",
529529
],
530+
extends: [],
530531
};

packages/core/src/environment.ts

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export const CharacterSchema = z.object({
135135
prompt: z.string().optional(),
136136
})
137137
.optional(),
138+
extends: z.array(z.string()).optional(),
138139
});
139140

140141
// Type inference

packages/core/src/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,8 @@ export type Character = {
872872
nft?: {
873873
prompt: string;
874874
};
875+
/**Optinal Parent characters to inherit information from */
876+
extends?: string[];
875877
};
876878

877879
/**

0 commit comments

Comments
 (0)