UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

110 lines • 5.42 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 { fetchMessagesForDay, getHistoryJsonl, streamMessagesForDay, } from "./history.js"; import { refusalsDenied } from "./client.js"; import { getUserProfile, userJsonBytes } from "./users.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 readUser(accessor, _match, path, entry) { if (entry === null) throw enoent(path); const user = await getUserProfile(accessor, String(entry.extra.user_id)); return userJsonBytes(user); } async function blobUrl(accessor, path, entry) { await guardDay(accessor, detectScope(path), path.virtual); if (entry === null) throw enoent(path); const url = typeof entry.extra.url_private_download === 'string' ? entry.extra.url_private_download : ''; if (url === '') throw enoent(path); return url; } async function downloadBlob(accessor, url, offset, size) { if (accessor.transport.downloadFile === undefined) { throw new Error('slack transport does not support file downloads'); } return accessor.transport.downloadFile(url, offset, size); } async function readBlob(accessor, _match, path, entry) { return downloadBlob(accessor, await blobUrl(accessor, path, entry), 0, null); } async function readBlobRange(accessor, _match, path, entry, _parent, offset, size) { return downloadBlob(accessor, await blobUrl(accessor, path, entry), offset, size); } export const read = makeRead(detectScope, { messages: readChat, user: readUser, file_blob: readBlob, }); // Only an uploaded file has a remote range to ask for. A channel's history // and a user 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); const source = accessor.transport.downloadFileStream === undefined ? await downloadBlob(accessor, url, 0, null) : accessor.transport.downloadFileStream(url, signal); yield* chunks(source, signal); } else { yield* chunks(await read(accessor, path, entry, parent), signal); } } /** * A channel or DM 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, () => fetchMessagesForDay(accessor, channelId, day)), n, end, interval, (last) => refusalsDenied(path, () => fetchMessagesForDay(accessor, channelId, day, last === null ? undefined : last.ts))); } 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, { lastDay: last }); } /** A message's place among its day's lines. Mirrors Python's `_sort_key`. */ function sortKey(message) { return Number.parseFloat(message.ts); } /** Whether a message was posted in a day's bounds. Mirrors Python's `_posted_within`. */ function postedWithin(message, start, end) { return Number(message.ts) >= start && Number(message.ts) < 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