Skip to content

Commit 6110782

Browse files
maxcotowtfsayo
andauthored
feat: add a way to create/store/restore agents in the filesystem (#2389)
* Add a way to create/store/restore agents in the filesystem at agents/data/characters * Update .env.example --------- Co-authored-by: Sayo <hi@sayo.wtf>
1 parent 9fbdab8 commit 6110782

File tree

3 files changed

+75
-5
lines changed

3 files changed

+75
-5
lines changed

.env.example

+4-1
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
DEFAULT_LOG_LEVEL=warn
2326
LOG_JSON_FORMAT=false # Print everything in logger as json; false by default
@@ -92,7 +95,7 @@ MEDIUM_OPENAI_MODEL= # Default: gpt-4o
9295
LARGE_OPENAI_MODEL= # Default: gpt-4o
9396
EMBEDDING_OPENAI_MODEL= # Default: text-embedding-3-small
9497
IMAGE_OPENAI_MODEL= # Default: dall-e-3
95-
USE_OPENAI_EMBEDDING= # Set to TRUE for OpenAI/1536, leave blank for local
98+
USE_OPENAI_EMBEDDING=TRUE # Set to TRUE for OpenAI/1536, leave blank for local
9699

97100
# Community Plugin for OpenAI Configuration
98101
ENABLE_OPEN_AI_COMMUNITY_PLUGIN=false

agent/src/index.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -389,10 +389,31 @@ function commaSeparatedStringToArray(commaSeparated: string): string[] {
389389
return commaSeparated?.split(",").map((value) => value.trim());
390390
}
391391

392+
async function readCharactersFromStorage(characterPaths: string[]): Promise<string[]> {
393+
try {
394+
const uploadDir = path.join(process.cwd(), "data", "characters");
395+
await fs.promises.mkdir(uploadDir, { recursive: true });
396+
const fileNames = await fs.promises.readdir(uploadDir);
397+
fileNames.forEach(fileName => {
398+
characterPaths.push(path.join(uploadDir, fileName));
399+
});
400+
} catch (err) {
401+
elizaLogger.error(`Error reading directory: ${err.message}`);
402+
}
403+
404+
return characterPaths;
405+
};
406+
392407
export async function loadCharacters(
393408
charactersArg: string
394409
): Promise<Character[]> {
395-
const characterPaths = commaSeparatedStringToArray(charactersArg);
410+
411+
let characterPaths = commaSeparatedStringToArray(charactersArg);
412+
413+
if(process.env.USE_CHARACTER_STORAGE === "true") {
414+
characterPaths = await readCharactersFromStorage(characterPaths);
415+
}
416+
396417
const loadedCharacters: Character[] = [];
397418

398419
if (characterPaths?.length > 0) {
@@ -1248,7 +1269,9 @@ const startAgents = async () => {
12481269
characters = await loadCharacterFromOnchain();
12491270
}
12501271

1251-
if ((!onchainJson && charactersArg) || hasValidRemoteUrls()) {
1272+
const notOnchainJson = !onchainJson || onchainJson == "null";
1273+
1274+
if ((notOnchainJson && charactersArg) || hasValidRemoteUrls()) {
12521275
characters = await loadCharacters(charactersArg);
12531276
}
12541277

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
type AgentRuntime,
@@ -80,6 +82,16 @@ export function createApiRouter(
8082
res.json({ agents: agentsList });
8183
});
8284

85+
router.get('/storage', async (req, res) => {
86+
try {
87+
const uploadDir = path.join(process.cwd(), "data", "characters");
88+
const files = await fs.promises.readdir(uploadDir);
89+
res.json({ files });
90+
} catch (error) {
91+
res.status(500).json({ error: error.message });
92+
}
93+
});
94+
8395
router.get("/agents/:agentId", (req, res) => {
8496
const { agentId } = validateUUIDParams(req.params, res) ?? {
8597
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)