UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

110 lines • 5.37 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 { 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