@aigne/afs-json 1.11.0-beta.8 → 1.12.0-beta.10
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 +32 -0
- package/dist/index.cjs +213 -34
- package/dist/index.d.cts +26 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +26 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +214 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -304,3 +304,35 @@ Search for values within the JSON/YAML structure.
|
|
|
304
304
|
- **Kubernetes Config**: Navigate and query YAML configuration files
|
|
305
305
|
- **CI/CD Pipelines**: Access and modify YAML pipeline configurations
|
|
306
306
|
|
|
307
|
+
|
|
308
|
+
## 选 provider 的判据:file-as-blob vs file-as-tree
|
|
309
|
+
|
|
310
|
+
- **file-as-blob**(用 `AFSFS`):内容是不透明字节流——README.md、二进制文件、用户上传文件等
|
|
311
|
+
- **file-as-tree**(用 `AFSJSON` / `AFSToml` / `AFSYaml`):内容是结构化数据,需要 sub-path 读写
|
|
312
|
+
单个字段。settings、config、provider 描述符等。
|
|
313
|
+
|
|
314
|
+
判断口诀:**写一个字段时另一个字段会变吗?** 会 = 选 blob;不会 = 选 tree。
|
|
315
|
+
Wrapper-format settings 永远是 tree。
|
|
316
|
+
|
|
317
|
+
## Structured settings MUST mount via AFSJSON sub-path overlay
|
|
318
|
+
|
|
319
|
+
任何 wrapper-format settings 文件(`{ label, description, value, ... }` 结构)**必须**通过 AFSJSON
|
|
320
|
+
overlay mount 在自身路径上,让 bind 指向内层 sub-path(例如 `/<file>.json/value`),而不是直接写整个
|
|
321
|
+
wrapper 文件。理由:
|
|
322
|
+
|
|
323
|
+
- 直接写 wrapper 文件会丢失 sibling 字段(label / description)。这是 G0 回归 bug。
|
|
324
|
+
- AFSJSON overlay 让每个 `{label, description, value}` 字段成为可读写的 primitive sub-path。
|
|
325
|
+
写 `/value` 只更新该字段,框架负责把整个 wrapper 重新序列化回磁盘——其他字段字节级保留。
|
|
326
|
+
- `buildFlatSubMounts` 已自动检测 wrapper 结构并挂 overlay;新写代码 **不要**绕过它直接写
|
|
327
|
+
wrapper 文件路径。
|
|
328
|
+
|
|
329
|
+
参考:`runtimes/node/src/program/structured-settings-overlay.ts`、回归 sentinel
|
|
330
|
+
`providers/runtime/ui/test/persistence-sentinel.test.ts`、约定全文
|
|
331
|
+
`providers/runtime/ui/docs/settings-persistence.md`。
|
|
332
|
+
|
|
333
|
+
## Nested mount 用 `allowOverlay: true`
|
|
334
|
+
|
|
335
|
+
在已经被一个 provider 覆盖的子路径下叠 overlay 时(典型场景:FS 已 mount `/instance` 后,要在
|
|
336
|
+
`/instance/.../foo.json` 上叠 AFSJSON),mount 调用必须显式传 `{ allowOverlay: true }`,否则
|
|
337
|
+
框架抛 `Mount conflict` 拒绝注册。验证要点:list 不重复、read 路由到 overlay、unmount
|
|
338
|
+
后回退到底层 provider。参考 `providers/core/json/test/overlay.test.ts`。
|
package/dist/index.cjs
CHANGED
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
2
|
const require_decorate = require('./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs');
|
|
3
|
-
let
|
|
4
|
-
let node_fs_promises = require("node:fs/promises");
|
|
5
|
-
let node_path = require("node:path");
|
|
3
|
+
let node_crypto = require("node:crypto");
|
|
6
4
|
let _aigne_afs = require("@aigne/afs");
|
|
7
5
|
let _aigne_afs_provider = require("@aigne/afs/provider");
|
|
8
6
|
let _aigne_afs_utils_zod = require("@aigne/afs/utils/zod");
|
|
7
|
+
let _aigne_afs_provider_utils = require("@aigne/afs-provider-utils");
|
|
8
|
+
let js_yaml = require("js-yaml");
|
|
9
9
|
let ufo = require("ufo");
|
|
10
|
-
let yaml = require("yaml");
|
|
11
10
|
let zod = require("zod");
|
|
12
11
|
|
|
13
12
|
//#region src/index.ts
|
|
14
13
|
const LIST_MAX_LIMIT = 1e3;
|
|
14
|
+
const JSON_AS_FORMATS = [
|
|
15
|
+
"content",
|
|
16
|
+
"compact",
|
|
17
|
+
"tree"
|
|
18
|
+
];
|
|
15
19
|
/** Hidden key for storing AFS metadata (mirrors FS provider's .afs directory) */
|
|
16
20
|
const AFS_KEY = ".afs";
|
|
17
21
|
/** Subkey for storing metadata (mirrors FS provider's meta.yaml file) */
|
|
18
22
|
const META_KEY = "meta";
|
|
19
23
|
/** Subkey for storing child node metadata (mirrors FS provider's .nodes directory) */
|
|
20
24
|
const NODES_KEY = ".nodes";
|
|
25
|
+
function assertLocalPathWithinCwd(resolvedPath, cwd, rawPath) {
|
|
26
|
+
const { path } = (0, _aigne_afs.getPlatform)();
|
|
27
|
+
const root = path.resolve(cwd);
|
|
28
|
+
const candidate = path.resolve(resolvedPath);
|
|
29
|
+
const rootPrefix = root.endsWith("/") || root.endsWith("\\") ? root : `${root}/`;
|
|
30
|
+
const windowsRootPrefix = root.endsWith("\\") ? root : `${root}\\`;
|
|
31
|
+
if (candidate !== root && !candidate.startsWith(rootPrefix) && !candidate.startsWith(windowsRootPrefix)) throw new Error(`Local path escapes configured base directory: ${rawPath}`);
|
|
32
|
+
return candidate;
|
|
33
|
+
}
|
|
21
34
|
const afsJSONOptionsSchema = (0, _aigne_afs_utils_zod.camelize)(zod.z.object({
|
|
22
35
|
name: (0, _aigne_afs_utils_zod.optionalize)(zod.z.string()),
|
|
23
36
|
jsonPath: zod.z.string().describe("The path to the JSON/YAML file to mount"),
|
|
@@ -38,7 +51,7 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
38
51
|
static manifest() {
|
|
39
52
|
return {
|
|
40
53
|
name: "json",
|
|
41
|
-
description: "
|
|
54
|
+
description: "JSON or YAML file — navigate and edit structured data as a tree.\n- Objects and arrays become directories, primitives become leaf nodes\n- Read/write individual values, search across structure\n- Path structure: `/{key}/{nested-key}` (arrays indexed by position)",
|
|
42
55
|
uriTemplate: "json://{localPath+}",
|
|
43
56
|
category: "structured-data",
|
|
44
57
|
schema: zod.z.object({ localPath: zod.z.string() }),
|
|
@@ -46,12 +59,56 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
46
59
|
"json",
|
|
47
60
|
"yaml",
|
|
48
61
|
"structured-data"
|
|
49
|
-
]
|
|
62
|
+
],
|
|
63
|
+
capabilityTags: [
|
|
64
|
+
"read-write",
|
|
65
|
+
"crud",
|
|
66
|
+
"search",
|
|
67
|
+
"auth:none",
|
|
68
|
+
"local"
|
|
69
|
+
],
|
|
70
|
+
security: {
|
|
71
|
+
riskLevel: "sandboxed",
|
|
72
|
+
resourceAccess: []
|
|
73
|
+
},
|
|
74
|
+
capabilities: { filesystem: {
|
|
75
|
+
read: true,
|
|
76
|
+
write: true
|
|
77
|
+
} }
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
static treeSchema() {
|
|
81
|
+
return {
|
|
82
|
+
operations: [
|
|
83
|
+
"list",
|
|
84
|
+
"read",
|
|
85
|
+
"write",
|
|
86
|
+
"delete",
|
|
87
|
+
"search",
|
|
88
|
+
"stat",
|
|
89
|
+
"explain"
|
|
90
|
+
],
|
|
91
|
+
tree: {
|
|
92
|
+
"/": { kind: "json:root" },
|
|
93
|
+
"/{key}": { kind: "json:value" }
|
|
94
|
+
},
|
|
95
|
+
auth: { type: "none" },
|
|
96
|
+
bestFor: [
|
|
97
|
+
"JSON/YAML navigation",
|
|
98
|
+
"structured data editing",
|
|
99
|
+
"config files"
|
|
100
|
+
],
|
|
101
|
+
notFor: ["binary files", "large datasets"]
|
|
50
102
|
};
|
|
51
103
|
}
|
|
52
104
|
static async load({ basePath, config } = {}) {
|
|
105
|
+
const raw = config;
|
|
106
|
+
const normalized = raw?.localPath && !raw.jsonPath ? {
|
|
107
|
+
...raw,
|
|
108
|
+
jsonPath: raw.localPath
|
|
109
|
+
} : raw;
|
|
53
110
|
return new AFSJSON({
|
|
54
|
-
...await AFSJSON.schema().parseAsync(
|
|
111
|
+
...await AFSJSON.schema().parseAsync(normalized),
|
|
55
112
|
cwd: basePath
|
|
56
113
|
});
|
|
57
114
|
}
|
|
@@ -68,22 +125,17 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
68
125
|
this.options = options;
|
|
69
126
|
if (options.localPath && !options.jsonPath) options.jsonPath = options.localPath;
|
|
70
127
|
(0, _aigne_afs_utils_zod.zodParse)(afsJSONOptionsSchema, options);
|
|
71
|
-
let jsonPath;
|
|
72
|
-
jsonPath = options.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (!(0, node_fs.existsSync)(jsonPath)) {
|
|
76
|
-
(0, node_fs.mkdirSync)((0, node_path.dirname)(jsonPath), { recursive: true });
|
|
77
|
-
(0, node_fs.writeFileSync)(jsonPath, "{}", "utf8");
|
|
78
|
-
}
|
|
79
|
-
const ext = (0, node_path.extname)(jsonPath).toLowerCase();
|
|
128
|
+
let jsonPath = (0, _aigne_afs_provider_utils.resolveLocalPath)(options.jsonPath, { cwd: options.cwd });
|
|
129
|
+
if (options.cwd) jsonPath = assertLocalPathWithinCwd(jsonPath, options.cwd, options.jsonPath);
|
|
130
|
+
const platform = (0, _aigne_afs.getPlatform)();
|
|
131
|
+
const ext = platform.path.extname(jsonPath).toLowerCase();
|
|
80
132
|
this.fileFormat = ext === ".yaml" || ext === ".yml" ? "yaml" : "json";
|
|
81
133
|
const extensions = [
|
|
82
134
|
".json",
|
|
83
135
|
".yaml",
|
|
84
136
|
".yml"
|
|
85
137
|
];
|
|
86
|
-
let name =
|
|
138
|
+
let name = platform.path.basename(jsonPath);
|
|
87
139
|
for (const e of extensions) if (name.endsWith(e)) {
|
|
88
140
|
name = name.slice(0, -e.length);
|
|
89
141
|
break;
|
|
@@ -94,6 +146,12 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
94
146
|
this.accessMode = options.accessMode ?? (options.agentSkills ? "readonly" : "readwrite");
|
|
95
147
|
this.resolvedJsonPath = jsonPath;
|
|
96
148
|
}
|
|
149
|
+
async supportedAs(path) {
|
|
150
|
+
await this.ensureLoaded();
|
|
151
|
+
const normalizedPath = this.normalizePath(path);
|
|
152
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
153
|
+
return this.getValueAtPath(this.jsonData, segments) === void 0 ? [] : [...JSON_AS_FORMATS];
|
|
154
|
+
}
|
|
97
155
|
/**
|
|
98
156
|
* Read metadata for a JSON node via /.meta or /path/.meta
|
|
99
157
|
* Returns stored metadata merged with computed type information
|
|
@@ -145,7 +203,17 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
145
203
|
if (!this.isDirectoryValue(value)) return { data: [] };
|
|
146
204
|
const entries = [];
|
|
147
205
|
const rootChildren = this.getChildren(value);
|
|
148
|
-
const
|
|
206
|
+
const rootChildrenToProcess = rootChildren.length > maxChildren ? rootChildren.slice(0, maxChildren) : rootChildren;
|
|
207
|
+
const orderBy = ctx.options?.orderBy;
|
|
208
|
+
if (maxDepth === 1 && (0, _aigne_afs.orderByRequested)(orderBy)) {
|
|
209
|
+
const { entries: ordered, applied } = (0, _aigne_afs.orderEntries)(rootChildrenToProcess.map((child) => this.valueToAFSEntry(normalizedPath === "/" ? `/${child.key}` : `${normalizedPath}/${child.key}`, child.value)), orderBy);
|
|
210
|
+
const data = ordered.slice(0, limit);
|
|
211
|
+
return applied ? { data } : {
|
|
212
|
+
data,
|
|
213
|
+
meta: { orderByIgnored: true }
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const queue = rootChildrenToProcess.map((child) => ({
|
|
149
217
|
path: normalizedPath === "/" ? `/${child.key}` : `${normalizedPath}/${child.key}`,
|
|
150
218
|
value: child.value,
|
|
151
219
|
depth: 1
|
|
@@ -170,14 +238,37 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
170
238
|
}
|
|
171
239
|
}
|
|
172
240
|
}
|
|
241
|
+
if ((0, _aigne_afs.orderByRequested)(orderBy)) return {
|
|
242
|
+
data: entries,
|
|
243
|
+
meta: { orderByIgnored: true }
|
|
244
|
+
};
|
|
173
245
|
return { data: entries };
|
|
174
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* AFSJSON handles `handleDepth` itself, so it doesn't get the base.ts
|
|
249
|
+
* orderBy fallback — it implements ordering natively in listHandler and
|
|
250
|
+
* declares the sub-capability here (consumed by both base.list's
|
|
251
|
+
* no-silent-drop check and the /.meta/.capabilities manifest).
|
|
252
|
+
*/
|
|
253
|
+
getOperationsDeclaration() {
|
|
254
|
+
const ops = super.getOperationsDeclaration();
|
|
255
|
+
if (typeof ops.list === "boolean" ? ops.list : ops.list?.supported === true) ops.list = {
|
|
256
|
+
supported: true,
|
|
257
|
+
features: { orderBy: true }
|
|
258
|
+
};
|
|
259
|
+
return ops;
|
|
260
|
+
}
|
|
175
261
|
async readHandler(ctx) {
|
|
176
262
|
await this.ensureLoaded();
|
|
177
263
|
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
178
264
|
const segments = this.getPathSegments(normalizedPath);
|
|
179
265
|
const value = this.getValueAtPath(this.jsonData, segments);
|
|
180
266
|
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
267
|
+
const as = ctx.options?.as;
|
|
268
|
+
if (as) {
|
|
269
|
+
if (!this.isJsonAsFormat(as)) throw new _aigne_afs.AFSNotFoundError((0, ufo.joinURL)(normalizedPath, ".as", as));
|
|
270
|
+
return this.buildJsonAsEntry(normalizedPath, value, as);
|
|
271
|
+
}
|
|
181
272
|
return this.valueToAFSEntry(normalizedPath, value);
|
|
182
273
|
}
|
|
183
274
|
/**
|
|
@@ -193,6 +284,15 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
193
284
|
await this.ensureLoaded();
|
|
194
285
|
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
195
286
|
const segments = this.getPathSegments(normalizedPath);
|
|
287
|
+
const ifMatch = ctx.options?.ifMatch;
|
|
288
|
+
if (ifMatch !== void 0) {
|
|
289
|
+
const currentValue = this.getValueAtPath(this.jsonData, segments);
|
|
290
|
+
const currentVersion = this.computeVersion(currentValue);
|
|
291
|
+
if (ifMatch !== currentVersion) throw new _aigne_afs.AFSConflictError(normalizedPath, {
|
|
292
|
+
cid: currentVersion,
|
|
293
|
+
mtime: this.fileStats.mtime ? this.fileStats.mtime.getTime() : null
|
|
294
|
+
});
|
|
295
|
+
}
|
|
196
296
|
if (payload.content !== void 0) this.setValueAtPath(this.jsonData, segments, payload.content);
|
|
197
297
|
if (payload.meta !== void 0 && typeof payload.meta === "object") {
|
|
198
298
|
const finalMeta = {
|
|
@@ -215,7 +315,8 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
215
315
|
updatedAt: this.fileStats.mtime,
|
|
216
316
|
meta: {
|
|
217
317
|
...storedMeta,
|
|
218
|
-
childrenCount: isDir ? children.length : void 0
|
|
318
|
+
childrenCount: isDir ? children.length : void 0,
|
|
319
|
+
version: this.computeVersion(newValue)
|
|
219
320
|
},
|
|
220
321
|
userId: payload.userId,
|
|
221
322
|
sessionId: payload.sessionId,
|
|
@@ -291,6 +392,7 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
291
392
|
const children = isDir ? this.getChildren(value) : [];
|
|
292
393
|
const meta = { ...this.loadMeta(normalizedPath) };
|
|
293
394
|
if (isDir) meta.childrenCount = children.length;
|
|
395
|
+
meta.version = this.computeVersion(value);
|
|
294
396
|
return { data: {
|
|
295
397
|
id: segments.length > 0 ? segments[segments.length - 1] : "/",
|
|
296
398
|
path: normalizedPath,
|
|
@@ -299,6 +401,16 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
299
401
|
meta
|
|
300
402
|
} };
|
|
301
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Compute an opaque version string for a JSON value at a given path.
|
|
406
|
+
* Used as the ifMatch token for optimistic-concurrency writes. SHA-256 over
|
|
407
|
+
* the canonical JSON serialization keeps the token stable across mounts of
|
|
408
|
+
* the same content. Clients treat this as opaque — see AFSWriteOptions.ifMatch.
|
|
409
|
+
*/
|
|
410
|
+
computeVersion(value) {
|
|
411
|
+
const canonical = value === void 0 ? " undefined" : JSON.stringify(value) ?? "null";
|
|
412
|
+
return (0, node_crypto.createHash)("sha256").update(canonical).digest("hex");
|
|
413
|
+
}
|
|
302
414
|
async readCapabilitiesHandler(_ctx) {
|
|
303
415
|
await this.ensureLoaded();
|
|
304
416
|
const operations = [
|
|
@@ -309,6 +421,11 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
309
421
|
"search"
|
|
310
422
|
];
|
|
311
423
|
if (this.accessMode === "readwrite") operations.push("write", "delete", "rename");
|
|
424
|
+
const baseOps = this.getOperationsDeclaration();
|
|
425
|
+
if (typeof baseOps.write === "boolean" ? baseOps.write : baseOps.write?.supported === true) baseOps.write = {
|
|
426
|
+
supported: true,
|
|
427
|
+
features: { ifMatch: true }
|
|
428
|
+
};
|
|
312
429
|
return {
|
|
313
430
|
id: "/.meta/.capabilities",
|
|
314
431
|
path: "/.meta/.capabilities",
|
|
@@ -318,7 +435,7 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
318
435
|
description: this.description || `JSON/YAML virtual filesystem (${this.fileFormat} format)`,
|
|
319
436
|
tools: [],
|
|
320
437
|
actions: [],
|
|
321
|
-
operations:
|
|
438
|
+
operations: baseOps
|
|
322
439
|
},
|
|
323
440
|
meta: {
|
|
324
441
|
kind: "afs:capabilities",
|
|
@@ -492,16 +609,20 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
492
609
|
*/
|
|
493
610
|
async ensureLoaded() {
|
|
494
611
|
if (this.jsonData !== null) return;
|
|
612
|
+
const platform = (0, _aigne_afs.getPlatform)();
|
|
495
613
|
try {
|
|
496
|
-
const stats = await
|
|
614
|
+
const stats = await platform.fs.stat(this.resolvedJsonPath);
|
|
497
615
|
this.fileStats = {
|
|
498
|
-
birthtime: stats.birthtime,
|
|
499
|
-
mtime: stats.mtime
|
|
616
|
+
birthtime: stats.birthtime ? new Date(stats.birthtime) : void 0,
|
|
617
|
+
mtime: stats.mtime ? new Date(stats.mtime) : void 0
|
|
500
618
|
};
|
|
501
|
-
this.jsonData = (0,
|
|
502
|
-
} catch
|
|
503
|
-
|
|
504
|
-
|
|
619
|
+
this.jsonData = (0, js_yaml.load)(await platform.fs.readTextFile(this.resolvedJsonPath));
|
|
620
|
+
} catch {
|
|
621
|
+
try {
|
|
622
|
+
await platform.fs.mkdir(platform.path.dirname(this.resolvedJsonPath), { recursive: true });
|
|
623
|
+
await platform.fs.writeFile(this.resolvedJsonPath, "{}");
|
|
624
|
+
} catch {}
|
|
625
|
+
this.jsonData = {};
|
|
505
626
|
}
|
|
506
627
|
}
|
|
507
628
|
/**
|
|
@@ -509,22 +630,34 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
509
630
|
*/
|
|
510
631
|
async saveToFile() {
|
|
511
632
|
let content;
|
|
512
|
-
if (this.fileFormat === "yaml") content = (0,
|
|
633
|
+
if (this.fileFormat === "yaml") content = (0, js_yaml.dump)(this.jsonData);
|
|
513
634
|
else content = JSON.stringify(this.jsonData, null, 2);
|
|
514
|
-
|
|
515
|
-
|
|
635
|
+
const platform = (0, _aigne_afs.getPlatform)();
|
|
636
|
+
await platform.fs.writeFile(this.resolvedJsonPath, content);
|
|
637
|
+
const stats = await platform.fs.stat(this.resolvedJsonPath);
|
|
516
638
|
this.fileStats = {
|
|
517
|
-
birthtime: this.fileStats.birthtime
|
|
518
|
-
mtime: stats.mtime
|
|
639
|
+
birthtime: this.fileStats.birthtime,
|
|
640
|
+
mtime: stats.mtime ? new Date(stats.mtime) : void 0
|
|
519
641
|
};
|
|
520
642
|
}
|
|
521
643
|
/**
|
|
522
644
|
* Get path segments from normalized path
|
|
523
645
|
*/
|
|
646
|
+
/**
|
|
647
|
+
* Dangerous property names that must never be used as object keys.
|
|
648
|
+
* Prevents prototype pollution attacks (e.g., writing to /__proto__/polluted).
|
|
649
|
+
*/
|
|
650
|
+
static DANGEROUS_KEYS = new Set([
|
|
651
|
+
"__proto__",
|
|
652
|
+
"constructor",
|
|
653
|
+
"prototype"
|
|
654
|
+
]);
|
|
524
655
|
getPathSegments(path) {
|
|
525
656
|
const normalized = this.normalizePath(path);
|
|
526
657
|
if (normalized === "/") return [];
|
|
527
|
-
|
|
658
|
+
const segments = normalized.slice(1).split("/");
|
|
659
|
+
for (const segment of segments) if (AFSJSON.DANGEROUS_KEYS.has(segment)) throw new Error(`Path segment "${segment}" is not allowed (prototype pollution guard)`);
|
|
660
|
+
return segments;
|
|
528
661
|
}
|
|
529
662
|
/**
|
|
530
663
|
* Navigate to a value in the JSON structure using path segments
|
|
@@ -609,6 +742,9 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
609
742
|
if (typeof value === "object" && value !== null) return true;
|
|
610
743
|
return false;
|
|
611
744
|
}
|
|
745
|
+
isJsonAsFormat(value) {
|
|
746
|
+
return typeof value === "string" && JSON_AS_FORMATS.includes(value);
|
|
747
|
+
}
|
|
612
748
|
/**
|
|
613
749
|
* Get children of a directory value (filters out .afs meta key)
|
|
614
750
|
*/
|
|
@@ -623,6 +759,48 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
623
759
|
}));
|
|
624
760
|
return [];
|
|
625
761
|
}
|
|
762
|
+
compactJsonValue(value) {
|
|
763
|
+
if (!this.isDirectoryValue(value)) return value;
|
|
764
|
+
if (Array.isArray(value)) return {
|
|
765
|
+
type: "array",
|
|
766
|
+
length: value.length,
|
|
767
|
+
sample: value.slice(0, 3).map((item) => this.compactJsonValue(item))
|
|
768
|
+
};
|
|
769
|
+
const entries = Object.entries(value).filter(([key]) => !this.isMetaKey(key));
|
|
770
|
+
const preview = {};
|
|
771
|
+
for (const [key, child] of entries) {
|
|
772
|
+
if (Object.keys(preview).length >= 6) break;
|
|
773
|
+
if (!this.isDirectoryValue(child)) preview[key] = child;
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
type: "object",
|
|
777
|
+
keys: entries.map(([key]) => key).slice(0, 12),
|
|
778
|
+
...Object.keys(preview).length > 0 ? { preview } : {}
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
stripMetaFromValue(value) {
|
|
782
|
+
if (!this.isDirectoryValue(value)) return value;
|
|
783
|
+
if (Array.isArray(value)) return value.map((item) => this.stripMetaFromValue(item));
|
|
784
|
+
const result = {};
|
|
785
|
+
for (const [key, child] of Object.entries(value)) if (!this.isMetaKey(key)) result[key] = this.stripMetaFromValue(child);
|
|
786
|
+
return result;
|
|
787
|
+
}
|
|
788
|
+
buildJsonAsEntry(path, value, as) {
|
|
789
|
+
let content;
|
|
790
|
+
if (as === "content") content = value;
|
|
791
|
+
else if (as === "tree") content = this.stripMetaFromValue(value);
|
|
792
|
+
else content = this.compactJsonValue(value);
|
|
793
|
+
return {
|
|
794
|
+
id: `${path}.as:${as}`,
|
|
795
|
+
path: (0, ufo.joinURL)(path, ".as", as),
|
|
796
|
+
content,
|
|
797
|
+
meta: {
|
|
798
|
+
kind: `json:${as}`,
|
|
799
|
+
representation: as
|
|
800
|
+
},
|
|
801
|
+
actions: []
|
|
802
|
+
};
|
|
803
|
+
}
|
|
626
804
|
/**
|
|
627
805
|
* Convert a JSON value to an AFSEntry
|
|
628
806
|
*/
|
|
@@ -634,7 +812,8 @@ var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
|
634
812
|
content: isDir ? void 0 : value,
|
|
635
813
|
meta: {
|
|
636
814
|
kind,
|
|
637
|
-
childrenCount: isDir ? children.length : void 0
|
|
815
|
+
childrenCount: isDir ? children.length : void 0,
|
|
816
|
+
version: this.computeVersion(value)
|
|
638
817
|
},
|
|
639
818
|
createdAt: this.fileStats.birthtime,
|
|
640
819
|
updatedAt: this.fileStats.mtime
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AFSAccessMode, AFSEntry, AFSExplainResult, AFSListResult, AFSModuleLoadParams, AFSSearchOptions, AFSStatResult, AFSWriteEntryPayload, ProviderManifest } from "@aigne/afs";
|
|
1
|
+
import { AFSAccessMode, AFSEntry, AFSExplainResult, AFSListResult, AFSModuleLoadParams, AFSSearchOptions, AFSStatResult, AFSWriteEntryPayload, OperationsDeclaration, ProviderManifest, ProviderTreeSchema } from "@aigne/afs";
|
|
2
2
|
import { AFSBaseProvider, RouteContext } from "@aigne/afs/provider";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
@@ -47,6 +47,7 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
47
47
|
agentSkills: boolean | undefined;
|
|
48
48
|
}, unknown>>;
|
|
49
49
|
static manifest(): ProviderManifest;
|
|
50
|
+
static treeSchema(): ProviderTreeSchema;
|
|
50
51
|
static load({
|
|
51
52
|
basePath,
|
|
52
53
|
config
|
|
@@ -64,6 +65,7 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
64
65
|
localPath?: string;
|
|
65
66
|
uri?: string;
|
|
66
67
|
});
|
|
68
|
+
supportedAs(path: string): Promise<string[]>;
|
|
67
69
|
/**
|
|
68
70
|
* Read metadata for a JSON node via /.meta or /path/.meta
|
|
69
71
|
* Returns stored metadata merged with computed type information
|
|
@@ -77,6 +79,13 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
77
79
|
}>): Promise<AFSListResult & {
|
|
78
80
|
noExpand?: string[];
|
|
79
81
|
}>;
|
|
82
|
+
/**
|
|
83
|
+
* AFSJSON handles `handleDepth` itself, so it doesn't get the base.ts
|
|
84
|
+
* orderBy fallback — it implements ordering natively in listHandler and
|
|
85
|
+
* declares the sub-capability here (consumed by both base.list's
|
|
86
|
+
* no-silent-drop check and the /.meta/.capabilities manifest).
|
|
87
|
+
*/
|
|
88
|
+
protected getOperationsDeclaration(): OperationsDeclaration;
|
|
80
89
|
readHandler(ctx: RouteContext<{
|
|
81
90
|
path?: string;
|
|
82
91
|
}>): Promise<AFSEntry | undefined>;
|
|
@@ -113,6 +122,13 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
113
122
|
statHandler(ctx: RouteContext<{
|
|
114
123
|
path?: string;
|
|
115
124
|
}>): Promise<AFSStatResult>;
|
|
125
|
+
/**
|
|
126
|
+
* Compute an opaque version string for a JSON value at a given path.
|
|
127
|
+
* Used as the ifMatch token for optimistic-concurrency writes. SHA-256 over
|
|
128
|
+
* the canonical JSON serialization keeps the token stable across mounts of
|
|
129
|
+
* the same content. Clients treat this as opaque — see AFSWriteOptions.ifMatch.
|
|
130
|
+
*/
|
|
131
|
+
private computeVersion;
|
|
116
132
|
readCapabilitiesHandler(_ctx: RouteContext): Promise<AFSEntry | undefined>;
|
|
117
133
|
explainHandler(ctx: RouteContext<{
|
|
118
134
|
path?: string;
|
|
@@ -153,6 +169,11 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
153
169
|
/**
|
|
154
170
|
* Get path segments from normalized path
|
|
155
171
|
*/
|
|
172
|
+
/**
|
|
173
|
+
* Dangerous property names that must never be used as object keys.
|
|
174
|
+
* Prevents prototype pollution attacks (e.g., writing to /__proto__/polluted).
|
|
175
|
+
*/
|
|
176
|
+
private static readonly DANGEROUS_KEYS;
|
|
156
177
|
private getPathSegments;
|
|
157
178
|
/**
|
|
158
179
|
* Navigate to a value in the JSON structure using path segments
|
|
@@ -170,10 +191,14 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
170
191
|
* Check if a value is a "directory" (object or array with children)
|
|
171
192
|
*/
|
|
172
193
|
private isDirectoryValue;
|
|
194
|
+
private isJsonAsFormat;
|
|
173
195
|
/**
|
|
174
196
|
* Get children of a directory value (filters out .afs meta key)
|
|
175
197
|
*/
|
|
176
198
|
private getChildren;
|
|
199
|
+
private compactJsonValue;
|
|
200
|
+
private stripMetaFromValue;
|
|
201
|
+
private buildJsonAsEntry;
|
|
177
202
|
/**
|
|
178
203
|
* Convert a JSON value to an AFSEntry
|
|
179
204
|
*/
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;UA2EiB,cAAA;EACf,IAAA;EACA,QAAA;EACA,WAAA;;;;;;;EAOA,UAAA,GAAa,aAAA;EAMb;;;AAuBF;;EAvBE,WAAA;AAAA;;;;;;;cAuBW,OAAA,SAAgB,eAAA;EA0DR,OAAA,EAAS,cAAA;IAAmB,GAAA;IAAc,SAAA;IAAoB,GAAA;EAAA;EAAA,OAzD1E,MAAA,CAAA,GAAM,CAAA,CAAA,OAAA;;;;;;;;;;;;;SAIN,QAAA,CAAA,GAAY,gBAAA;EAAA,OAoBZ,UAAA,CAAA,GAAc,kBAAA;EAAA,OAaR,IAAA,CAAA;IAAO,QAAA;IAAU;EAAA,IAAU,mBAAA,GAAwB,OAAA,CAAA,OAAA;EAAA,SAOvD,IAAA;EAAA,SACA,WAAA;EAAA,SACA,UAAA,EAAY,aAAA;EAAA,SACZ,WAAA;EAAA,QAED,QAAA;EAAA,QACA,SAAA;EAAA,QAIA,UAAA;EAAA,QACA,gBAAA;cAEW,OAAA,EAAS,cAAA;IAAmB,GAAA;IAAc,SAAA;IAAoB,GAAA;EAAA;EAwC3E,WAAA,CAAY,IAAA,WAAe,OAAA;EAyhB2B;;;;;EAvftD,eAAA,CAAgB,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,QAAA;EAmE/D,WAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KACnB,OAAA,CAAQ,aAAA;IAAkB,QAAA;EAAA;;;;;;;YA8GnB,wBAAA,CAAA,GAA4B,qBAAA;EAUhC,WAAA,CAAY,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,QAAA;;;;;;;;;;EA8B3D,YAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,IACpB,OAAA,EAAS,oBAAA,GACR,OAAA;IAAU,IAAA,EAAM,QAAA;EAAA;EAiEb,aAAA,CAAc,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA;IAAU,OAAA;EAAA;EA0B/D,aAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,IACpB,OAAA,WACC,OAAA;IAAU,OAAA;EAAA;EAiCP,aAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,IACpB,KAAA,UACA,OAAA,GAAU,gBAAA,GACT,OAAA;IAAU,IAAA,EAAM,QAAA;IAAY,OAAA;EAAA;EAuDzB,WAAA,CAAY,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,aAAA;EAxZ3D;;;;;;EAAA,QAkcE,cAAA;EAQF,uBAAA,CAAwB,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,QAAA;EA6CrD,cAAA,CAAe,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,gBAAA;EAlbzD;;;EAAA,QA8hBH,YAAA;EAtaF;;;EAAA,QAqbE,SAAA;EArbiD;;;;;;;EAAA,QAgcjD,QAAA;EA/ZL;;;;;;;EAAA,QA+dK,QAAA;EA9Z6D;;;;EAAA,QAwdvD,YAAA;EA5bZ;;;EAAA,QA4dY,UAAA;EAzbP;;;EACL;;;;EAAA,wBAodsB,cAAA;EAAA,QAEhB,eAAA;EApduB;;;EAAA,QAsevB,cAAA;EA/aU;;;EAAA,QAucV,cAAA;EArZF;;;EAAA,QA+cE,iBAAA;EA/cmD;;;EAAA,QA0fnD,gBAAA;EAAA,QAMA,cAAA;EAndoD;;;EAAA,QA0dpD,WAAA;EAAA,QAYA,gBAAA;EAAA,QAwBA,kBAAA;EAAA,QAUA,gBAAA;EAxOM;;;EAAA,QA4PN,eAAA;AAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AFSAccessMode, AFSEntry, AFSExplainResult, AFSListResult, AFSModuleLoadParams, AFSSearchOptions, AFSStatResult, AFSWriteEntryPayload, ProviderManifest } from "@aigne/afs";
|
|
1
|
+
import { AFSAccessMode, AFSEntry, AFSExplainResult, AFSListResult, AFSModuleLoadParams, AFSSearchOptions, AFSStatResult, AFSWriteEntryPayload, OperationsDeclaration, ProviderManifest, ProviderTreeSchema } from "@aigne/afs";
|
|
2
2
|
import { AFSBaseProvider, RouteContext } from "@aigne/afs/provider";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
@@ -47,6 +47,7 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
47
47
|
agentSkills: boolean | undefined;
|
|
48
48
|
}, unknown>>;
|
|
49
49
|
static manifest(): ProviderManifest;
|
|
50
|
+
static treeSchema(): ProviderTreeSchema;
|
|
50
51
|
static load({
|
|
51
52
|
basePath,
|
|
52
53
|
config
|
|
@@ -64,6 +65,7 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
64
65
|
localPath?: string;
|
|
65
66
|
uri?: string;
|
|
66
67
|
});
|
|
68
|
+
supportedAs(path: string): Promise<string[]>;
|
|
67
69
|
/**
|
|
68
70
|
* Read metadata for a JSON node via /.meta or /path/.meta
|
|
69
71
|
* Returns stored metadata merged with computed type information
|
|
@@ -77,6 +79,13 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
77
79
|
}>): Promise<AFSListResult & {
|
|
78
80
|
noExpand?: string[];
|
|
79
81
|
}>;
|
|
82
|
+
/**
|
|
83
|
+
* AFSJSON handles `handleDepth` itself, so it doesn't get the base.ts
|
|
84
|
+
* orderBy fallback — it implements ordering natively in listHandler and
|
|
85
|
+
* declares the sub-capability here (consumed by both base.list's
|
|
86
|
+
* no-silent-drop check and the /.meta/.capabilities manifest).
|
|
87
|
+
*/
|
|
88
|
+
protected getOperationsDeclaration(): OperationsDeclaration;
|
|
80
89
|
readHandler(ctx: RouteContext<{
|
|
81
90
|
path?: string;
|
|
82
91
|
}>): Promise<AFSEntry | undefined>;
|
|
@@ -113,6 +122,13 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
113
122
|
statHandler(ctx: RouteContext<{
|
|
114
123
|
path?: string;
|
|
115
124
|
}>): Promise<AFSStatResult>;
|
|
125
|
+
/**
|
|
126
|
+
* Compute an opaque version string for a JSON value at a given path.
|
|
127
|
+
* Used as the ifMatch token for optimistic-concurrency writes. SHA-256 over
|
|
128
|
+
* the canonical JSON serialization keeps the token stable across mounts of
|
|
129
|
+
* the same content. Clients treat this as opaque — see AFSWriteOptions.ifMatch.
|
|
130
|
+
*/
|
|
131
|
+
private computeVersion;
|
|
116
132
|
readCapabilitiesHandler(_ctx: RouteContext): Promise<AFSEntry | undefined>;
|
|
117
133
|
explainHandler(ctx: RouteContext<{
|
|
118
134
|
path?: string;
|
|
@@ -153,6 +169,11 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
153
169
|
/**
|
|
154
170
|
* Get path segments from normalized path
|
|
155
171
|
*/
|
|
172
|
+
/**
|
|
173
|
+
* Dangerous property names that must never be used as object keys.
|
|
174
|
+
* Prevents prototype pollution attacks (e.g., writing to /__proto__/polluted).
|
|
175
|
+
*/
|
|
176
|
+
private static readonly DANGEROUS_KEYS;
|
|
156
177
|
private getPathSegments;
|
|
157
178
|
/**
|
|
158
179
|
* Navigate to a value in the JSON structure using path segments
|
|
@@ -170,10 +191,14 @@ declare class AFSJSON extends AFSBaseProvider {
|
|
|
170
191
|
* Check if a value is a "directory" (object or array with children)
|
|
171
192
|
*/
|
|
172
193
|
private isDirectoryValue;
|
|
194
|
+
private isJsonAsFormat;
|
|
173
195
|
/**
|
|
174
196
|
* Get children of a directory value (filters out .afs meta key)
|
|
175
197
|
*/
|
|
176
198
|
private getChildren;
|
|
199
|
+
private compactJsonValue;
|
|
200
|
+
private stripMetaFromValue;
|
|
201
|
+
private buildJsonAsEntry;
|
|
177
202
|
/**
|
|
178
203
|
* Convert a JSON value to an AFSEntry
|
|
179
204
|
*/
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;UA2EiB,cAAA;EACf,IAAA;EACA,QAAA;EACA,WAAA;;;;;;;EAOA,UAAA,GAAa,aAAA;EAMb;;;AAuBF;;EAvBE,WAAA;AAAA;;;;;;;cAuBW,OAAA,SAAgB,eAAA;EA0DR,OAAA,EAAS,cAAA;IAAmB,GAAA;IAAc,SAAA;IAAoB,GAAA;EAAA;EAAA,OAzD1E,MAAA,CAAA,GAAM,CAAA,CAAA,OAAA;;;;;;;;;;;;;SAIN,QAAA,CAAA,GAAY,gBAAA;EAAA,OAoBZ,UAAA,CAAA,GAAc,kBAAA;EAAA,OAaR,IAAA,CAAA;IAAO,QAAA;IAAU;EAAA,IAAU,mBAAA,GAAwB,OAAA,CAAA,OAAA;EAAA,SAOvD,IAAA;EAAA,SACA,WAAA;EAAA,SACA,UAAA,EAAY,aAAA;EAAA,SACZ,WAAA;EAAA,QAED,QAAA;EAAA,QACA,SAAA;EAAA,QAIA,UAAA;EAAA,QACA,gBAAA;cAEW,OAAA,EAAS,cAAA;IAAmB,GAAA;IAAc,SAAA;IAAoB,GAAA;EAAA;EAwC3E,WAAA,CAAY,IAAA,WAAe,OAAA;EAyhB2B;;;;;EAvftD,eAAA,CAAgB,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,QAAA;EAmE/D,WAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KACnB,OAAA,CAAQ,aAAA;IAAkB,QAAA;EAAA;;;;;;;YA8GnB,wBAAA,CAAA,GAA4B,qBAAA;EAUhC,WAAA,CAAY,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,QAAA;;;;;;;;;;EA8B3D,YAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,IACpB,OAAA,EAAS,oBAAA,GACR,OAAA;IAAU,IAAA,EAAM,QAAA;EAAA;EAiEb,aAAA,CAAc,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA;IAAU,OAAA;EAAA;EA0B/D,aAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,IACpB,OAAA,WACC,OAAA;IAAU,OAAA;EAAA;EAiCP,aAAA,CACJ,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,IACpB,KAAA,UACA,OAAA,GAAU,gBAAA,GACT,OAAA;IAAU,IAAA,EAAM,QAAA;IAAY,OAAA;EAAA;EAuDzB,WAAA,CAAY,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,aAAA;EAxZ3D;;;;;;EAAA,QAkcE,cAAA;EAQF,uBAAA,CAAwB,IAAA,EAAM,YAAA,GAAe,OAAA,CAAQ,QAAA;EA6CrD,cAAA,CAAe,GAAA,EAAK,YAAA;IAAe,IAAA;EAAA,KAAmB,OAAA,CAAQ,gBAAA;EAlbzD;;;EAAA,QA8hBH,YAAA;EAtaF;;;EAAA,QAqbE,SAAA;EArbiD;;;;;;;EAAA,QAgcjD,QAAA;EA/ZL;;;;;;;EAAA,QA+dK,QAAA;EA9Z6D;;;;EAAA,QAwdvD,YAAA;EA5bZ;;;EAAA,QA4dY,UAAA;EAzbP;;;EACL;;;;EAAA,wBAodsB,cAAA;EAAA,QAEhB,eAAA;EApduB;;;EAAA,QAsevB,cAAA;EA/aU;;;EAAA,QAucV,cAAA;EArZF;;;EAAA,QA+cE,iBAAA;EA/cmD;;;EAAA,QA0fnD,gBAAA;EAAA,QAMA,cAAA;EAndoD;;;EAAA,QA0dpD,WAAA;EAAA,QAYA,gBAAA;EAAA,QAwBA,kBAAA;EAAA,QAUA,gBAAA;EAxOM;;;EAAA,QA4PN,eAAA;AAAA"}
|