@nkmc/agent-fs 0.1.0

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 (42) hide show
  1. package/dist/chunk-7LIZT7L3.js +966 -0
  2. package/dist/index.cjs +1278 -0
  3. package/dist/index.d.cts +96 -0
  4. package/dist/index.d.ts +96 -0
  5. package/dist/index.js +419 -0
  6. package/dist/rpc-D1IHpjF_.d.cts +330 -0
  7. package/dist/rpc-D1IHpjF_.d.ts +330 -0
  8. package/dist/testing.cjs +842 -0
  9. package/dist/testing.d.cts +29 -0
  10. package/dist/testing.d.ts +29 -0
  11. package/dist/testing.js +10 -0
  12. package/package.json +25 -0
  13. package/src/agent-fs.ts +151 -0
  14. package/src/backends/http.ts +835 -0
  15. package/src/backends/memory.ts +183 -0
  16. package/src/backends/rpc.ts +456 -0
  17. package/src/index.ts +36 -0
  18. package/src/mount.ts +84 -0
  19. package/src/parser.ts +162 -0
  20. package/src/server.ts +158 -0
  21. package/src/testing.ts +3 -0
  22. package/src/types.ts +52 -0
  23. package/test/agent-fs.test.ts +325 -0
  24. package/test/http-204.test.ts +102 -0
  25. package/test/http-auth-prefix.test.ts +79 -0
  26. package/test/http-cloudflare.test.ts +533 -0
  27. package/test/http-form-encoding.test.ts +119 -0
  28. package/test/http-github.test.ts +580 -0
  29. package/test/http-listkey.test.ts +128 -0
  30. package/test/http-oauth2.test.ts +174 -0
  31. package/test/http-pagination.test.ts +200 -0
  32. package/test/http-param-styles.test.ts +98 -0
  33. package/test/http-passthrough.test.ts +282 -0
  34. package/test/http-retry.test.ts +132 -0
  35. package/test/http.test.ts +360 -0
  36. package/test/memory.test.ts +120 -0
  37. package/test/mount.test.ts +94 -0
  38. package/test/parser.test.ts +100 -0
  39. package/test/rpc-crud.test.ts +627 -0
  40. package/test/rpc-evm.test.ts +390 -0
  41. package/tsconfig.json +8 -0
  42. package/tsup.config.ts +8 -0
@@ -0,0 +1,183 @@
1
+ import type { FsBackend } from "../types.js";
2
+
3
+ interface Record {
4
+ id: string;
5
+ [key: string]: unknown;
6
+ }
7
+
8
+ /**
9
+ * In-memory FsBackend for testing and prototyping.
10
+ * Stores data as a map of collections, each collection is a map of id → record.
11
+ */
12
+ export class MemoryBackend implements FsBackend {
13
+ private collections = new Map<string, Map<string, Record>>();
14
+ private nextId = 1;
15
+
16
+ /** Pre-seed a collection with data */
17
+ seed(collection: string, records: Record[]): void {
18
+ const col = this.getOrCreateCollection(collection);
19
+ for (const record of records) {
20
+ col.set(record.id, { ...record });
21
+ }
22
+ }
23
+
24
+ async list(path: string): Promise<string[]> {
25
+ const { collection, id } = this.parsePath(path);
26
+
27
+ // List all collections
28
+ if (!collection) {
29
+ return Array.from(this.collections.keys()).map((c) => c + "/");
30
+ }
31
+
32
+ // List records in a collection
33
+ if (!id) {
34
+ const col = this.collections.get(collection);
35
+ if (!col) return [];
36
+ return Array.from(col.keys()).map((k) => `${k}.json`);
37
+ }
38
+
39
+ // Can't list a single record
40
+ return [];
41
+ }
42
+
43
+ async read(path: string): Promise<unknown> {
44
+ const { collection, id } = this.parsePath(path);
45
+
46
+ if (!collection) {
47
+ throw new NotFoundError(path);
48
+ }
49
+
50
+ const col = this.collections.get(collection);
51
+ if (!col) {
52
+ throw new NotFoundError(path);
53
+ }
54
+
55
+ // Special paths
56
+ if (id === "_schema") {
57
+ return this.getSchema(collection);
58
+ }
59
+ if (id === "_count") {
60
+ return { count: col.size };
61
+ }
62
+
63
+ if (!id) {
64
+ // Reading a collection returns all records
65
+ return Array.from(col.values());
66
+ }
67
+
68
+ const record = col.get(id);
69
+ if (!record) {
70
+ throw new NotFoundError(path);
71
+ }
72
+ return record;
73
+ }
74
+
75
+ async write(path: string, data: unknown): Promise<{ id: string }> {
76
+ const { collection, id } = this.parsePath(path);
77
+
78
+ if (!collection) {
79
+ throw new Error("Cannot write to root");
80
+ }
81
+
82
+ const col = this.getOrCreateCollection(collection);
83
+ const record = data as Record;
84
+
85
+ if (id) {
86
+ // Update existing record
87
+ const existing = col.get(id);
88
+ if (!existing) {
89
+ throw new NotFoundError(path);
90
+ }
91
+ const updated = { ...existing, ...record, id };
92
+ col.set(id, updated);
93
+ return { id };
94
+ }
95
+
96
+ // Create new record
97
+ const newId = record.id ?? String(this.nextId++);
98
+ const newRecord = { ...record, id: newId };
99
+ col.set(newId, newRecord);
100
+ return { id: newId };
101
+ }
102
+
103
+ async remove(path: string): Promise<void> {
104
+ const { collection, id } = this.parsePath(path);
105
+
106
+ if (!collection || !id) {
107
+ throw new Error("Cannot remove a collection, specify a record path");
108
+ }
109
+
110
+ const col = this.collections.get(collection);
111
+ if (!col || !col.has(id)) {
112
+ throw new NotFoundError(path);
113
+ }
114
+ col.delete(id);
115
+ }
116
+
117
+ async search(path: string, pattern: string): Promise<unknown[]> {
118
+ const { collection } = this.parsePath(path);
119
+
120
+ if (!collection) {
121
+ throw new Error("grep requires a collection path");
122
+ }
123
+
124
+ const col = this.collections.get(collection);
125
+ if (!col) return [];
126
+
127
+ const results: unknown[] = [];
128
+ for (const record of col.values()) {
129
+ const json = JSON.stringify(record);
130
+ if (json.includes(pattern)) {
131
+ results.push(record);
132
+ }
133
+ }
134
+ return results;
135
+ }
136
+
137
+ private parsePath(path: string): { collection?: string; id?: string } {
138
+ // Remove leading slash, trailing slash, and .json extension
139
+ const cleaned = path.replace(/^\/+/, "").replace(/\/+$/, "");
140
+ if (!cleaned) return {};
141
+
142
+ const parts = cleaned.split("/");
143
+ const collection = parts[0];
144
+ let id = parts[1];
145
+
146
+ // Strip .json extension from id
147
+ if (id?.endsWith(".json")) {
148
+ id = id.slice(0, -5);
149
+ }
150
+
151
+ return { collection, id };
152
+ }
153
+
154
+ private getOrCreateCollection(name: string): Map<string, Record> {
155
+ let col = this.collections.get(name);
156
+ if (!col) {
157
+ col = new Map();
158
+ this.collections.set(name, col);
159
+ }
160
+ return col;
161
+ }
162
+
163
+ private getSchema(collection: string): object {
164
+ const col = this.collections.get(collection);
165
+ if (!col || col.size === 0) {
166
+ return { collection, fields: [] };
167
+ }
168
+ // Infer schema from first record
169
+ const first = col.values().next().value!;
170
+ const fields = Object.entries(first).map(([name, value]) => ({
171
+ name,
172
+ type: typeof value,
173
+ }));
174
+ return { collection, fields };
175
+ }
176
+ }
177
+
178
+ export class NotFoundError extends Error {
179
+ constructor(path: string) {
180
+ super(`Not found: ${path}`);
181
+ this.name = "NotFoundError";
182
+ }
183
+ }
@@ -0,0 +1,456 @@
1
+ import type { FsBackend } from "../types.js";
2
+ import { NotFoundError } from "./memory.js";
3
+
4
+ // --- Types ---
5
+
6
+ /** Transport interface for RPC calls */
7
+ export interface RpcTransport {
8
+ call(method: string, params: unknown[]): Promise<unknown>;
9
+ batch?(
10
+ calls: Array<{ method: string; params: unknown[] }>,
11
+ ): Promise<unknown[]>;
12
+ }
13
+
14
+ /** Configuration for JSON-RPC 2.0 transport */
15
+ export interface JsonRpcTransportConfig {
16
+ url: string;
17
+ headers?: Record<string, string>;
18
+ fetch?: typeof globalThis.fetch;
19
+ /** Retry configuration for transient errors (429, 5xx, -32603, -32000~-32099) */
20
+ retry?: {
21
+ /** Max retry attempts (default 3) */
22
+ maxRetries?: number;
23
+ /** Base delay in ms for exponential backoff (default 1000) */
24
+ baseDelayMs?: number;
25
+ };
26
+ }
27
+
28
+ /** JSON-RPC error with code and optional data */
29
+ export class RpcError extends Error {
30
+ code: number;
31
+ data?: unknown;
32
+
33
+ constructor(code: number, message: string, data?: unknown) {
34
+ super(message);
35
+ this.name = "RpcError";
36
+ this.code = code;
37
+ this.data = data;
38
+ }
39
+ }
40
+
41
+ /** Context passed to RpcMethod.params callback */
42
+ export interface RpcCallContext {
43
+ id?: string;
44
+ data?: unknown;
45
+ pattern?: string;
46
+ }
47
+
48
+ /** Maps a filesystem operation to an RPC call */
49
+ export interface RpcMethod {
50
+ method: string;
51
+ params: (ctx: RpcCallContext) => unknown[];
52
+ }
53
+
54
+ /** A resource exposed as a directory in the filesystem */
55
+ export interface RpcResource {
56
+ name: string;
57
+ idField?: string;
58
+
59
+ methods: {
60
+ list?: RpcMethod;
61
+ read?: RpcMethod;
62
+ write?: RpcMethod;
63
+ create?: RpcMethod;
64
+ remove?: RpcMethod;
65
+ search?: RpcMethod;
66
+ };
67
+
68
+ transform?: {
69
+ read?: (data: unknown) => unknown;
70
+ write?: (data: unknown) => unknown;
71
+ remove?: (id: string) => unknown;
72
+ list?: (item: unknown) => string | string[];
73
+ };
74
+ }
75
+
76
+ /** Configuration for RpcBackend */
77
+ export interface RpcBackendConfig {
78
+ transport: RpcTransport;
79
+ resources: RpcResource[];
80
+ }
81
+
82
+ // --- Internal helpers ---
83
+
84
+ function sleep(ms: number): Promise<void> {
85
+ return new Promise((resolve) => setTimeout(resolve, ms));
86
+ }
87
+
88
+ /**
89
+ * Safely parse JSON from a Response.
90
+ * Unlike HTTP safeJson (which returns null for empty/invalid), this throws RpcError
91
+ * because JSON-RPC protocol requires responses to be valid JSON.
92
+ */
93
+ async function safeRpcJson(resp: Response): Promise<any> {
94
+ const text = await resp.text();
95
+ if (!text || !text.trim()) {
96
+ throw new RpcError(-32700, "Parse error: empty response body");
97
+ }
98
+ try {
99
+ return JSON.parse(text);
100
+ } catch {
101
+ throw new RpcError(-32700, `Parse error: invalid JSON response`);
102
+ }
103
+ }
104
+
105
+ /** Deterministic RPC errors that should NOT be retried */
106
+ const NON_RETRYABLE_RPC_CODES = new Set([-32700, -32600, -32601, -32602]);
107
+
108
+ /** Check if an RPC error code is retryable (-32603 internal, -32000~-32099 server) */
109
+ function isRetryableRpcError(code: number): boolean {
110
+ if (NON_RETRYABLE_RPC_CODES.has(code)) return false;
111
+ // -32603 (internal error) or server errors (-32000 to -32099)
112
+ return code === -32603 || (code >= -32099 && code <= -32000);
113
+ }
114
+
115
+ /** Check if an HTTP status code is retryable (429, 5xx) */
116
+ function isRetryableHttpStatus(status: number): boolean {
117
+ return status === 429 || status >= 500;
118
+ }
119
+
120
+ // --- JSON-RPC 2.0 Transport ---
121
+
122
+ export class JsonRpcTransport implements RpcTransport {
123
+ private url: string;
124
+ private headers: Record<string, string>;
125
+ private _fetch: typeof globalThis.fetch;
126
+ private nextId = 1;
127
+ private retryConfig: { maxRetries: number; baseDelayMs: number };
128
+
129
+ constructor(config: JsonRpcTransportConfig) {
130
+ this.url = config.url;
131
+ this.headers = config.headers ?? {};
132
+ this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
133
+ this.retryConfig = {
134
+ maxRetries: config.retry?.maxRetries ?? 3,
135
+ baseDelayMs: config.retry?.baseDelayMs ?? 1000,
136
+ };
137
+ }
138
+
139
+ async call(method: string, params: unknown[]): Promise<unknown> {
140
+ const id = this.nextId++;
141
+ const body = JSON.stringify({ jsonrpc: "2.0", id, method, params });
142
+ const headers = { "Content-Type": "application/json", ...this.headers };
143
+
144
+ let lastError: Error | undefined;
145
+
146
+ for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
147
+ let resp: Response;
148
+ try {
149
+ resp = await this._fetch(this.url, { method: "POST", headers, body });
150
+ } catch (err) {
151
+ lastError = err instanceof Error ? err : new Error(String(err));
152
+ if (attempt < this.retryConfig.maxRetries) {
153
+ await this.backoff(attempt);
154
+ continue;
155
+ }
156
+ throw lastError;
157
+ }
158
+
159
+ // HTTP-level retry (429, 5xx)
160
+ if (isRetryableHttpStatus(resp.status)) {
161
+ lastError = new RpcError(-32000, `HTTP ${resp.status}`);
162
+ if (attempt < this.retryConfig.maxRetries) {
163
+ await this.backoff(attempt, resp);
164
+ continue;
165
+ }
166
+ throw lastError;
167
+ }
168
+
169
+ // Parse JSON safely
170
+ const result = await safeRpcJson(resp);
171
+
172
+ // RPC-level error handling
173
+ if (result.error) {
174
+ const { code, message, data } = result.error;
175
+ if (isRetryableRpcError(code) && attempt < this.retryConfig.maxRetries) {
176
+ lastError = new RpcError(code, message, data);
177
+ await this.backoff(attempt);
178
+ continue;
179
+ }
180
+ throw new RpcError(code, message, data);
181
+ }
182
+
183
+ return result.result;
184
+ }
185
+
186
+ throw lastError ?? new RpcError(-32000, "Max retries exhausted");
187
+ }
188
+
189
+ async batch(
190
+ calls: Array<{ method: string; params: unknown[] }>,
191
+ ): Promise<unknown[]> {
192
+ const requests = calls.map((c) => ({
193
+ jsonrpc: "2.0",
194
+ id: this.nextId++,
195
+ method: c.method,
196
+ params: c.params,
197
+ }));
198
+ const body = JSON.stringify(requests);
199
+ const headers = { "Content-Type": "application/json", ...this.headers };
200
+
201
+ let lastError: Error | undefined;
202
+
203
+ for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
204
+ let resp: Response;
205
+ try {
206
+ resp = await this._fetch(this.url, { method: "POST", headers, body });
207
+ } catch (err) {
208
+ lastError = err instanceof Error ? err : new Error(String(err));
209
+ if (attempt < this.retryConfig.maxRetries) {
210
+ await this.backoff(attempt);
211
+ continue;
212
+ }
213
+ throw lastError;
214
+ }
215
+
216
+ // HTTP-level retry (429, 5xx)
217
+ if (isRetryableHttpStatus(resp.status)) {
218
+ lastError = new RpcError(-32000, `HTTP ${resp.status}`);
219
+ if (attempt < this.retryConfig.maxRetries) {
220
+ await this.backoff(attempt, resp);
221
+ continue;
222
+ }
223
+ throw lastError;
224
+ }
225
+
226
+ // Parse JSON safely
227
+ const results = await safeRpcJson(resp);
228
+
229
+ // Check for retryable RPC errors in batch results
230
+ const sorted = (results as Array<{ id: number; result?: unknown; error?: { code: number; message: string; data?: unknown } }>)
231
+ .sort((a, b) => a.id - b.id);
232
+
233
+ const hasRetryableError = sorted.some(
234
+ (r) => r.error && isRetryableRpcError(r.error.code),
235
+ );
236
+
237
+ if (hasRetryableError && attempt < this.retryConfig.maxRetries) {
238
+ const firstErr = sorted.find((r) => r.error)!.error!;
239
+ lastError = new RpcError(firstErr.code, firstErr.message, firstErr.data);
240
+ await this.backoff(attempt);
241
+ continue;
242
+ }
243
+
244
+ return sorted.map((r) => {
245
+ if (r.error) {
246
+ throw new RpcError(r.error.code, r.error.message, r.error.data);
247
+ }
248
+ return r.result;
249
+ });
250
+ }
251
+
252
+ throw lastError ?? new RpcError(-32000, "Max retries exhausted");
253
+ }
254
+
255
+ /** Exponential backoff with jitter + Retry-After header support */
256
+ private async backoff(attempt: number, resp?: Response): Promise<void> {
257
+ let delayMs: number;
258
+ const retryAfter = resp?.headers.get("retry-after");
259
+ if (retryAfter) {
260
+ const seconds = Number(retryAfter);
261
+ delayMs = isNaN(seconds) ? this.retryConfig.baseDelayMs : seconds * 1000;
262
+ } else {
263
+ delayMs = this.retryConfig.baseDelayMs * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5);
264
+ }
265
+ await sleep(delayMs);
266
+ }
267
+ }
268
+
269
+ // --- RpcBackend ---
270
+
271
+ type ParsedPath =
272
+ | { type: "root" }
273
+ | { type: "resource-list"; resource: RpcResource }
274
+ | { type: "resource-item"; resource: RpcResource; id: string };
275
+
276
+ export class RpcBackend implements FsBackend {
277
+ private transport: RpcTransport;
278
+ private resources: RpcResource[];
279
+
280
+ constructor(config: RpcBackendConfig) {
281
+ this.transport = config.transport;
282
+ this.resources = config.resources;
283
+ }
284
+
285
+ async list(path: string): Promise<string[]> {
286
+ const parsed = this.parsePath(path);
287
+
288
+ if (parsed.type === "root") {
289
+ return this.resources
290
+ .filter((r) => r.methods.list || r.methods.read)
291
+ .map((r) => r.name + "/");
292
+ }
293
+
294
+ if (parsed.type === "resource-list") {
295
+ const { resource } = parsed;
296
+ if (!resource.methods.list) return [];
297
+
298
+ const { method, params } = resource.methods.list;
299
+ const result = await this.transport.call(method, params({}));
300
+
301
+ if (resource.transform?.list) {
302
+ const transformed = resource.transform.list(result);
303
+ return Array.isArray(transformed) ? transformed : [transformed];
304
+ }
305
+
306
+ if (Array.isArray(result)) {
307
+ const idField = resource.idField ?? "id";
308
+ return result.map(
309
+ (item: unknown) =>
310
+ String((item as Record<string, unknown>)[idField]) + ".json",
311
+ );
312
+ }
313
+
314
+ return [];
315
+ }
316
+
317
+ return [];
318
+ }
319
+
320
+ async read(path: string): Promise<unknown> {
321
+ const parsed = this.parsePath(path);
322
+
323
+ if (parsed.type === "resource-item") {
324
+ const { resource, id } = parsed;
325
+
326
+ if (!resource.methods.read) {
327
+ throw new NotFoundError(path);
328
+ }
329
+
330
+ const { method, params } = resource.methods.read;
331
+ const result = await this.transport.call(method, params({ id }));
332
+
333
+ if (result === null || result === undefined) {
334
+ throw new NotFoundError(path);
335
+ }
336
+
337
+ return resource.transform?.read ? resource.transform.read(result) : result;
338
+ }
339
+
340
+ if (parsed.type === "resource-list") {
341
+ const { resource } = parsed;
342
+ if (!resource.methods.list) {
343
+ throw new NotFoundError(path);
344
+ }
345
+
346
+ const { method, params } = resource.methods.list;
347
+ return this.transport.call(method, params({}));
348
+ }
349
+
350
+ throw new NotFoundError(path);
351
+ }
352
+
353
+ async write(path: string, data: unknown): Promise<{ id: string }> {
354
+ const parsed = this.parsePath(path);
355
+
356
+ if (parsed.type === "resource-item") {
357
+ const { resource, id } = parsed;
358
+ const rpcMethod = resource.methods.write ?? resource.methods.create;
359
+ if (!rpcMethod) throw new Error(`Cannot write to path: ${path}`);
360
+
361
+ const writeData = resource.transform?.write
362
+ ? resource.transform.write(data)
363
+ : data;
364
+ const { method, params } = rpcMethod;
365
+ const result = await this.transport.call(method, params({ id, data: writeData }));
366
+ return { id: String(result ?? id) };
367
+ }
368
+
369
+ if (parsed.type === "resource-list") {
370
+ const { resource } = parsed;
371
+ const rpcMethod = resource.methods.create ?? resource.methods.write;
372
+ if (!rpcMethod) throw new Error(`Cannot write to path: ${path}`);
373
+
374
+ const writeData = resource.transform?.write
375
+ ? resource.transform.write(data)
376
+ : data;
377
+ const { method, params } = rpcMethod;
378
+ const result = await this.transport.call(method, params({ data: writeData }));
379
+ return { id: String(result ?? "unknown") };
380
+ }
381
+
382
+ throw new Error(`Cannot write to path: ${path}`);
383
+ }
384
+
385
+ async remove(path: string): Promise<void> {
386
+ const parsed = this.parsePath(path);
387
+
388
+ if (parsed.type === "resource-item") {
389
+ const { resource, id } = parsed;
390
+ if (!resource.methods.remove) {
391
+ throw new Error(`Cannot remove path: ${path}`);
392
+ }
393
+
394
+ const { method, params } = resource.methods.remove;
395
+ await this.transport.call(method, params({ id }));
396
+ return;
397
+ }
398
+
399
+ throw new Error(`Cannot remove path: ${path}`);
400
+ }
401
+
402
+ async search(path: string, pattern: string): Promise<unknown[]> {
403
+ const parsed = this.parsePath(path);
404
+
405
+ if (parsed.type === "resource-list" || parsed.type === "resource-item") {
406
+ const { resource } = parsed;
407
+
408
+ // Try dedicated search method
409
+ if (resource.methods.search) {
410
+ const { method, params } = resource.methods.search;
411
+ const result = await this.transport.call(method, params({ pattern }));
412
+ if (Array.isArray(result)) return result;
413
+ return [result];
414
+ }
415
+
416
+ // Fallback: list + client-side filter
417
+ if (resource.methods.list) {
418
+ const { method, params } = resource.methods.list;
419
+ const result = await this.transport.call(method, params({}));
420
+ if (Array.isArray(result)) {
421
+ return result.filter((item: unknown) =>
422
+ JSON.stringify(item).includes(pattern),
423
+ );
424
+ }
425
+ if (JSON.stringify(result).includes(pattern)) {
426
+ return [result];
427
+ }
428
+ }
429
+ }
430
+
431
+ return [];
432
+ }
433
+
434
+ // --- Internal ---
435
+
436
+ private parsePath(path: string): ParsedPath {
437
+ const cleaned = path.replace(/^\/+/, "").replace(/\/+$/, "");
438
+
439
+ if (!cleaned) return { type: "root" };
440
+
441
+ const parts = cleaned.split("/");
442
+ const resourceName = parts[0];
443
+ const resource = this.resources.find((r) => r.name === resourceName);
444
+ if (!resource) throw new NotFoundError(`Invalid path: ${path}`);
445
+
446
+ if (parts.length === 1) {
447
+ return { type: "resource-list", resource };
448
+ }
449
+
450
+ // All remaining segments form the ID (support composite IDs)
451
+ let id = parts.slice(1).join("/");
452
+ if (id.endsWith(".json")) id = id.slice(0, -5);
453
+
454
+ return { type: "resource-item", resource, id };
455
+ }
456
+ }
package/src/index.ts ADDED
@@ -0,0 +1,36 @@
1
+ export const VERSION = "0.1.0";
2
+
3
+ export type {
4
+ FsOp,
5
+ FsCommand,
6
+ FsResult,
7
+ FsError,
8
+ FsBackend,
9
+ Mount,
10
+ AccessRole,
11
+ AgentContext,
12
+ } from "./types.js";
13
+
14
+ export { parseCommand } from "./parser.js";
15
+ export { MountResolver, type ResolvedMount } from "./mount.js";
16
+ export { AgentFs, type AgentFsOptions } from "./agent-fs.js";
17
+ export { createAgentFsServer, type ServerOptions } from "./server.js";
18
+ export {
19
+ HttpBackend,
20
+ type HttpBackendConfig,
21
+ type HttpAuth,
22
+ type HttpResource,
23
+ type HttpEndpoint,
24
+ type PaginationConfig,
25
+ } from "./backends/http.js";
26
+ export {
27
+ RpcBackend,
28
+ JsonRpcTransport,
29
+ RpcError,
30
+ type RpcBackendConfig,
31
+ type RpcTransport,
32
+ type RpcResource,
33
+ type RpcMethod,
34
+ type RpcCallContext,
35
+ type JsonRpcTransportConfig,
36
+ } from "./backends/rpc.js";