@elizaos/plugin-local-storage 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +162 -0
- package/dist/index.js.map +12 -0
- package/dist/services/local-storage.d.ts +87 -0
- package/dist/types.d.ts +50 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @elizaos/plugin-local-storage
|
|
2
|
+
|
|
3
|
+
Local filesystem attachment storage for Eliza agents. This is the default
|
|
4
|
+
fallback storage backend used when Eliza Cloud storage is not connected.
|
|
5
|
+
|
|
6
|
+
The plugin registers a `Service` under `ServiceType.REMOTE_FILES` so consumers
|
|
7
|
+
that previously read from `runtime.getService(ServiceType.REMOTE_FILES)` keep
|
|
8
|
+
working without code changes after the deprecated `@elizaos/plugin-s3-storage`
|
|
9
|
+
package was removed.
|
|
10
|
+
|
|
11
|
+
## Storage root
|
|
12
|
+
|
|
13
|
+
The root directory is resolved in this order:
|
|
14
|
+
|
|
15
|
+
1. `runtime.getSetting("LOCAL_STORAGE_PATH")`
|
|
16
|
+
2. `process.env.LOCAL_STORAGE_PATH`
|
|
17
|
+
3. `${ELIZA_STATE_DIR ?? `${os.homedir()}/.eliza`}/attachments`
|
|
18
|
+
|
|
19
|
+
The directory is created on `start()` if missing.
|
|
20
|
+
|
|
21
|
+
## Surface
|
|
22
|
+
|
|
23
|
+
`LocalFileStorageService` exposes the same method names as the removed
|
|
24
|
+
`AwsS3Service` so call sites can be retargeted with no refactor:
|
|
25
|
+
|
|
26
|
+
- `uploadFile(filePath, subDirectory?)`
|
|
27
|
+
- `uploadBytes(data, fileName, contentType, subDirectory?)`
|
|
28
|
+
- `uploadJson(jsonData, fileName?, subDirectory?)`
|
|
29
|
+
- `downloadBytes(_unusedBucket, key)`
|
|
30
|
+
- `downloadFile(_unusedBucket, key, localPath)`
|
|
31
|
+
- `delete(_unusedBucket, key)`
|
|
32
|
+
- `exists(_unusedBucket, key)`
|
|
33
|
+
- `generateSignedUrl(fileName, _expiresIn?)` — returns a `file://` absolute path
|
|
34
|
+
|
|
35
|
+
`_unusedBucket` parameters exist purely for drop-in API compatibility with
|
|
36
|
+
the previous S3 service. They are ignored — keys always resolve under the
|
|
37
|
+
configured storage root.
|
|
38
|
+
|
|
39
|
+
## Migration from `@elizaos/plugin-s3-storage`
|
|
40
|
+
|
|
41
|
+
To migrate from a self-hosted S3 bucket:
|
|
42
|
+
|
|
43
|
+
- Point Eliza Cloud at it via the R2-compatible bucket UI, or
|
|
44
|
+
- Copy attachments to the local storage root and run with this plugin.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// src/services/local-storage.ts
|
|
2
|
+
import { promises as fsp } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Storage } from "@brighter/storage-adapter-local";
|
|
5
|
+
import {
|
|
6
|
+
logger,
|
|
7
|
+
resolveStateDir,
|
|
8
|
+
Service,
|
|
9
|
+
ServiceType
|
|
10
|
+
} from "@elizaos/core";
|
|
11
|
+
function resolveStorageRoot(runtime) {
|
|
12
|
+
const fromRuntime = runtime.getSetting("LOCAL_STORAGE_PATH");
|
|
13
|
+
if (typeof fromRuntime === "string" && fromRuntime.length > 0) {
|
|
14
|
+
return path.resolve(fromRuntime);
|
|
15
|
+
}
|
|
16
|
+
const fromEnv = process.env.LOCAL_STORAGE_PATH;
|
|
17
|
+
if (typeof fromEnv === "string" && fromEnv.length > 0) {
|
|
18
|
+
return path.resolve(fromEnv);
|
|
19
|
+
}
|
|
20
|
+
return path.join(resolveStateDir(), "attachments");
|
|
21
|
+
}
|
|
22
|
+
function joinKey(...segments) {
|
|
23
|
+
const joined = segments.filter((s) => typeof s === "string" && s.length > 0).join("/");
|
|
24
|
+
return joined.replace(/\/+/g, "/").replace(/^\/+/, "");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class LocalFileStorageService extends Service {
|
|
28
|
+
static serviceType = ServiceType.REMOTE_FILES;
|
|
29
|
+
capabilityDescription = "Local filesystem attachment storage";
|
|
30
|
+
storage = null;
|
|
31
|
+
storageRoot = "";
|
|
32
|
+
static async start(runtime) {
|
|
33
|
+
logger.log("Initializing LocalFileStorageService");
|
|
34
|
+
const service = new LocalFileStorageService(runtime);
|
|
35
|
+
await service.initialize(runtime);
|
|
36
|
+
return service;
|
|
37
|
+
}
|
|
38
|
+
static async stop(runtime) {
|
|
39
|
+
const service = runtime.getService(ServiceType.REMOTE_FILES);
|
|
40
|
+
if (service) {
|
|
41
|
+
await service.stop();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async stop() {
|
|
45
|
+
this.storage = null;
|
|
46
|
+
}
|
|
47
|
+
get root() {
|
|
48
|
+
return this.storageRoot;
|
|
49
|
+
}
|
|
50
|
+
async initialize(runtime) {
|
|
51
|
+
this.storageRoot = resolveStorageRoot(runtime);
|
|
52
|
+
await fsp.mkdir(this.storageRoot, { recursive: true });
|
|
53
|
+
this.storage = Storage({ path: this.storageRoot });
|
|
54
|
+
}
|
|
55
|
+
getStorage() {
|
|
56
|
+
if (!this.storage) {
|
|
57
|
+
throw new Error("LocalFileStorageService not initialized");
|
|
58
|
+
}
|
|
59
|
+
return this.storage;
|
|
60
|
+
}
|
|
61
|
+
absolutePath(key) {
|
|
62
|
+
return path.join(this.storageRoot, key);
|
|
63
|
+
}
|
|
64
|
+
fileUrl(key) {
|
|
65
|
+
return `file://${this.absolutePath(key)}`;
|
|
66
|
+
}
|
|
67
|
+
async uploadFile(filePath, subDirectory) {
|
|
68
|
+
const storage = this.getStorage();
|
|
69
|
+
const baseFileName = `${Date.now()}-${path.basename(filePath)}`;
|
|
70
|
+
const key = joinKey(subDirectory, baseFileName);
|
|
71
|
+
const buffer = await fsp.readFile(filePath);
|
|
72
|
+
await storage.write(key, buffer, { encoding: "binary" });
|
|
73
|
+
return { success: true, url: this.fileUrl(key) };
|
|
74
|
+
}
|
|
75
|
+
async uploadBytes(data, fileName, contentType, subDirectory) {
|
|
76
|
+
const storage = this.getStorage();
|
|
77
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
78
|
+
const key = joinKey(subDirectory, fileName);
|
|
79
|
+
await storage.write(key, buffer, { encoding: "binary" });
|
|
80
|
+
return { success: true, url: this.fileUrl(key) };
|
|
81
|
+
}
|
|
82
|
+
async uploadJson(jsonData, fileName, subDirectory) {
|
|
83
|
+
if (!jsonData) {
|
|
84
|
+
return { success: false, error: "JSON data is required" };
|
|
85
|
+
}
|
|
86
|
+
const storage = this.getStorage();
|
|
87
|
+
const actualFileName = fileName ?? `${Date.now()}.json`;
|
|
88
|
+
const key = joinKey(subDirectory, actualFileName);
|
|
89
|
+
const body = JSON.stringify(jsonData, null, 2);
|
|
90
|
+
await storage.write(key, body, { encoding: "utf8" });
|
|
91
|
+
return { success: true, key, url: this.fileUrl(key) };
|
|
92
|
+
}
|
|
93
|
+
async downloadBytes(_unusedBucket, key) {
|
|
94
|
+
const storage = this.getStorage();
|
|
95
|
+
const result = await storage.read(key, { encoding: "binary" });
|
|
96
|
+
if (result === undefined) {
|
|
97
|
+
throw new Error(`Object not found: ${key}`);
|
|
98
|
+
}
|
|
99
|
+
if (typeof result === "string") {
|
|
100
|
+
return Buffer.from(result, "binary");
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
async downloadFile(_unusedBucket, key, localPath) {
|
|
105
|
+
const buffer = await this.downloadBytes(_unusedBucket, key);
|
|
106
|
+
await fsp.writeFile(localPath, buffer);
|
|
107
|
+
}
|
|
108
|
+
async delete(_unusedBucket, key) {
|
|
109
|
+
const storage = this.getStorage();
|
|
110
|
+
await storage.remove(key);
|
|
111
|
+
}
|
|
112
|
+
async exists(_unusedBucket, key) {
|
|
113
|
+
const storage = this.getStorage();
|
|
114
|
+
return storage.exists(key);
|
|
115
|
+
}
|
|
116
|
+
async generateSignedUrl(fileName, _expiresIn) {
|
|
117
|
+
return this.fileUrl(fileName);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/types.ts
|
|
122
|
+
var CONTENT_TYPES = {
|
|
123
|
+
".png": "image/png",
|
|
124
|
+
".jpg": "image/jpeg",
|
|
125
|
+
".jpeg": "image/jpeg",
|
|
126
|
+
".gif": "image/gif",
|
|
127
|
+
".webp": "image/webp",
|
|
128
|
+
".pdf": "application/pdf",
|
|
129
|
+
".json": "application/json",
|
|
130
|
+
".txt": "text/plain",
|
|
131
|
+
".html": "text/html",
|
|
132
|
+
".css": "text/css",
|
|
133
|
+
".js": "application/javascript",
|
|
134
|
+
".mp3": "audio/mpeg",
|
|
135
|
+
".mp4": "video/mp4",
|
|
136
|
+
".wav": "audio/wav",
|
|
137
|
+
".webm": "video/webm"
|
|
138
|
+
};
|
|
139
|
+
function getContentType(filePath) {
|
|
140
|
+
const dot = filePath.lastIndexOf(".");
|
|
141
|
+
if (dot === -1)
|
|
142
|
+
return "application/octet-stream";
|
|
143
|
+
const ext = filePath.substring(dot).toLowerCase();
|
|
144
|
+
return CONTENT_TYPES[ext] ?? "application/octet-stream";
|
|
145
|
+
}
|
|
146
|
+
// src/index.ts
|
|
147
|
+
var localStoragePlugin = {
|
|
148
|
+
name: "local-storage",
|
|
149
|
+
description: "Local filesystem attachment storage (default fallback when Eliza Cloud storage is not connected)",
|
|
150
|
+
services: [LocalFileStorageService],
|
|
151
|
+
actions: []
|
|
152
|
+
};
|
|
153
|
+
var src_default = localStoragePlugin;
|
|
154
|
+
export {
|
|
155
|
+
localStoragePlugin,
|
|
156
|
+
getContentType,
|
|
157
|
+
src_default as default,
|
|
158
|
+
LocalFileStorageService,
|
|
159
|
+
CONTENT_TYPES
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
//# debugId=B7B5210279BE486A64756E2164756E21
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/services/local-storage.ts", "../src/types.ts", "../src/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { promises as fsp } from \"node:fs\";\nimport path from \"node:path\";\nimport { Storage } from \"@brighter/storage-adapter-local\";\nimport {\n type IAgentRuntime,\n logger,\n resolveStateDir,\n Service,\n ServiceType,\n} from \"@elizaos/core\";\n\nimport type { JsonUploadResult, JsonValue, UploadResult } from \"../types\";\n\n/**\n * Subset of the @brighter/storage-adapter-local interface that this service\n * actually exercises. Typed locally to avoid leaking the upstream package's\n * loose `string | Buffer` return types into our public API.\n */\ninterface LocalStorage {\n write(path: string, data: Buffer | string, opts?: { encoding?: string }): Promise<void>;\n read(path: string, opts?: { encoding?: string }): Promise<Buffer | string | undefined>;\n exists(path: string): Promise<boolean>;\n remove(path: string, opts?: { recursive?: boolean }): Promise<void>;\n}\n\n/**\n * Resolves the storage root directory. Order of precedence:\n *\n * 1. `runtime.getSetting(\"LOCAL_STORAGE_PATH\")`\n * 2. `process.env.LOCAL_STORAGE_PATH`\n * 3. `<resolveStateDir()>/attachments`\n */\nfunction resolveStorageRoot(runtime: IAgentRuntime): string {\n const fromRuntime = runtime.getSetting(\"LOCAL_STORAGE_PATH\");\n if (typeof fromRuntime === \"string\" && fromRuntime.length > 0) {\n return path.resolve(fromRuntime);\n }\n const fromEnv = process.env.LOCAL_STORAGE_PATH;\n if (typeof fromEnv === \"string\" && fromEnv.length > 0) {\n return path.resolve(fromEnv);\n }\n return path.join(resolveStateDir(), \"attachments\");\n}\n\nfunction joinKey(...segments: Array<string | undefined>): string {\n const joined = segments\n .filter((s): s is string => typeof s === \"string\" && s.length > 0)\n .join(\"/\");\n return joined.replace(/\\/+/g, \"/\").replace(/^\\/+/, \"\");\n}\n\n/**\n * Local filesystem implementation of `ServiceType.REMOTE_FILES`. Backed by\n * `@brighter/storage-adapter-local`. Method names mirror the surface that\n * the removed `@elizaos/plugin-s3-storage` `AwsS3Service` exposed so call\n * sites can be retargeted with no refactor.\n */\nexport class LocalFileStorageService extends Service {\n static override serviceType = ServiceType.REMOTE_FILES;\n capabilityDescription = \"Local filesystem attachment storage\";\n\n private storage: LocalStorage | null = null;\n private storageRoot = \"\";\n\n static override async start(runtime: IAgentRuntime): Promise<LocalFileStorageService> {\n logger.log(\"Initializing LocalFileStorageService\");\n const service = new LocalFileStorageService(runtime);\n await service.initialize(runtime);\n return service;\n }\n\n static async stop(runtime: IAgentRuntime): Promise<void> {\n const service = runtime.getService(ServiceType.REMOTE_FILES);\n if (service) {\n await service.stop();\n }\n }\n\n async stop(): Promise<void> {\n this.storage = null;\n }\n\n /**\n * Filesystem path to the storage root. Useful for tests and tooling.\n */\n get root(): string {\n return this.storageRoot;\n }\n\n private async initialize(runtime: IAgentRuntime): Promise<void> {\n this.storageRoot = resolveStorageRoot(runtime);\n await fsp.mkdir(this.storageRoot, { recursive: true });\n this.storage = Storage({ path: this.storageRoot });\n }\n\n private getStorage(): LocalStorage {\n if (!this.storage) {\n throw new Error(\"LocalFileStorageService not initialized\");\n }\n return this.storage;\n }\n\n private absolutePath(key: string): string {\n return path.join(this.storageRoot, key);\n }\n\n private fileUrl(key: string): string {\n return `file://${this.absolutePath(key)}`;\n }\n\n /**\n * Copy a file from the filesystem into the storage root.\n *\n * @param filePath Source path on the local filesystem.\n * @param subDirectory Optional subdirectory under the storage root.\n */\n async uploadFile(filePath: string, subDirectory?: string): Promise<UploadResult> {\n const storage = this.getStorage();\n const baseFileName = `${Date.now()}-${path.basename(filePath)}`;\n const key = joinKey(subDirectory, baseFileName);\n const buffer = await fsp.readFile(filePath);\n await storage.write(key, buffer, { encoding: \"binary\" });\n return { success: true, url: this.fileUrl(key) };\n }\n\n /**\n * Write raw bytes under a fixed key.\n *\n * @param data Bytes to write.\n * @param fileName Final segment of the storage key.\n * @param contentType Reserved for API parity with the previous S3\n * service. Local storage does not record per-object\n * content types beyond what the OS infers, so this\n * value is currently unused.\n * @param subDirectory Optional subdirectory under the storage root.\n */\n async uploadBytes(\n data: Buffer | Uint8Array,\n fileName: string,\n contentType: string,\n subDirectory?: string\n ): Promise<UploadResult> {\n const storage = this.getStorage();\n const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);\n const key = joinKey(subDirectory, fileName);\n await storage.write(key, buffer, { encoding: \"binary\" });\n void contentType;\n return { success: true, url: this.fileUrl(key) };\n }\n\n /**\n * Serialize a JSON-shaped value and write it under a fixed key.\n *\n * @param jsonData The object to serialize.\n * @param fileName Optional filename. Defaults to `${Date.now()}.json`.\n * @param subDirectory Optional subdirectory under the storage root.\n */\n async uploadJson(\n jsonData: Record<string, JsonValue>,\n fileName?: string,\n subDirectory?: string\n ): Promise<JsonUploadResult> {\n if (!jsonData) {\n return { success: false, error: \"JSON data is required\" };\n }\n const storage = this.getStorage();\n const actualFileName = fileName ?? `${Date.now()}.json`;\n const key = joinKey(subDirectory, actualFileName);\n const body = JSON.stringify(jsonData, null, 2);\n await storage.write(key, body, { encoding: \"utf8\" });\n return { success: true, key, url: this.fileUrl(key) };\n }\n\n /**\n * Read bytes for a previously-stored key.\n *\n * @param _unusedBucket Kept for API parity with the previous S3 service.\n * Local storage has no bucket concept; the value is\n * ignored and the key resolves under the storage\n * root.\n * @param key Storage key (relative path under the root).\n */\n async downloadBytes(_unusedBucket: string, key: string): Promise<Buffer> {\n const storage = this.getStorage();\n const result = await storage.read(key, { encoding: \"binary\" });\n if (result === undefined) {\n throw new Error(`Object not found: ${key}`);\n }\n if (typeof result === \"string\") {\n // Defensive: brighter local always returns Buffer when encoding is\n // 'binary', but the upstream type signature allows string. Keep the\n // public API a strict Buffer.\n return Buffer.from(result, \"binary\");\n }\n return result;\n }\n\n /**\n * Read bytes and write them to a local filesystem path.\n */\n async downloadFile(_unusedBucket: string, key: string, localPath: string): Promise<void> {\n const buffer = await this.downloadBytes(_unusedBucket, key);\n await fsp.writeFile(localPath, buffer);\n }\n\n /**\n * Remove a stored object. Idempotent: removing a missing key throws.\n */\n async delete(_unusedBucket: string, key: string): Promise<void> {\n const storage = this.getStorage();\n await storage.remove(key);\n }\n\n /**\n * Whether a stored object exists.\n */\n async exists(_unusedBucket: string, key: string): Promise<boolean> {\n const storage = this.getStorage();\n return storage.exists(key);\n }\n\n /**\n * Returns a `file://` absolute URL for the stored object.\n *\n * Local storage cannot mint short-lived signed URLs the way S3 can — the\n * URL is permanent and exposes the absolute filesystem path. Callers that\n * need a public, expiring URL should route attachment storage through\n * Eliza Cloud instead.\n *\n * @param fileName Storage key (relative path under the root).\n * @param _expiresIn Reserved for API parity with the previous S3 service.\n */\n async generateSignedUrl(fileName: string, _expiresIn?: number): Promise<string> {\n return this.fileUrl(fileName);\n }\n}\n\nexport default LocalFileStorageService;\n",
|
|
6
|
+
"/**\n * JSON-serializable primitive values.\n */\nexport type JsonPrimitive = string | number | boolean | null;\n\n/**\n * JSON-serializable object type.\n */\nexport interface JsonObject {\n [key: string]: JsonPrimitive | JsonObject | JsonArray;\n}\n\n/**\n * JSON-serializable array type.\n */\nexport type JsonArray = Array<JsonPrimitive | JsonObject | JsonArray>;\n\n/**\n * JSON-serializable value type.\n */\nexport type JsonValue = JsonPrimitive | JsonObject | JsonArray;\n\n/**\n * Result of a binary upload operation.\n *\n * Mirrors the shape returned by the removed `@elizaos/plugin-s3-storage`\n * `AwsS3Service` so callers can be retargeted without refactoring.\n */\nexport interface UploadResult {\n success: boolean;\n /** Absolute `file://` URL for the stored object on success. */\n url?: string;\n /** Human-readable error message on failure. */\n error?: string;\n}\n\n/**\n * Result of a JSON upload operation. Same as `UploadResult` plus the\n * resolved storage key.\n */\nexport interface JsonUploadResult extends UploadResult {\n /** Storage key (relative path under the storage root). */\n key?: string;\n}\n\n/**\n * Mapping from filename extension to MIME type. Mirrors the table that\n * shipped with the deprecated S3 plugin so behavior matches for callers\n * that depended on the original content-type inference.\n */\nexport const CONTENT_TYPES: Readonly<Record<string, string>> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".pdf\": \"application/pdf\",\n \".json\": \"application/json\",\n \".txt\": \"text/plain\",\n \".html\": \"text/html\",\n \".css\": \"text/css\",\n \".js\": \"application/javascript\",\n \".mp3\": \"audio/mpeg\",\n \".mp4\": \"video/mp4\",\n \".wav\": \"audio/wav\",\n \".webm\": \"video/webm\",\n};\n\n/**\n * Resolve a Content-Type by extension. Falls back to\n * `application/octet-stream` when the extension is unknown.\n */\nexport function getContentType(filePath: string): string {\n const dot = filePath.lastIndexOf(\".\");\n if (dot === -1) return \"application/octet-stream\";\n const ext = filePath.substring(dot).toLowerCase();\n return CONTENT_TYPES[ext] ?? \"application/octet-stream\";\n}\n",
|
|
7
|
+
"import type { Plugin } from \"@elizaos/core\";\n\nimport { LocalFileStorageService } from \"./services/local-storage\";\n\nexport * from \"./types\";\nexport { LocalFileStorageService };\n\nexport const localStoragePlugin: Plugin = {\n name: \"local-storage\",\n description:\n \"Local filesystem attachment storage (default fallback when Eliza Cloud storage is not connected)\",\n services: [LocalFileStorageService],\n actions: [],\n};\n\nexport default localStoragePlugin;\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "B7B5210279BE486A64756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type IAgentRuntime, Service } from "@elizaos/core";
|
|
2
|
+
import type { JsonUploadResult, JsonValue, UploadResult } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* Local filesystem implementation of `ServiceType.REMOTE_FILES`. Backed by
|
|
5
|
+
* `@brighter/storage-adapter-local`. Method names mirror the surface that
|
|
6
|
+
* the removed `@elizaos/plugin-s3-storage` `AwsS3Service` exposed so call
|
|
7
|
+
* sites can be retargeted with no refactor.
|
|
8
|
+
*/
|
|
9
|
+
export declare class LocalFileStorageService extends Service {
|
|
10
|
+
static serviceType: "aws_s3";
|
|
11
|
+
capabilityDescription: string;
|
|
12
|
+
private storage;
|
|
13
|
+
private storageRoot;
|
|
14
|
+
static start(runtime: IAgentRuntime): Promise<LocalFileStorageService>;
|
|
15
|
+
static stop(runtime: IAgentRuntime): Promise<void>;
|
|
16
|
+
stop(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Filesystem path to the storage root. Useful for tests and tooling.
|
|
19
|
+
*/
|
|
20
|
+
get root(): string;
|
|
21
|
+
private initialize;
|
|
22
|
+
private getStorage;
|
|
23
|
+
private absolutePath;
|
|
24
|
+
private fileUrl;
|
|
25
|
+
/**
|
|
26
|
+
* Copy a file from the filesystem into the storage root.
|
|
27
|
+
*
|
|
28
|
+
* @param filePath Source path on the local filesystem.
|
|
29
|
+
* @param subDirectory Optional subdirectory under the storage root.
|
|
30
|
+
*/
|
|
31
|
+
uploadFile(filePath: string, subDirectory?: string): Promise<UploadResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Write raw bytes under a fixed key.
|
|
34
|
+
*
|
|
35
|
+
* @param data Bytes to write.
|
|
36
|
+
* @param fileName Final segment of the storage key.
|
|
37
|
+
* @param contentType Reserved for API parity with the previous S3
|
|
38
|
+
* service. Local storage does not record per-object
|
|
39
|
+
* content types beyond what the OS infers, so this
|
|
40
|
+
* value is currently unused.
|
|
41
|
+
* @param subDirectory Optional subdirectory under the storage root.
|
|
42
|
+
*/
|
|
43
|
+
uploadBytes(data: Buffer | Uint8Array, fileName: string, contentType: string, subDirectory?: string): Promise<UploadResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Serialize a JSON-shaped value and write it under a fixed key.
|
|
46
|
+
*
|
|
47
|
+
* @param jsonData The object to serialize.
|
|
48
|
+
* @param fileName Optional filename. Defaults to `${Date.now()}.json`.
|
|
49
|
+
* @param subDirectory Optional subdirectory under the storage root.
|
|
50
|
+
*/
|
|
51
|
+
uploadJson(jsonData: Record<string, JsonValue>, fileName?: string, subDirectory?: string): Promise<JsonUploadResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Read bytes for a previously-stored key.
|
|
54
|
+
*
|
|
55
|
+
* @param _unusedBucket Kept for API parity with the previous S3 service.
|
|
56
|
+
* Local storage has no bucket concept; the value is
|
|
57
|
+
* ignored and the key resolves under the storage
|
|
58
|
+
* root.
|
|
59
|
+
* @param key Storage key (relative path under the root).
|
|
60
|
+
*/
|
|
61
|
+
downloadBytes(_unusedBucket: string, key: string): Promise<Buffer>;
|
|
62
|
+
/**
|
|
63
|
+
* Read bytes and write them to a local filesystem path.
|
|
64
|
+
*/
|
|
65
|
+
downloadFile(_unusedBucket: string, key: string, localPath: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Remove a stored object. Idempotent: removing a missing key throws.
|
|
68
|
+
*/
|
|
69
|
+
delete(_unusedBucket: string, key: string): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Whether a stored object exists.
|
|
72
|
+
*/
|
|
73
|
+
exists(_unusedBucket: string, key: string): Promise<boolean>;
|
|
74
|
+
/**
|
|
75
|
+
* Returns a `file://` absolute URL for the stored object.
|
|
76
|
+
*
|
|
77
|
+
* Local storage cannot mint short-lived signed URLs the way S3 can — the
|
|
78
|
+
* URL is permanent and exposes the absolute filesystem path. Callers that
|
|
79
|
+
* need a public, expiring URL should route attachment storage through
|
|
80
|
+
* Eliza Cloud instead.
|
|
81
|
+
*
|
|
82
|
+
* @param fileName Storage key (relative path under the root).
|
|
83
|
+
* @param _expiresIn Reserved for API parity with the previous S3 service.
|
|
84
|
+
*/
|
|
85
|
+
generateSignedUrl(fileName: string, _expiresIn?: number): Promise<string>;
|
|
86
|
+
}
|
|
87
|
+
export default LocalFileStorageService;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-serializable primitive values.
|
|
3
|
+
*/
|
|
4
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
5
|
+
/**
|
|
6
|
+
* JSON-serializable object type.
|
|
7
|
+
*/
|
|
8
|
+
export interface JsonObject {
|
|
9
|
+
[key: string]: JsonPrimitive | JsonObject | JsonArray;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* JSON-serializable array type.
|
|
13
|
+
*/
|
|
14
|
+
export type JsonArray = Array<JsonPrimitive | JsonObject | JsonArray>;
|
|
15
|
+
/**
|
|
16
|
+
* JSON-serializable value type.
|
|
17
|
+
*/
|
|
18
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
|
19
|
+
/**
|
|
20
|
+
* Result of a binary upload operation.
|
|
21
|
+
*
|
|
22
|
+
* Mirrors the shape returned by the removed `@elizaos/plugin-s3-storage`
|
|
23
|
+
* `AwsS3Service` so callers can be retargeted without refactoring.
|
|
24
|
+
*/
|
|
25
|
+
export interface UploadResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
/** Absolute `file://` URL for the stored object on success. */
|
|
28
|
+
url?: string;
|
|
29
|
+
/** Human-readable error message on failure. */
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Result of a JSON upload operation. Same as `UploadResult` plus the
|
|
34
|
+
* resolved storage key.
|
|
35
|
+
*/
|
|
36
|
+
export interface JsonUploadResult extends UploadResult {
|
|
37
|
+
/** Storage key (relative path under the storage root). */
|
|
38
|
+
key?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Mapping from filename extension to MIME type. Mirrors the table that
|
|
42
|
+
* shipped with the deprecated S3 plugin so behavior matches for callers
|
|
43
|
+
* that depended on the original content-type inference.
|
|
44
|
+
*/
|
|
45
|
+
export declare const CONTENT_TYPES: Readonly<Record<string, string>>;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a Content-Type by extension. Falls back to
|
|
48
|
+
* `application/octet-stream` when the extension is unknown.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getContentType(filePath: string): string;
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elizaos/plugin-local-storage",
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"description": "Local filesystem attachment storage backed by @brighter/storage-adapter-local. Default fallback when Eliza Cloud storage is not connected.",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/elizaos/eliza.git"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
"./package.json": "./package.json",
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@brighter/storage": "^1.6.3",
|
|
27
|
+
"@brighter/storage-adapter-local": "^1.6.3",
|
|
28
|
+
"@elizaos/core": "2.0.0-beta.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@biomejs/biome": "^2.4.14",
|
|
32
|
+
"@types/bun": "^1.3.5",
|
|
33
|
+
"@types/node": "^25.0.3",
|
|
34
|
+
"typescript": "^6.0.3"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "bun run build.ts",
|
|
38
|
+
"dev": "bun --hot build.ts",
|
|
39
|
+
"test": "vitest run --config ./vitest.config.ts",
|
|
40
|
+
"clean": "rm -rf dist .turbo .turbo-tsconfig.json tsconfig.tsbuildinfo",
|
|
41
|
+
"format": "bunx @biomejs/biome format --write .",
|
|
42
|
+
"format:check": "bunx @biomejs/biome format .",
|
|
43
|
+
"lint": "bunx @biomejs/biome check --write --unsafe .",
|
|
44
|
+
"lint:check": "bunx @biomejs/biome check .",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"agentConfig": {
|
|
51
|
+
"pluginType": "elizaos:plugin:1.0.0",
|
|
52
|
+
"pluginParameters": {
|
|
53
|
+
"LOCAL_STORAGE_PATH": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "Filesystem root for attachment storage. Defaults to ${ELIZA_STATE_DIR ?? ~/.eliza}/attachments.",
|
|
56
|
+
"required": false,
|
|
57
|
+
"sensitive": false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"eliza": {
|
|
62
|
+
"platforms": [
|
|
63
|
+
"node"
|
|
64
|
+
],
|
|
65
|
+
"runtime": "node",
|
|
66
|
+
"platformDetails": {
|
|
67
|
+
"node": "Node.js build available — uses node:fs / node:path"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|