Skip to content

Commit 0d9717f

Browse files
committed
Add a way to create/store/restore agents in the filesystem at agents/data/characters
1 parent 5a28cd9 commit 0d9717f

File tree

3 files changed

+71
-3
lines changed

3 files changed

+71
-3
lines changed

.env.example

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ SUPABASE_ANON_KEY=
1818
# Comma separated list of remote character urls (optional)
1919
REMOTE_CHARACTER_URLS=
2020

21+
# Stores characters set by using the direct API in the data/character folder for further load when the app restarts
22+
USE_CHARACTER_STORAGE=false
23+
2124
# Logging
2225
LOG_JSON_FORMAT= # Print everything in logger as json; false by default
2326

agent/src/index.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -308,10 +308,30 @@ function commaSeparatedStringToArray(commaSeparated: string): string[] {
308308
return commaSeparated?.split(",").map((value) => value.trim());
309309
}
310310

311+
async function readCharactersFromStorage(characterPaths: string[]): Promise<string[]> {
312+
try {
313+
const uploadDir = path.join(process.cwd(), "data", "characters");
314+
await fs.promises.mkdir(uploadDir, { recursive: true });
315+
const fileNames = await fs.promises.readdir(uploadDir);
316+
fileNames.forEach(fileName => {
317+
characterPaths.push(path.join(uploadDir, fileName));
318+
});
319+
} catch (err) {
320+
elizaLogger.error(`Error reading directory: ${err.message}`);
321+
}
322+
323+
return characterPaths;
324+
};
325+
311326
export async function loadCharacters(
312327
charactersArg: string
313328
): Promise<Character[]> {
314329
let characterPaths = commaSeparatedStringToArray(charactersArg);
330+
331+
if(process.env.USE_CHARACTER_STORAGE === "true") {
332+
characterPaths = await readCharactersFromStorage(characterPaths);
333+
}
334+
315335
const loadedCharacters: Character[] = [];
316336

317337
if (characterPaths?.length > 0) {
@@ -1200,7 +1220,8 @@ const startAgents = async () => {
12001220
characters = await loadCharacterFromOnchain();
12011221
}
12021222

1203-
if ((onchainJson == "null" && charactersArg) || hasValidRemoteUrls()) {
1223+
const notOnchainJson = !onchainJson || onchainJson == "null";
1224+
if ((notOnchainJson && charactersArg) || hasValidRemoteUrls()) {
12041225
characters = await loadCharacters(charactersArg);
12051226
}
12061227

packages/client-direct/src/api.ts

+46-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import express from "express";
22
import bodyParser from "body-parser";
33
import cors from "cors";
4+
import path from "path";
5+
import fs from "fs";
46

57
import {
68
AgentRuntime,
@@ -79,6 +81,16 @@ export function createApiRouter(
7981
res.json({ agents: agentsList });
8082
});
8183

84+
router.get('/storage', async (req, res) => {
85+
try {
86+
const uploadDir = path.join(process.cwd(), "data", "characters");
87+
const files = await fs.promises.readdir(uploadDir);
88+
res.json({ files });
89+
} catch (error) {
90+
res.status(500).json({ error: error.message });
91+
}
92+
});
93+
8294
router.get("/agents/:agentId", (req, res) => {
8395
const { agentId } = validateUUIDParams(req.params, res) ?? {
8496
agentId: null,
@@ -127,7 +139,7 @@ export function createApiRouter(
127139
};
128140
if (!agentId) return;
129141

130-
const agent: AgentRuntime = agents.get(agentId);
142+
let agent: AgentRuntime = agents.get(agentId);
131143

132144
// update character
133145
if (agent) {
@@ -137,6 +149,9 @@ export function createApiRouter(
137149
// if it has a different name, the agentId will change
138150
}
139151

152+
// stores the json data before it is modified with added data
153+
const characterJson = { ...req.body };
154+
140155
// load character from body
141156
const character = req.body;
142157
try {
@@ -152,7 +167,7 @@ export function createApiRouter(
152167

153168
// start it up (and register it)
154169
try {
155-
await directClient.startAgent(character);
170+
agent = await directClient.startAgent(character);
156171
elizaLogger.log(`${character.name} started`);
157172
} catch (e) {
158173
elizaLogger.error(`Error starting agent: ${e}`);
@@ -162,6 +177,35 @@ export function createApiRouter(
162177
});
163178
return;
164179
}
180+
181+
if (process.env.USE_CHARACTER_STORAGE === "true") {
182+
try {
183+
const filename = `${agent.agentId}.json`;
184+
const uploadDir = path.join(
185+
process.cwd(),
186+
"data",
187+
"characters"
188+
);
189+
const filepath = path.join(uploadDir, filename);
190+
await fs.promises.mkdir(uploadDir, { recursive: true });
191+
await fs.promises.writeFile(
192+
filepath,
193+
JSON.stringify(
194+
{ ...characterJson, id: agent.agentId },
195+
null,
196+
2
197+
)
198+
);
199+
elizaLogger.info(
200+
`Character stored successfully at ${filepath}`
201+
);
202+
} catch (error) {
203+
elizaLogger.error(
204+
`Failed to store character: ${error.message}`
205+
);
206+
}
207+
}
208+
165209
res.json({
166210
id: character.id,
167211
character: character,

0 commit comments

Comments
 (0)