@aigne/afs 1.4.0 → 1.11.0-beta

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.
Files changed (53) hide show
  1. package/README.md +95 -51
  2. package/dist/index.cjs +346 -0
  3. package/dist/index.d.cts +300 -0
  4. package/dist/index.d.cts.map +1 -0
  5. package/dist/index.d.mts +300 -0
  6. package/dist/index.d.mts.map +1 -0
  7. package/dist/index.mjs +343 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/dist/utils/camelize.cjs +34 -0
  10. package/dist/utils/camelize.d.cts +9 -0
  11. package/dist/utils/camelize.d.cts.map +1 -0
  12. package/dist/utils/camelize.d.mts +9 -0
  13. package/dist/utils/camelize.d.mts.map +1 -0
  14. package/dist/utils/camelize.mjs +33 -0
  15. package/dist/utils/camelize.mjs.map +1 -0
  16. package/dist/utils/type-utils.cjs +27 -0
  17. package/dist/utils/type-utils.d.cts +11 -0
  18. package/dist/utils/type-utils.d.cts.map +1 -0
  19. package/dist/utils/type-utils.d.mts +11 -0
  20. package/dist/utils/type-utils.d.mts.map +1 -0
  21. package/dist/utils/type-utils.mjs +23 -0
  22. package/dist/utils/type-utils.mjs.map +1 -0
  23. package/dist/utils/zod.cjs +35 -0
  24. package/dist/utils/zod.d.cts +23 -0
  25. package/dist/utils/zod.d.cts.map +1 -0
  26. package/dist/utils/zod.d.mts +23 -0
  27. package/dist/utils/zod.d.mts.map +1 -0
  28. package/dist/utils/zod.mjs +34 -0
  29. package/dist/utils/zod.mjs.map +1 -0
  30. package/package.json +44 -43
  31. package/CHANGELOG.md +0 -274
  32. package/lib/cjs/afs.d.ts +0 -42
  33. package/lib/cjs/afs.js +0 -350
  34. package/lib/cjs/error.d.ts +0 -13
  35. package/lib/cjs/error.js +0 -25
  36. package/lib/cjs/index.d.ts +0 -3
  37. package/lib/cjs/index.js +0 -19
  38. package/lib/cjs/package.json +0 -3
  39. package/lib/cjs/type.d.ts +0 -242
  40. package/lib/cjs/type.js +0 -25
  41. package/lib/dts/afs.d.ts +0 -42
  42. package/lib/dts/error.d.ts +0 -13
  43. package/lib/dts/index.d.ts +0 -3
  44. package/lib/dts/type.d.ts +0 -242
  45. package/lib/esm/afs.d.ts +0 -42
  46. package/lib/esm/afs.js +0 -346
  47. package/lib/esm/error.d.ts +0 -13
  48. package/lib/esm/error.js +0 -20
  49. package/lib/esm/index.d.ts +0 -3
  50. package/lib/esm/index.js +0 -3
  51. package/lib/esm/package.json +0 -3
  52. package/lib/esm/type.d.ts +0 -242
  53. package/lib/esm/type.js +0 -22
package/README.md CHANGED
@@ -34,28 +34,30 @@ import { AFSHistory } from "@aigne/afs-history";
34
34
  const afs = new AFS();
35
35
 
36
36
  // Mount history module (optional)
37
- afs.mount(new AFSHistory({
38
- storage: { url: "file:./memory.sqlite3" }
39
- }));
37
+ afs.mount(
38
+ new AFSHistory({
39
+ storage: { url: "file:./memory.sqlite3" },
40
+ }),
41
+ );
40
42
 
41
43
  // All modules are mounted under /modules
42
44
  // List modules
43
45
  const modules = await afs.listModules();
44
46
 
45
47
  // List entries in a module
46
- const { list } = await afs.list('/modules/history');
48
+ const { list } = await afs.list("/modules/history");
47
49
 
48
50
  // Read an entry
49
- const { result } = await afs.read('/modules/history/some-id');
51
+ const { result } = await afs.read("/modules/history/some-id");
50
52
 
51
53
  // Write an entry (if module supports write)
52
- await afs.write('/modules/history/notes', {
53
- content: 'My notes',
54
- summary: 'Personal notes'
54
+ await afs.write("/modules/history/notes", {
55
+ content: "My notes",
56
+ summary: "Personal notes",
55
57
  });
56
58
 
57
59
  // Search for content
58
- const { list: results } = await afs.search('/modules/history', 'search query');
60
+ const { list: results } = await afs.search("/modules/history", "search query");
59
61
  ```
60
62
 
61
63
  ## Core Concepts
@@ -66,16 +68,16 @@ All data in AFS is represented as `AFSEntry` objects:
66
68
 
67
69
  ```typescript
68
70
  interface AFSEntry {
69
- id: string; // Unique identifier
70
- path: string; // Full path in AFS
71
- content?: any; // File/data content
72
- summary?: string; // Optional summary
73
- metadata?: Record<string, any>; // Custom metadata
74
- createdAt?: Date; // Creation timestamp
75
- updatedAt?: Date; // Last update timestamp
76
- userId?: string; // Associated user
77
- sessionId?: string; // Associated session
78
- linkTo?: string; // Link to another entry
71
+ id: string; // Unique identifier
72
+ path: string; // Full path in AFS
73
+ content?: any; // File/data content
74
+ summary?: string; // Optional summary
75
+ metadata?: Record<string, any>; // Custom metadata
76
+ createdAt?: Date; // Creation timestamp
77
+ updatedAt?: Date; // Last update timestamp
78
+ userId?: string; // Associated user
79
+ sessionId?: string; // Associated session
80
+ linkTo?: string; // Link to another entry
79
81
  }
80
82
  ```
81
83
 
@@ -85,15 +87,29 @@ Modules are pluggable components that implement storage backends. All modules ar
85
87
 
86
88
  ```typescript
87
89
  interface AFSModule {
88
- name: string; // Module name (used as mount path)
89
- description?: string; // Description for AI agents
90
+ name: string; // Module name (used as mount path)
91
+ description?: string; // Description for AI agents
90
92
 
91
93
  // Operations (all optional)
92
- list?(path: string, options?: AFSListOptions): Promise<{ list: AFSEntry[]; message?: string }>;
94
+ list?(
95
+ path: string,
96
+ options?: AFSListOptions,
97
+ ): Promise<{ list: AFSEntry[]; message?: string }>;
93
98
  read?(path: string): Promise<{ result?: AFSEntry; message?: string }>;
94
- write?(path: string, entry: AFSWriteEntryPayload): Promise<{ result: AFSEntry; message?: string }>;
95
- search?(path: string, query: string, options?: AFSSearchOptions): Promise<{ list: AFSEntry[]; message?: string }>;
96
- exec?(path: string, args: Record<string, any>, options: { context: any }): Promise<{ result: Record<string, any> }>;
99
+ write?(
100
+ path: string,
101
+ entry: AFSWriteEntryPayload,
102
+ ): Promise<{ result: AFSEntry; message?: string }>;
103
+ search?(
104
+ path: string,
105
+ query: string,
106
+ options?: AFSSearchOptions,
107
+ ): Promise<{ list: AFSEntry[]; message?: string }>;
108
+ exec?(
109
+ path: string,
110
+ args: Record<string, any>,
111
+ options: { context: any },
112
+ ): Promise<{ result: Record<string, any> }>;
97
113
 
98
114
  // Lifecycle
99
115
  onMount?(afs: AFSRoot): void;
@@ -114,11 +130,13 @@ new AFS(options?: AFSOptions)
114
130
  ```
115
131
 
116
132
  Options:
133
+
117
134
  - `modules`: Optional array of modules to mount on initialization
118
135
 
119
136
  #### Methods
120
137
 
121
138
  ##### mount(module: AFSModule)
139
+
122
140
  ##### mount(path: string, module: AFSModule)
123
141
 
124
142
  Mount a module. The module will be accessible under `/modules/{module.name}` or `/modules/{path}`:
@@ -145,12 +163,13 @@ const modules = await afs.listModules();
145
163
  List entries in a directory:
146
164
 
147
165
  ```typescript
148
- const { list, message } = await afs.list('/modules/history', {
149
- maxDepth: 2
166
+ const { list, message } = await afs.list("/modules/history", {
167
+ maxDepth: 2,
150
168
  });
151
169
  ```
152
170
 
153
171
  Options:
172
+
154
173
  - `maxDepth`: Maximum recursion depth (default: 1)
155
174
 
156
175
  ##### read(path: string)
@@ -158,7 +177,7 @@ Options:
158
177
  Read a specific entry:
159
178
 
160
179
  ```typescript
161
- const { result, message } = await afs.read('/modules/history/uuid-123');
180
+ const { result, message } = await afs.read("/modules/history/uuid-123");
162
181
  ```
163
182
 
164
183
  ##### write(path: string, content: AFSWriteEntryPayload)
@@ -166,10 +185,10 @@ const { result, message } = await afs.read('/modules/history/uuid-123');
166
185
  Write or update an entry:
167
186
 
168
187
  ```typescript
169
- const { result, message } = await afs.write('/modules/my-module/file.txt', {
170
- content: 'Hello, world!',
171
- summary: 'Greeting file',
172
- metadata: { type: 'greeting' }
188
+ const { result, message } = await afs.write("/modules/my-module/file.txt", {
189
+ content: "Hello, world!",
190
+ summary: "Greeting file",
191
+ metadata: { type: "greeting" },
173
192
  });
174
193
  ```
175
194
 
@@ -178,7 +197,10 @@ const { result, message } = await afs.write('/modules/my-module/file.txt', {
178
197
  Search for content:
179
198
 
180
199
  ```typescript
181
- const { list, message } = await afs.search('/modules/history', 'authentication');
200
+ const { list, message } = await afs.search(
201
+ "/modules/history",
202
+ "authentication",
203
+ );
182
204
  ```
183
205
 
184
206
  ##### exec(path: string, args: Record<string, any>, options: { context: any })
@@ -186,7 +208,11 @@ const { list, message } = await afs.search('/modules/history', 'authentication')
186
208
  Execute a module-specific operation:
187
209
 
188
210
  ```typescript
189
- const { result } = await afs.exec('/modules/my-module/action', { param: 'value' }, { context });
211
+ const { result } = await afs.exec(
212
+ "/modules/my-module/action",
213
+ { param: "value" },
214
+ { context },
215
+ );
190
216
  ```
191
217
 
192
218
  ### Events
@@ -195,15 +221,16 @@ AFS uses an event system for module communication:
195
221
 
196
222
  ```typescript
197
223
  // Modules can listen to events
198
- afs.on('agentSucceed', ({ input, output }) => {
199
- console.log('Agent succeeded:', input, output);
224
+ afs.on("agentSucceed", ({ input, output }) => {
225
+ console.log("Agent succeeded:", input, output);
200
226
  });
201
227
 
202
228
  // Modules can emit custom events
203
- afs.emit('customEvent', { data: 'value' });
229
+ afs.emit("customEvent", { data: "value" });
204
230
  ```
205
231
 
206
232
  Common events from `AFSRootEvents`:
233
+
207
234
  - `agentSucceed`: Emitted when an agent successfully completes
208
235
  - `agentFail`: Emitted when an agent fails
209
236
 
@@ -214,6 +241,7 @@ Common events from `AFSRootEvents`:
214
241
  The history module tracks conversation history. It is available as a separate package: `@aigne/afs-history`.
215
242
 
216
243
  **Features:**
244
+
217
245
  - Listens to `agentSucceed` events and records agent interactions
218
246
  - Stores input/output pairs with UUID paths
219
247
  - Supports list and read operations
@@ -221,6 +249,7 @@ The history module tracks conversation history. It is available as a separate pa
221
249
  - Persistent SQLite storage
222
250
 
223
251
  **Installation:**
252
+
224
253
  ```bash
225
254
  npm install @aigne/afs-history
226
255
  ```
@@ -232,15 +261,18 @@ import { AFS } from "@aigne/afs";
232
261
  import { AFSHistory } from "@aigne/afs-history";
233
262
 
234
263
  const afs = new AFS();
235
- afs.mount(new AFSHistory({
236
- storage: { url: "file:./memory.sqlite3" }
237
- }));
264
+ afs.mount(
265
+ new AFSHistory({
266
+ storage: { url: "file:./memory.sqlite3" },
267
+ }),
268
+ );
238
269
 
239
270
  // History entries are accessible at /modules/history
240
- const { list } = await afs.list('/modules/history');
271
+ const { list } = await afs.list("/modules/history");
241
272
  ```
242
273
 
243
274
  **Configuration:**
275
+
244
276
  - `storage`: Storage configuration (e.g., `{ url: "file:./memory.sqlite3" }`) or a SharedAFSStorage instance
245
277
 
246
278
  **Note:** History is NOT automatically mounted. You must explicitly mount it if needed.
@@ -258,7 +290,10 @@ export class CustomModule implements AFSModule {
258
290
  readonly name = "custom-module";
259
291
  readonly description = "My custom storage";
260
292
 
261
- async list(path: string, options?: AFSListOptions): Promise<{ list: AFSEntry[]; message?: string }> {
293
+ async list(
294
+ path: string,
295
+ options?: AFSListOptions,
296
+ ): Promise<{ list: AFSEntry[]; message?: string }> {
262
297
  // path is the subpath within your module
263
298
  // Implement your list logic
264
299
  return { list: [] };
@@ -269,13 +304,20 @@ export class CustomModule implements AFSModule {
269
304
  return { result: undefined };
270
305
  }
271
306
 
272
- async write(path: string, content: AFSWriteEntryPayload): Promise<{ result: AFSEntry; message?: string }> {
307
+ async write(
308
+ path: string,
309
+ content: AFSWriteEntryPayload,
310
+ ): Promise<{ result: AFSEntry; message?: string }> {
273
311
  // Implement your write logic
274
- const entry: AFSEntry = { id: 'id', path, ...content };
312
+ const entry: AFSEntry = { id: "id", path, ...content };
275
313
  return { result: entry };
276
314
  }
277
315
 
278
- async search(path: string, query: string, options?: AFSSearchOptions): Promise<{ list: AFSEntry[]; message?: string }> {
316
+ async search(
317
+ path: string,
318
+ query: string,
319
+ options?: AFSSearchOptions,
320
+ ): Promise<{ list: AFSEntry[]; message?: string }> {
279
321
  // Implement your search logic
280
322
  return { list: [] };
281
323
  }
@@ -284,7 +326,7 @@ export class CustomModule implements AFSModule {
284
326
  console.log(`${this.name} mounted`);
285
327
 
286
328
  // Listen to events
287
- afs.on('agentSucceed', (data) => {
329
+ afs.on("agentSucceed", (data) => {
288
330
  // Handle event
289
331
  });
290
332
  }
@@ -323,18 +365,20 @@ import { AFS } from "@aigne/afs";
323
365
  import { AFSHistory } from "@aigne/afs-history";
324
366
 
325
367
  const afs = new AFS();
326
- afs.mount(new AFSHistory({
327
- storage: { url: "file:./memory.sqlite3" }
328
- }));
368
+ afs.mount(
369
+ new AFSHistory({
370
+ storage: { url: "file:./memory.sqlite3" },
371
+ }),
372
+ );
329
373
 
330
374
  const agent = AIAgent.from({
331
375
  name: "assistant",
332
- afs: afs
376
+ afs: afs,
333
377
  });
334
378
 
335
379
  const context = aigne.newContext();
336
380
  const result = await context.invoke(agent, {
337
- message: "What's in my history?"
381
+ message: "What's in my history?",
338
382
  });
339
383
  ```
340
384
 
package/dist/index.cjs ADDED
@@ -0,0 +1,346 @@
1
+ let _aigne_uuid = require("@aigne/uuid");
2
+ let strict_event_emitter = require("strict-event-emitter");
3
+ let ufo = require("ufo");
4
+ let zod = require("zod");
5
+
6
+ //#region src/error.ts
7
+ /**
8
+ * Base error class for all AFS errors.
9
+ */
10
+ var AFSError = class extends Error {
11
+ code;
12
+ constructor(message, code) {
13
+ super(message);
14
+ this.name = "AFSError";
15
+ this.code = code;
16
+ }
17
+ };
18
+ /**
19
+ * Error thrown when attempting write operations on a readonly AFS or module.
20
+ */
21
+ var AFSReadonlyError = class extends AFSError {
22
+ constructor(message) {
23
+ super(message, "AFS_READONLY");
24
+ this.name = "AFSReadonlyError";
25
+ }
26
+ };
27
+
28
+ //#endregion
29
+ //#region src/type.ts
30
+ /**
31
+ * Zod schema for access mode validation.
32
+ * Can be reused across modules that support access mode configuration.
33
+ */
34
+ const accessModeSchema = zod.z.enum(["readonly", "readwrite"]).describe("Access mode for this module").optional();
35
+ const afsEntrySchema = zod.z.object({
36
+ id: zod.z.string(),
37
+ createdAt: zod.z.date().optional(),
38
+ updatedAt: zod.z.date().optional(),
39
+ path: zod.z.string(),
40
+ userId: zod.z.string().nullable().optional(),
41
+ sessionId: zod.z.string().nullable().optional(),
42
+ summary: zod.z.string().nullable().optional(),
43
+ description: zod.z.string().nullable().optional(),
44
+ metadata: zod.z.record(zod.z.any()).nullable().optional(),
45
+ linkTo: zod.z.string().nullable().optional(),
46
+ content: zod.z.any().optional()
47
+ });
48
+
49
+ //#endregion
50
+ //#region src/afs.ts
51
+ const DEFAULT_MAX_DEPTH = 1;
52
+ const MODULES_ROOT_DIR = "/modules";
53
+ var AFS = class extends strict_event_emitter.Emitter {
54
+ name = "AFSRoot";
55
+ constructor(options = {}) {
56
+ super();
57
+ this.options = options;
58
+ for (const module of options?.modules ?? []) this.mount(module);
59
+ }
60
+ modules = /* @__PURE__ */ new Map();
61
+ /**
62
+ * Check if write operations are allowed for the given module.
63
+ * Throws AFSReadonlyError if not allowed.
64
+ */
65
+ checkWritePermission(module, operation, path) {
66
+ if (module.accessMode !== "readwrite") throw new AFSReadonlyError(`Module '${module.name}' is readonly, cannot perform ${operation} to ${path}`);
67
+ }
68
+ mount(module) {
69
+ if (module.name.includes("/")) throw new Error(`Invalid module name: ${module.name}. Module name must not contain '/'`);
70
+ const path = (0, ufo.joinURL)(MODULES_ROOT_DIR, module.name);
71
+ if (this.modules.has(path)) throw new Error(`Module already mounted at path: ${path}`);
72
+ this.modules.set(path, module);
73
+ module.onMount?.(this);
74
+ return this;
75
+ }
76
+ async listModules() {
77
+ return Array.from(this.modules.entries()).map(([path, module]) => ({
78
+ path,
79
+ name: module.name,
80
+ description: module.description,
81
+ module
82
+ }));
83
+ }
84
+ async list(path, options = {}) {
85
+ let preset;
86
+ if (options.preset) {
87
+ preset = this.options?.context?.list?.presets?.[options.preset];
88
+ if (!preset) throw new Error(`Preset not found: ${options.preset}`);
89
+ }
90
+ return await this.processWithPreset(path, void 0, preset, {
91
+ ...options,
92
+ defaultSelect: () => this._list(path, options)
93
+ });
94
+ }
95
+ async _list(path, options = {}) {
96
+ const results = [];
97
+ if (path === "/" && this.modules.size > 0) {
98
+ const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
99
+ results.push({
100
+ id: "modules",
101
+ path: MODULES_ROOT_DIR,
102
+ summary: "All mounted modules"
103
+ });
104
+ if (maxDepth === 1) return { data: results };
105
+ const childrenResult = await this._list(MODULES_ROOT_DIR, {
106
+ ...options,
107
+ maxDepth: maxDepth - 1
108
+ });
109
+ results.push(...childrenResult.data);
110
+ return { data: results };
111
+ }
112
+ const matches = this.findModules(path, options);
113
+ for (const matched of matches) {
114
+ if (matched.maxDepth === 0) {
115
+ const moduleEntry = {
116
+ id: matched.module.name,
117
+ path: matched.modulePath,
118
+ summary: matched.module.description
119
+ };
120
+ results.push(moduleEntry);
121
+ continue;
122
+ }
123
+ if (!matched.module.list) continue;
124
+ try {
125
+ const { data } = await matched.module.list(matched.subpath, {
126
+ ...options,
127
+ maxDepth: matched.maxDepth
128
+ });
129
+ const children = data.map((entry) => ({
130
+ ...entry,
131
+ path: (0, ufo.joinURL)(matched.modulePath, entry.path)
132
+ }));
133
+ results.push(...children);
134
+ } catch (error) {
135
+ throw new Error(`Error listing from module at ${matched.modulePath}: ${error.message}`);
136
+ }
137
+ }
138
+ return { data: results };
139
+ }
140
+ async read(path, _options) {
141
+ const modules = this.findModules(path, { exactMatch: true });
142
+ for (const { module, modulePath, subpath } of modules) {
143
+ const res = await module.read?.(subpath);
144
+ if (res?.data) return {
145
+ ...res,
146
+ data: {
147
+ ...res.data,
148
+ path: (0, ufo.joinURL)(modulePath, res.data.path)
149
+ }
150
+ };
151
+ }
152
+ return {
153
+ data: void 0,
154
+ message: "File not found"
155
+ };
156
+ }
157
+ async write(path, content, options) {
158
+ const module = this.findModules(path, { exactMatch: true })[0];
159
+ if (!module?.module.write) throw new Error(`No module found for path: ${path}`);
160
+ this.checkWritePermission(module.module, "write", path);
161
+ const res = await module.module.write(module.subpath, content, options);
162
+ return {
163
+ ...res,
164
+ data: {
165
+ ...res.data,
166
+ path: (0, ufo.joinURL)(module.modulePath, res.data.path)
167
+ }
168
+ };
169
+ }
170
+ async delete(path, options) {
171
+ const module = this.findModules(path, { exactMatch: true })[0];
172
+ if (!module?.module.delete) throw new Error(`No module found for path: ${path}`);
173
+ this.checkWritePermission(module.module, "delete", path);
174
+ return await module.module.delete(module.subpath, options);
175
+ }
176
+ async rename(oldPath, newPath, options) {
177
+ const oldModule = this.findModules(oldPath, { exactMatch: true })[0];
178
+ const newModule = this.findModules(newPath, { exactMatch: true })[0];
179
+ if (!oldModule || !newModule || oldModule.modulePath !== newModule.modulePath) throw new Error(`Cannot rename across different modules. Both paths must be in the same module.`);
180
+ if (!oldModule.module.rename) throw new Error(`Module does not support rename operation: ${oldModule.modulePath}`);
181
+ this.checkWritePermission(oldModule.module, "rename", oldPath);
182
+ return await oldModule.module.rename(oldModule.subpath, newModule.subpath, options);
183
+ }
184
+ async search(path, query, options = {}) {
185
+ let preset;
186
+ if (options.preset) {
187
+ preset = this.options?.context?.search?.presets?.[options.preset];
188
+ if (!preset) throw new Error(`Preset not found: ${options.preset}`);
189
+ }
190
+ return await this.processWithPreset(path, query, preset, {
191
+ ...options,
192
+ defaultSelect: () => this._search(path, query, options)
193
+ });
194
+ }
195
+ async processWithPreset(path, query, preset, options) {
196
+ const select = options.select || preset?.select;
197
+ const per = options.per || preset?.per;
198
+ const dedupe = options.dedupe || preset?.dedupe;
199
+ const format = options.format || preset?.format;
200
+ const entries = select ? (await this._select(path, query, select, options)).data : (await options.defaultSelect()).data;
201
+ const mapped = per ? await Promise.all(entries.map((data) => per.invoke({ data }, options).then((res) => res.data))) : entries;
202
+ const deduped = dedupe ? await dedupe.invoke({ data: mapped }, options).then((res) => res.data) : mapped;
203
+ let formatted = deduped;
204
+ if (format === "simple-list" || format === "tree") {
205
+ const valid = zod.z.array(afsEntrySchema).safeParse(deduped);
206
+ if (!valid.data) throw new Error("Tree format requires entries to be AFSEntry objects");
207
+ if (format === "tree") formatted = this.buildTreeView(valid.data);
208
+ else if (format === "simple-list") formatted = this.buildSimpleListView(valid.data);
209
+ } else if (typeof format === "object" && typeof format.invoke === "function") formatted = await format.invoke({ data: deduped }, options).then((res) => res.data);
210
+ return { data: formatted };
211
+ }
212
+ async _select(path, query, select, options) {
213
+ const { data } = await select.invoke({
214
+ path,
215
+ query
216
+ }, options);
217
+ return { data: (await Promise.all(data.map((p) => this.read(p).then((res) => res.data)))).filter((i) => !!i) };
218
+ }
219
+ async _search(path, query, options) {
220
+ const results = [];
221
+ const messages = [];
222
+ for (const { module, modulePath, subpath } of this.findModules(path)) {
223
+ if (!module.search) continue;
224
+ try {
225
+ const { data, message } = await module.search(subpath, query, options);
226
+ results.push(...data.map((entry) => ({
227
+ ...entry,
228
+ path: (0, ufo.joinURL)(modulePath, entry.path)
229
+ })));
230
+ if (message) messages.push(message);
231
+ } catch (error) {
232
+ throw new Error(`Error searching in module at ${modulePath}: ${error.message}`);
233
+ }
234
+ }
235
+ return {
236
+ data: results,
237
+ message: messages.join("; ")
238
+ };
239
+ }
240
+ findModules(path, options) {
241
+ const maxDepth = Math.max(options?.maxDepth ?? DEFAULT_MAX_DEPTH, 1);
242
+ const matched = [];
243
+ for (const [modulePath, module] of this.modules) {
244
+ const pathSegments = path.split("/").filter(Boolean);
245
+ const modulePathSegments = modulePath.split("/").filter(Boolean);
246
+ let newMaxDepth;
247
+ let subpath;
248
+ let remainedModulePath;
249
+ if (!options?.exactMatch && modulePath.startsWith(path)) {
250
+ newMaxDepth = Math.max(0, maxDepth - (modulePathSegments.length - pathSegments.length));
251
+ subpath = "/";
252
+ remainedModulePath = (0, ufo.joinURL)("/", ...modulePathSegments.slice(pathSegments.length).slice(0, maxDepth));
253
+ } else if (path.startsWith(modulePath)) {
254
+ newMaxDepth = maxDepth;
255
+ subpath = (0, ufo.joinURL)("/", ...pathSegments.slice(modulePathSegments.length));
256
+ remainedModulePath = "/";
257
+ } else continue;
258
+ if (newMaxDepth < 0) continue;
259
+ matched.push({
260
+ module,
261
+ modulePath,
262
+ maxDepth: newMaxDepth,
263
+ subpath,
264
+ remainedModulePath
265
+ });
266
+ }
267
+ return matched;
268
+ }
269
+ async exec(path, args, options) {
270
+ const module = this.findModules(path)[0];
271
+ if (!module?.module.exec) throw new Error(`No module found for path: ${path}`);
272
+ return await module.module.exec(module.subpath, args, options);
273
+ }
274
+ buildSimpleListView(entries) {
275
+ return entries.map((entry) => `${entry.path}${this.buildMetadataSuffix(entry)}`);
276
+ }
277
+ buildTreeView(entries) {
278
+ const tree = {};
279
+ const entryMap = /* @__PURE__ */ new Map();
280
+ for (const entry of entries) {
281
+ entryMap.set(entry.path, entry);
282
+ const parts = entry.path.split("/").filter(Boolean);
283
+ let current = tree;
284
+ for (const part of parts) {
285
+ if (!current[part]) current[part] = {};
286
+ current = current[part];
287
+ }
288
+ }
289
+ const renderTree = (node, prefix = "", currentPath = "") => {
290
+ let result = "";
291
+ const keys = Object.keys(node);
292
+ keys.forEach((key, index) => {
293
+ const isLast = index === keys.length - 1;
294
+ const fullPath = currentPath ? `${currentPath}/${key}` : `/${key}`;
295
+ const entry = entryMap.get(fullPath);
296
+ result += `${prefix}${isLast ? "└── " : "├── "}${key}${entry ? this.buildMetadataSuffix(entry) : ""}`;
297
+ result += `\n`;
298
+ result += renderTree(node[key], `${prefix}${isLast ? " " : "│ "}`, fullPath);
299
+ });
300
+ return result;
301
+ };
302
+ return renderTree(tree);
303
+ }
304
+ buildMetadataSuffix(entry) {
305
+ const metadataParts = [];
306
+ const childrenCount = entry?.metadata?.childrenCount;
307
+ if (typeof childrenCount === "number") metadataParts.push(`${childrenCount} items`);
308
+ if (entry?.metadata?.childrenTruncated) metadataParts.push("truncated");
309
+ if (entry?.metadata?.gitignored) metadataParts.push("gitignored");
310
+ if (entry?.metadata?.execute) metadataParts.push("executable");
311
+ return metadataParts.length > 0 ? ` [${metadataParts.join(", ")}]` : "";
312
+ }
313
+ physicalPath;
314
+ async initializePhysicalPath() {
315
+ this.physicalPath ??= (async () => {
316
+ const path = await import("node:path");
317
+ const os = await import("node:os");
318
+ const fs = await import("node:fs/promises");
319
+ const rootDir = path.join(os.tmpdir(), (0, _aigne_uuid.v7)());
320
+ await fs.mkdir(rootDir, { recursive: true });
321
+ for (const [modulePath, module] of this.modules) {
322
+ const physicalModulePath = path.join(rootDir, modulePath);
323
+ await fs.mkdir(path.dirname(physicalModulePath), { recursive: true });
324
+ await module.symlinkToPhysical?.(physicalModulePath);
325
+ }
326
+ return rootDir;
327
+ })();
328
+ return this.physicalPath;
329
+ }
330
+ async cleanupPhysicalPath() {
331
+ if (this.physicalPath) {
332
+ await (await import("node:fs/promises")).rm(await this.physicalPath, {
333
+ recursive: true,
334
+ force: true
335
+ });
336
+ this.physicalPath = void 0;
337
+ }
338
+ }
339
+ };
340
+
341
+ //#endregion
342
+ exports.AFS = AFS;
343
+ exports.AFSError = AFSError;
344
+ exports.AFSReadonlyError = AFSReadonlyError;
345
+ exports.accessModeSchema = accessModeSchema;
346
+ exports.afsEntrySchema = afsEntrySchema;