UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

154 lines • 6.92 kB
// ========= 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 { DirListing, FileStat, FileType } from "../../types.js"; import { stringOf as pickString } from "../../utils/records.js"; import { makeReaddir } from "../hierarchy/readdir.js"; import { jsonlBytes } from "../render/json.js"; import { fetchDatasetItems, fetchDatasetRuns, fetchDatasets, fetchPrompts, fetchSessions, fetchTraces, } from "./client.js"; import { detectScope, TOP_LEVEL_DIRS } from "./scope.js"; // Mirrors LangfuseConfig.default_trace_limit in python. const DEFAULT_TRACE_LIMIT = 100; function promptVersions(record) { const value = record.versions; if (!Array.isArray(value)) return []; const numbers = []; for (const entry of value) { const parsed = typeof entry === 'number' ? entry : Number(entry); if (Number.isFinite(parsed)) numbers.push(parsed); } return numbers.sort((a, b) => a - b).map(String); } function sessionExtra(match) { return { session_id: match.slots.session_id ?? '' }; } function promptExtra(match) { return { prompt_name: match.slots.prompt_name ?? '' }; } function datasetExtra(match) { return { dataset_name: match.slots.dataset_name ?? '' }; } // What each kind's stat carries in `extra`, listed or not. export const EXTRAS = { session: sessionExtra, prompt: promptExtra, dataset: datasetExtra, }; function dirs(names) { return names.map((name) => new FileStat({ name, type: FileType.DIRECTORY })); } // A trace listing is never the directory: it is one page of root // observations, and a trace whose root never arrived has none. Cached as // complete it would prove such a trace absent while read fetches it by id. function traceRows(roots) { // A trace can hold more than one root observation. const ids = new Set(roots.map((r) => pickString(r, 'traceId'))); return [...ids].map((id) => new FileStat({ name: `${id}.json`, type: FileType.FILE })); } async function listTraces(accessor, _match) { // No implicit time window: an unset defaultFromTimestamp lists whatever the // project holds, up to defaultTraceLimit. A rolling default would hide // traces that read() happily serves, and python applies no window either. const traces = await fetchTraces(accessor.transport, { limit: accessor.config.defaultTraceLimit ?? DEFAULT_TRACE_LIMIT, fromTimestamp: accessor.config.defaultFromTimestamp, }); // The listing carries root observations while a read renders the whole // trace with its observations and scores, so a size here would cost one // fetchTrace per entry. Traces and prompts stay size-unknown until a read // hydrates them; the dataset .jsonl files are sized because their listing // already carries every item. return new DirListing({ entries: traceRows(traces), partial: true }); } async function listSessions(accessor, _match) { // The sessions of one page of the newest traces: an older one that drops // off it has not been deleted, so the listing evicts nothing. const sessions = await fetchSessions(accessor.transport, { limit: accessor.config.defaultTraceLimit ?? DEFAULT_TRACE_LIMIT, fromTimestamp: accessor.config.defaultFromTimestamp, }); return new DirListing({ entries: dirs(sessions), window: true }); } async function listSessionTraces(accessor, match) { const traces = await fetchTraces(accessor.transport, { sessionId: match.slots.session_id ?? '', limit: accessor.config.defaultTraceLimit ?? DEFAULT_TRACE_LIMIT, fromTimestamp: accessor.config.defaultFromTimestamp, }); return new DirListing({ entries: traceRows(traces), partial: true }); } async function listPrompts(accessor, _match) { const prompts = await fetchPrompts(accessor.transport); const names = [...new Set(prompts.map((p) => pickString(p, 'name')))]; return new DirListing({ entries: dirs(names), window: true }); } async function listPromptVersions(accessor, match) { const promptName = match.slots.prompt_name ?? ''; const prompts = await fetchPrompts(accessor.transport); const rows = []; for (const p of prompts) { if (pickString(p, 'name') !== promptName) continue; // The list endpoint returns PromptMeta, which carries every version of a // prompt in a `versions` array; there is no scalar `version`. for (const version of promptVersions(p)) { rows.push(new FileStat({ name: `${version}.json`, type: FileType.FILE })); } } return rows; } async function listDatasets(accessor, _match) { const datasets = await fetchDatasets(accessor.transport); return new DirListing({ entries: dirs(datasets.map((d) => pickString(d, 'name'))), window: true, }); } async function listDataset(accessor, match) { // One dataset_items call per dataset directory actually entered: the // dataset listing carries no item payloads, so items.jsonl can only be // sized here, and only for datasets the caller opens. const items = await fetchDatasetItems(accessor.transport, match.slots.dataset_name ?? ''); return [ new FileStat({ name: 'items.jsonl', type: FileType.FILE, size: jsonlBytes(items).byteLength }), new FileStat({ name: 'runs', type: FileType.DIRECTORY }), ]; } async function listDatasetRuns(accessor, match) { const runs = await fetchDatasetRuns(accessor.transport, match.slots.dataset_name ?? ''); // The listing already carries the run document read() renders, so each run // file's exact size is free here. const rows = runs.map((r) => new FileStat({ name: `${pickString(r, 'name')}.jsonl`, type: FileType.FILE, size: jsonlBytes([r]).byteLength, })); return new DirListing({ entries: rows, window: true }); } export const readdir = makeReaddir(detectScope, { listers: { traces: listTraces, sessions: listSessions, session: listSessionTraces, prompts: listPrompts, prompt: listPromptVersions, datasets: listDatasets, dataset: listDataset, runs: listDatasetRuns, }, staticRoot: TOP_LEVEL_DIRS, extras: EXTRAS, }); //# sourceMappingURL=readdir.js.map