UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

181 lines • 6.87 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 { conditionVersion, stale } from "../../cache/context.js"; import { DropboxApiError, dropboxRpc } from "./client.js"; import { GONE_SUMMARIES, LOST_SUMMARIES, MISS_SUMMARIES } from "./constants.js"; /** * List a folder's entries, following every continuation cursor. * * `/` and `''` both mean the account root. */ export async function listFolder(tm, path, opts = {}) { const apiPath = path === '/' || path === '' ? '' : path; const recursive = opts.recursive === true; const pageSize = opts.pageSize ?? 2000; const limit = opts.limit ?? null; const out = []; let resp = (await dropboxRpc(tm, '/files/list_folder', { path: apiPath, recursive, limit: limit === null ? pageSize : Math.min(pageSize, limit), })); for (const item of resp.entries) out.push(item); while (resp.has_more) { if (limit !== null && out.length >= limit) break; resp = (await dropboxRpc(tm, '/files/list_folder/continue', { cursor: resp.cursor, })); for (const item of resp.entries) out.push(item); } return out; } /** * A cursor at the folder's present state, without listing it. * * `/` and `''` both mean the account root. Mirrors Python `latest_cursor`. */ export async function latestCursor(tm, path, opts = {}) { const apiPath = path === '/' || path === '' ? '' : path; const resp = (await dropboxRpc(tm, '/files/list_folder/get_latest_cursor', { path: apiPath, recursive: opts.recursive === true, })); return resp.cursor; } /** * Replay changes since `cursor`, following every continuation page. */ export async function continueFolder(tm, cursor) { const out = []; let resp = (await dropboxRpc(tm, '/files/list_folder/continue', { cursor, })); for (const item of resp.entries) out.push(item); while (resp.has_more) { resp = (await dropboxRpc(tm, '/files/list_folder/continue', { cursor: resp.cursor, })); for (const item of resp.entries) out.push(item); } return { entries: out, cursor: resp.cursor }; } export async function getMetadata(tm, path) { return (await dropboxRpc(tm, '/files/get_metadata', { path })); } export async function createFolder(tm, path) { await dropboxRpc(tm, '/files/create_folder_v2', { path, autorename: false }); } export async function deletePath(tm, path, parentRev = null) { await dropboxRpc(tm, '/files/delete_v2', parentRev === null ? { path } : { path, parent_rev: parentRev }); } /** * An entry's metadata, null when Dropbox has nothing at the path. Only a * not_found or not_folder 409 is a miss; any other refusal (restricted_content, * a relay's 5xx naming not_found) names a path that may exist, and throws. * Mirrors Python's `lookup`. */ export async function lookup(tm, path) { try { return await getMetadata(tm, path); } catch (err) { if (err instanceof DropboxApiError && err.status === 409 && MISS_SUMMARIES.some((s) => err.summary.startsWith(s))) { return null; } throw err; } } /** * The refusal a write sent with a rev met, null for any other failure or a * write that went plain. Mirrors Python's `refused`. */ export async function refused(path, err, cond, rev) { if (rev === null || rev === '' || !(err instanceof DropboxApiError)) return null; if (![...LOST_SUMMARIES, ...GONE_SUMMARIES].some((s) => err.summary.startsWith(s))) return null; return stale(path, { version: conditionVersion(cond) }); } /** * Move one entry with `/files/move_v2`. Answers the moved entry's metadata * (`.tag` names its kind), or an empty object when the reply carries none. */ export async function movePath(tm, from, to) { const reply = (await dropboxRpc(tm, '/files/move_v2', { from_path: from, to_path: to, autorename: false, })); const metadata = reply?.metadata; return metadata !== null && typeof metadata === 'object' && !Array.isArray(metadata) ? metadata : {}; } export async function copyPath(tm, from, to) { await dropboxRpc(tm, '/files/copy_v2', { from_path: from, to_path: to, autorename: false }); } export const SEARCH_PAGE = 1000; // search_v2 + search/continue_v2 serve at most 10,000 matches total; a // result set that hits the ceiling may be incomplete and must not be used // to narrow a scan. export const MAX_SEARCH_MATCHES = 10_000; /** * Collect `[path_lower, path_display]` file matches for a search query. * * Pages through `/files/search_v2` and `/files/search/continue_v2`, * deduplicating across pages (the API may repeat results between pages). * `path: ''` searches the whole account. `truncated` reports whether the * result hit the API's 10,000-match ceiling (a truncated set is not a * trustworthy superset). */ export async function searchFiles(tm, query, opts = {}) { const options = { max_results: SEARCH_PAGE, file_status: 'active', filename_only: opts.filenameOnly === true, }; if (opts.path !== undefined && opts.path !== '') options.path = opts.path; let resp = (await dropboxRpc(tm, '/files/search_v2', { query, options })); const seen = new Set(); const paths = []; for (;;) { for (const m of resp.matches ?? []) { const entry = m.metadata?.metadata; if (entry?.['.tag'] !== 'file') continue; const lower = entry.path_lower ?? ''; const display = entry.path_display ?? lower; if (lower === '' || seen.has(lower)) continue; seen.add(lower); paths.push([lower, display]); } if (paths.length >= MAX_SEARCH_MATCHES) return { paths, truncated: true }; if (resp.has_more !== true || resp.cursor === undefined) return { paths, truncated: false }; resp = (await dropboxRpc(tm, '/files/search/continue_v2', { cursor: resp.cursor, })); } } //# sourceMappingURL=api.js.map