agentfs-sdk 0.2.0-pre.1 → 0.2.0-pre.3
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/dist/filesystem.d.ts +7 -2
- package/dist/filesystem.js +72 -31
- package/dist/index.d.ts +17 -9
- package/dist/index.js +19 -20
- package/package.json +1 -1
package/dist/filesystem.d.ts
CHANGED
|
@@ -17,10 +17,15 @@ export declare class Filesystem {
|
|
|
17
17
|
private db;
|
|
18
18
|
private initialized;
|
|
19
19
|
private rootIno;
|
|
20
|
+
private chunkSize;
|
|
20
21
|
constructor(db: Database);
|
|
22
|
+
/**
|
|
23
|
+
* Get the configured chunk size
|
|
24
|
+
*/
|
|
25
|
+
getChunkSize(): number;
|
|
21
26
|
private initialize;
|
|
22
27
|
/**
|
|
23
|
-
* Ensure root directory
|
|
28
|
+
* Ensure config and root directory exist, returns the chunk_size
|
|
24
29
|
*/
|
|
25
30
|
private ensureRoot;
|
|
26
31
|
/**
|
|
@@ -57,7 +62,7 @@ export declare class Filesystem {
|
|
|
57
62
|
private getLinkCount;
|
|
58
63
|
writeFile(path: string, content: string | Buffer): Promise<void>;
|
|
59
64
|
private updateFileContent;
|
|
60
|
-
readFile(path: string): Promise<string>;
|
|
65
|
+
readFile(path: string, encoding?: BufferEncoding): Promise<Buffer | string>;
|
|
61
66
|
readdir(path: string): Promise<string[]>;
|
|
62
67
|
deleteFile(path: string): Promise<void>;
|
|
63
68
|
stat(path: string): Promise<Stats>;
|
package/dist/filesystem.js
CHANGED
|
@@ -9,12 +9,20 @@ const S_IFLNK = 0o120000; // Symbolic link
|
|
|
9
9
|
// Default permissions
|
|
10
10
|
const DEFAULT_FILE_MODE = S_IFREG | 0o644; // Regular file, rw-r--r--
|
|
11
11
|
const DEFAULT_DIR_MODE = S_IFDIR | 0o755; // Directory, rwxr-xr-x
|
|
12
|
+
const DEFAULT_CHUNK_SIZE = 4096;
|
|
12
13
|
class Filesystem {
|
|
13
14
|
constructor(db) {
|
|
14
15
|
this.rootIno = 1;
|
|
16
|
+
this.chunkSize = DEFAULT_CHUNK_SIZE;
|
|
15
17
|
this.db = db;
|
|
16
18
|
this.initialized = this.initialize();
|
|
17
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Get the configured chunk size
|
|
22
|
+
*/
|
|
23
|
+
getChunkSize() {
|
|
24
|
+
return this.chunkSize;
|
|
25
|
+
}
|
|
18
26
|
async initialize() {
|
|
19
27
|
// Ensure database is connected
|
|
20
28
|
try {
|
|
@@ -26,6 +34,13 @@ class Filesystem {
|
|
|
26
34
|
throw error;
|
|
27
35
|
}
|
|
28
36
|
}
|
|
37
|
+
// Create the config table
|
|
38
|
+
await this.db.exec(`
|
|
39
|
+
CREATE TABLE IF NOT EXISTS fs_config (
|
|
40
|
+
key TEXT PRIMARY KEY,
|
|
41
|
+
value TEXT NOT NULL
|
|
42
|
+
)
|
|
43
|
+
`);
|
|
29
44
|
// Create the inode table
|
|
30
45
|
await this.db.exec(`
|
|
31
46
|
CREATE TABLE IF NOT EXISTS fs_inode (
|
|
@@ -54,20 +69,14 @@ class Filesystem {
|
|
|
54
69
|
CREATE INDEX IF NOT EXISTS idx_fs_dentry_parent
|
|
55
70
|
ON fs_dentry(parent_ino, name)
|
|
56
71
|
`);
|
|
57
|
-
// Create the data
|
|
72
|
+
// Create the data chunks table
|
|
58
73
|
await this.db.exec(`
|
|
59
74
|
CREATE TABLE IF NOT EXISTS fs_data (
|
|
60
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
61
75
|
ino INTEGER NOT NULL,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
chunk_index INTEGER NOT NULL,
|
|
77
|
+
data BLOB NOT NULL,
|
|
78
|
+
PRIMARY KEY (ino, chunk_index)
|
|
65
79
|
)
|
|
66
|
-
`);
|
|
67
|
-
// Create index for efficient data block lookups
|
|
68
|
-
await this.db.exec(`
|
|
69
|
-
CREATE INDEX IF NOT EXISTS idx_fs_data_ino_offset
|
|
70
|
-
ON fs_data(ino, offset)
|
|
71
80
|
`);
|
|
72
81
|
// Create the symlink table
|
|
73
82
|
await this.db.exec(`
|
|
@@ -76,13 +85,28 @@ class Filesystem {
|
|
|
76
85
|
target TEXT NOT NULL
|
|
77
86
|
)
|
|
78
87
|
`);
|
|
79
|
-
//
|
|
80
|
-
await this.ensureRoot();
|
|
88
|
+
// Initialize config and root directory, and get chunk_size
|
|
89
|
+
this.chunkSize = await this.ensureRoot();
|
|
81
90
|
}
|
|
82
91
|
/**
|
|
83
|
-
* Ensure root directory
|
|
92
|
+
* Ensure config and root directory exist, returns the chunk_size
|
|
84
93
|
*/
|
|
85
94
|
async ensureRoot() {
|
|
95
|
+
// Ensure chunk_size config exists and get its value
|
|
96
|
+
const configStmt = this.db.prepare("SELECT value FROM fs_config WHERE key = 'chunk_size'");
|
|
97
|
+
const config = await configStmt.get();
|
|
98
|
+
let chunkSize;
|
|
99
|
+
if (!config) {
|
|
100
|
+
const insertConfigStmt = this.db.prepare(`
|
|
101
|
+
INSERT INTO fs_config (key, value) VALUES ('chunk_size', ?)
|
|
102
|
+
`);
|
|
103
|
+
await insertConfigStmt.run(DEFAULT_CHUNK_SIZE.toString());
|
|
104
|
+
chunkSize = DEFAULT_CHUNK_SIZE;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
chunkSize = parseInt(config.value, 10) || DEFAULT_CHUNK_SIZE;
|
|
108
|
+
}
|
|
109
|
+
// Ensure root directory exists
|
|
86
110
|
const stmt = this.db.prepare('SELECT ino FROM fs_inode WHERE ino = ?');
|
|
87
111
|
const root = await stmt.get(this.rootIno);
|
|
88
112
|
if (!root) {
|
|
@@ -93,6 +117,7 @@ class Filesystem {
|
|
|
93
117
|
`);
|
|
94
118
|
await insertStmt.run(this.rootIno, DEFAULT_DIR_MODE, now, now, now);
|
|
95
119
|
}
|
|
120
|
+
return chunkSize;
|
|
96
121
|
}
|
|
97
122
|
/**
|
|
98
123
|
* Normalize a path
|
|
@@ -240,15 +265,22 @@ class Filesystem {
|
|
|
240
265
|
async updateFileContent(ino, content) {
|
|
241
266
|
const buffer = typeof content === 'string' ? Buffer.from(content, 'utf-8') : content;
|
|
242
267
|
const now = Math.floor(Date.now() / 1000);
|
|
243
|
-
// Delete existing data
|
|
268
|
+
// Delete existing data chunks
|
|
244
269
|
const deleteStmt = this.db.prepare('DELETE FROM fs_data WHERE ino = ?');
|
|
245
270
|
await deleteStmt.run(ino);
|
|
246
|
-
// Write data in chunks
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
271
|
+
// Write data in chunks
|
|
272
|
+
if (buffer.length > 0) {
|
|
273
|
+
const stmt = this.db.prepare(`
|
|
274
|
+
INSERT INTO fs_data (ino, chunk_index, data)
|
|
275
|
+
VALUES (?, ?, ?)
|
|
276
|
+
`);
|
|
277
|
+
let chunkIndex = 0;
|
|
278
|
+
for (let offset = 0; offset < buffer.length; offset += this.chunkSize) {
|
|
279
|
+
const chunk = buffer.subarray(offset, Math.min(offset + this.chunkSize, buffer.length));
|
|
280
|
+
await stmt.run(ino, chunkIndex, chunk);
|
|
281
|
+
chunkIndex++;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
252
284
|
// Update inode size and mtime
|
|
253
285
|
const updateStmt = this.db.prepare(`
|
|
254
286
|
UPDATE fs_inode
|
|
@@ -257,30 +289,36 @@ class Filesystem {
|
|
|
257
289
|
`);
|
|
258
290
|
await updateStmt.run(buffer.length, now, ino);
|
|
259
291
|
}
|
|
260
|
-
async readFile(path) {
|
|
292
|
+
async readFile(path, encoding) {
|
|
261
293
|
await this.initialized;
|
|
262
294
|
const ino = await this.resolvePath(path);
|
|
263
295
|
if (ino === null) {
|
|
264
296
|
throw new Error(`ENOENT: no such file or directory, open '${path}'`);
|
|
265
297
|
}
|
|
266
|
-
// Get all data
|
|
298
|
+
// Get all data chunks
|
|
267
299
|
const stmt = this.db.prepare(`
|
|
268
300
|
SELECT data FROM fs_data
|
|
269
301
|
WHERE ino = ?
|
|
270
|
-
ORDER BY
|
|
302
|
+
ORDER BY chunk_index ASC
|
|
271
303
|
`);
|
|
272
304
|
const rows = await stmt.all(ino);
|
|
305
|
+
let combined;
|
|
273
306
|
if (rows.length === 0) {
|
|
274
|
-
|
|
307
|
+
combined = Buffer.alloc(0);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
// Concatenate all chunks
|
|
311
|
+
const buffers = rows.map(row => row.data);
|
|
312
|
+
combined = Buffer.concat(buffers);
|
|
275
313
|
}
|
|
276
|
-
// Concatenate all chunks
|
|
277
|
-
const buffers = rows.map(row => row.data);
|
|
278
|
-
const combined = Buffer.concat(buffers);
|
|
279
314
|
// Update atime
|
|
280
315
|
const now = Math.floor(Date.now() / 1000);
|
|
281
316
|
const updateStmt = this.db.prepare('UPDATE fs_inode SET atime = ? WHERE ino = ?');
|
|
282
317
|
await updateStmt.run(now, ino);
|
|
283
|
-
|
|
318
|
+
if (encoding) {
|
|
319
|
+
return combined.toString(encoding);
|
|
320
|
+
}
|
|
321
|
+
return combined;
|
|
284
322
|
}
|
|
285
323
|
async readdir(path) {
|
|
286
324
|
await this.initialized;
|
|
@@ -316,9 +354,12 @@ class Filesystem {
|
|
|
316
354
|
// Check if this was the last link to the inode
|
|
317
355
|
const linkCount = await this.getLinkCount(ino);
|
|
318
356
|
if (linkCount === 0) {
|
|
319
|
-
// Delete the inode
|
|
320
|
-
const
|
|
321
|
-
await
|
|
357
|
+
// Delete the inode
|
|
358
|
+
const deleteInodeStmt = this.db.prepare('DELETE FROM fs_inode WHERE ino = ?');
|
|
359
|
+
await deleteInodeStmt.run(ino);
|
|
360
|
+
// Delete all data chunks
|
|
361
|
+
const deleteDataStmt = this.db.prepare('DELETE FROM fs_data WHERE ino = ?');
|
|
362
|
+
await deleteDataStmt.run(ino);
|
|
322
363
|
}
|
|
323
364
|
}
|
|
324
365
|
async stat(path) {
|
package/dist/index.d.ts
CHANGED
|
@@ -7,11 +7,17 @@ import { ToolCalls } from './toolcalls';
|
|
|
7
7
|
*/
|
|
8
8
|
export interface AgentFSOptions {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
* - If provided
|
|
12
|
-
* - If
|
|
10
|
+
* Unique identifier for the agent.
|
|
11
|
+
* - If provided without `path`: Creates storage at `.agentfs/{id}.db`
|
|
12
|
+
* - If provided with `path`: Uses the specified path
|
|
13
13
|
*/
|
|
14
14
|
id?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Explicit path to the database file.
|
|
17
|
+
* - If provided: Uses the specified path directly
|
|
18
|
+
* - Can be combined with `id`
|
|
19
|
+
*/
|
|
20
|
+
path?: string;
|
|
15
21
|
}
|
|
16
22
|
export declare class AgentFS {
|
|
17
23
|
private db;
|
|
@@ -24,19 +30,21 @@ export declare class AgentFS {
|
|
|
24
30
|
private constructor();
|
|
25
31
|
/**
|
|
26
32
|
* Open an agent filesystem
|
|
27
|
-
* @param options Configuration options (
|
|
33
|
+
* @param options Configuration options (id and/or path required)
|
|
28
34
|
* @returns Fully initialized AgentFS instance
|
|
29
35
|
* @example
|
|
30
36
|
* ```typescript
|
|
31
|
-
* //
|
|
37
|
+
* // Using id (creates .agentfs/my-agent.db)
|
|
32
38
|
* const agent = await AgentFS.open({ id: 'my-agent' });
|
|
33
|
-
* // Creates: .agentfs/my-agent.db
|
|
34
39
|
*
|
|
35
|
-
* //
|
|
36
|
-
* const agent = await AgentFS.open();
|
|
40
|
+
* // Using id with custom path
|
|
41
|
+
* const agent = await AgentFS.open({ id: 'my-agent', path: './data/mydb.db' });
|
|
42
|
+
*
|
|
43
|
+
* // Using path only
|
|
44
|
+
* const agent = await AgentFS.open({ path: './data/mydb.db' });
|
|
37
45
|
* ```
|
|
38
46
|
*/
|
|
39
|
-
static open(options
|
|
47
|
+
static open(options: AgentFSOptions): Promise<AgentFS>;
|
|
40
48
|
/**
|
|
41
49
|
* Get the underlying Database instance
|
|
42
50
|
*/
|
package/dist/index.js
CHANGED
|
@@ -18,38 +18,37 @@ class AgentFS {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Open an agent filesystem
|
|
21
|
-
* @param options Configuration options (
|
|
21
|
+
* @param options Configuration options (id and/or path required)
|
|
22
22
|
* @returns Fully initialized AgentFS instance
|
|
23
23
|
* @example
|
|
24
24
|
* ```typescript
|
|
25
|
-
* //
|
|
25
|
+
* // Using id (creates .agentfs/my-agent.db)
|
|
26
26
|
* const agent = await AgentFS.open({ id: 'my-agent' });
|
|
27
|
-
* // Creates: .agentfs/my-agent.db
|
|
28
27
|
*
|
|
29
|
-
* //
|
|
30
|
-
* const agent = await AgentFS.open();
|
|
28
|
+
* // Using id with custom path
|
|
29
|
+
* const agent = await AgentFS.open({ id: 'my-agent', path: './data/mydb.db' });
|
|
30
|
+
*
|
|
31
|
+
* // Using path only
|
|
32
|
+
* const agent = await AgentFS.open({ path: './data/mydb.db' });
|
|
31
33
|
* ```
|
|
32
34
|
*/
|
|
33
35
|
static async open(options) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
const { id, path } = options;
|
|
37
|
+
// Require at least id or path
|
|
38
|
+
if (!id && !path) {
|
|
39
|
+
throw new Error("AgentFS.open() requires at least 'id' or 'path'.");
|
|
40
|
+
}
|
|
41
|
+
// Validate agent ID if provided
|
|
42
|
+
if (id && !/^[a-zA-Z0-9_-]+$/.test(id)) {
|
|
43
|
+
throw new Error('Agent ID must contain only alphanumeric characters, hyphens, and underscores');
|
|
39
44
|
}
|
|
40
|
-
|
|
41
|
-
// Determine database path based on id
|
|
45
|
+
// Determine database path: explicit path takes precedence, otherwise use id-based path
|
|
42
46
|
let dbPath;
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
dbPath = ':memory:';
|
|
47
|
+
if (path) {
|
|
48
|
+
dbPath = path;
|
|
46
49
|
}
|
|
47
50
|
else {
|
|
48
|
-
//
|
|
49
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(id)) {
|
|
50
|
-
throw new Error('Agent ID must contain only alphanumeric characters, hyphens, and underscores');
|
|
51
|
-
}
|
|
52
|
-
// Ensure .agentfs directory exists
|
|
51
|
+
// id is guaranteed to be defined here (we checked !id && !path above)
|
|
53
52
|
const dir = '.agentfs';
|
|
54
53
|
if (!(0, fs_1.existsSync)(dir)) {
|
|
55
54
|
(0, fs_1.mkdirSync)(dir, { recursive: true });
|