@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
110 lines • 5.37 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 { chunks } from "../../io/cooperative.js";
import { dayChannelId, followedDay, guardDay, readDays } from "../time_range.js";
import { enoent } from "../../errors/fs.js";
import { makeRead, makeReadRange, perKind } from "../hierarchy/read.js";
import { refusalsDenied } from "./client.js";
import { downloadFile, downloadFileStream } from "./files.js";
import { listMessagesForDay, getHistoryJsonl, snowflakeAt, streamMessagesForDay, } from "./history.js";
import { listMembers } from "./members.js";
import { compactJsonBytes } from "../render/json.js";
import { detectScope } from "./scope.js";
async function readChat(accessor, match, path, entry, parent) {
await guardDay(accessor, match, path.virtual);
const channelId = dayChannelId(path, entry, parent);
return refusalsDenied(path, () => getHistoryJsonl(accessor, channelId, match.slots.day ?? ''));
}
async function readMember(accessor, match, path, entry) {
if (entry === null)
throw enoent(path);
const members = await listMembers(accessor, match.slots.guild_id ?? '');
for (const m of members) {
if (m.user?.id === entry.extra.user_id)
return compactJsonBytes(m);
}
throw enoent(path);
}
async function blobUrl(accessor, path, entry) {
await guardDay(accessor, detectScope(path), path.virtual);
if (entry === null)
throw enoent(path);
const extra = entry.extra;
const direct = typeof extra.url === 'string' ? extra.url : '';
const proxy = typeof extra.proxy_url === 'string' ? extra.proxy_url : '';
const url = direct !== '' ? direct : proxy;
if (url === '')
throw enoent(path);
return url;
}
async function readBlob(accessor, _match, path, entry) {
return downloadFile(await blobUrl(accessor, path, entry), 0, null, accessor.timeout);
}
async function readBlobRange(accessor, _match, path, entry, parent, offset, size) {
return downloadFile(await blobUrl(accessor, path, entry), offset, size, accessor.timeout);
}
export const read = makeRead(detectScope, {
messages: readChat,
member: readMember,
file_blob: readBlob,
});
// Only an attachment has a remote range to ask for. A channel's history and
// a member profile are rendered here into JSON, so their bytes do not exist
// until we make them and the window can only be taken afterwards.
export const readRange = makeReadRange(detectScope, read, {
file_blob: readBlobRange,
});
/** Stream attachments; preserve the rendered order of JSON records. */
export async function* readStream(accessor, path, entry = null, parent = null, signal) {
if (detectScope(path).kind === 'file_blob') {
const url = await blobUrl(accessor, path, entry);
yield* downloadFileStream(url, signal, accessor.timeout);
}
else {
yield* chunks(await read(accessor, path, entry, parent), signal);
}
}
/**
* A channel day's last `n` messages, then each one posted to it, asked for every
* `interval` seconds while the day lasts (`followedDay`). Mirrors Python's
* `_follow_chat`.
*/
async function followChat(accessor, match, path, n, interval, entry = null, parent = null) {
await guardDay(accessor, match, path.virtual);
const channelId = dayChannelId(path, entry, parent);
const day = match.slots.day ?? '';
const [, end] = accessor.timeRange.dayBounds(day);
return followedDay(await refusalsDenied(path, () => listMessagesForDay(accessor, channelId, day)), n, end, interval, (last) => refusalsDenied(path, () => listMessagesForDay(accessor, channelId, day, 100, last === null ? undefined : last.id)));
}
export const follow = perKind(detectScope, { messages: followChat });
/** A run of a channel's days, fetched as one. Mirrors Python's `_fetch_run`. */
function runPages(accessor, channelId, first, last) {
return streamMessagesForDay(accessor, channelId, first, 100, undefined, last);
}
/** A message's place among its day's lines. Mirrors Python's `_sort_key`. */
function sortKey(message) {
return BigInt(message.id);
}
/** Whether a message was posted in a day's bounds. Mirrors Python's `_posted_within`. */
function postedWithin(message, start, end) {
return BigInt(message.id) >= snowflakeAt(start) && BigInt(message.id) < snowflakeAt(end);
}
/**
* Several chat.jsonl files, one history walk per run of a channel's
* consecutive days (`readDays`). Mirrors Python's `read_many`.
*/
export function readMany(accessor, paths, limit, handed = []) {
return readDays(accessor.timeRange, paths, handed, detectScope, (channelId, first, last) => runPages(accessor, channelId, first, last), sortKey, postedWithin, limit);
}
//# sourceMappingURL=read.js.map