@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
128 lines • 5.65 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { ContentType, FileStat, FileType } from "../../types.js";
import { epochToIso } from "../../utils/dates.js";
import { gnuBasename } from "../../utils/path.js";
import { stripSlash } from "../../utils/slash.js";
import { dirRows, dropCollisions, normalizeSlug, scalarString } from "../slug_tree/rows.js";
import { SlugTree } from "../slug_tree/tree.js";
import { listAllDocuments } from "./client.js";
export const SLUG_NOUN = 'Dify document slug';
async function loadTree(accessor) {
return buildDirEntries(await listAllDocuments(accessor), accessor.config.slugMetadataName);
}
export function buildDirEntries(documents, slugMetadataName = 'slug') {
const files = new Map();
const rawSlugs = new Map();
const hasSlugs = new Map();
for (const document of documents) {
let path;
let slug;
let hasSlug;
const documentId = scalarString(document.id);
try {
if (documentId === null || documentId.trim() === '') {
throw new Error('missing document id');
}
;
[slug, hasSlug] = extractSlug(document, slugMetadataName);
path = normalizeSlug(slug, SLUG_NOUN);
}
catch (err) {
console.warn(`Skipping invalid Dify document ${documentId ?? '?'}: ${String(err)}`);
continue;
}
if (files.has(path)) {
console.warn(`Skipping duplicate Dify document slug '${stripSlash(path)}': documents ` +
`${scalarString(files.get(path)?.id) ?? '?'} and ${documentId} share the same path.`);
continue;
}
files.set(path, document);
rawSlugs.set(path, slug);
hasSlugs.set(path, hasSlug);
}
const kept = dropCollisions(files, (ancestor, path) => {
console.warn(`Skipping Dify document path collision: document ${scalarString(files.get(ancestor)?.id) ?? '?'} ` +
`uses file path '${stripSlash(ancestor)}' but document ${scalarString(files.get(path)?.id) ?? '?'} ` +
`requires it as a directory prefix.`);
});
// No size: the API's is the uploaded source file (a PDF, say), not the
// segment text this mount serves, so it rides in extra.
return dirRows(kept, (path, document) => {
const created = epochText(document.created_at);
return new FileStat({
name: gnuBasename(path),
type: FileType.FILE,
content: ContentType.TEXT,
modified: created,
birthtime: created,
extra: {
slug: stripSlash(path),
source_size: extractDocumentSize(document),
slug_metadata_name: slugMetadataName,
raw_slug: rawSlugs.get(path) ?? '',
has_slug: hasSlugs.get(path) ?? false,
tokens: document.tokens ?? null,
indexing_status: document.indexing_status ?? null,
data_source_type: document.data_source_type ?? null,
document_id: scalarString(document.id) ?? '',
},
});
});
}
export function extractSlug(document, slugMetadataName = 'slug') {
const metadata = document.doc_metadata;
if (Array.isArray(metadata)) {
for (const item of metadata) {
if (item !== null && typeof item === 'object' && !Array.isArray(item)) {
const record = item;
if (record.name === slugMetadataName) {
const value = scalarString(record.value);
if (value !== null)
return [value, true];
}
}
}
}
if (metadata !== null && typeof metadata === 'object' && !Array.isArray(metadata)) {
const value = scalarString(metadata[slugMetadataName]);
if (value !== null)
return [value, true];
}
const name = scalarString(document.name);
if (name === null)
throw new Error('missing document name');
return [name, false];
}
export function extractDocumentSize(document) {
for (const candidate of [document.data_source_detail_dict, document.data_source_info]) {
if (candidate !== null && typeof candidate === 'object' && !Array.isArray(candidate)) {
const uploadFile = candidate.upload_file;
if (uploadFile !== null && typeof uploadFile === 'object' && !Array.isArray(uploadFile)) {
const size = uploadFile.size;
if (typeof size === 'number' && Number.isInteger(size))
return size;
}
}
}
return null;
}
/** A Dify timestamp (epoch seconds) as `YYYY-MM-DDTHH:MM:SSZ`; a string passes through. */
export function epochText(value) {
if (typeof value === 'number')
return epochToIso(value);
return typeof value === 'string' ? value : null;
}
export const DIFY_TREE = new SlugTree(loadTree);
//# sourceMappingURL=tree.js.map