UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

137 lines • 5.59 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 { z } from 'zod'; import { ByteSpool } from "../../io/spool.js"; import { apiRequest, apiStream, NO_RETRY } from "../api/client.js"; import { DatabricksVolumeCredentialError, errorOf } from "./errors.js"; /** The Files API takes a file in one PUT of at most 5 GiB. */ export const MAX_UPLOAD_SIZE = 5 * 1024 ** 3; const DirectoryEntry = z.object({ path: z.string(), is_directory: z.boolean().default(false), file_size: z.number().int().nonnegative().nullish(), last_modified: z.number().int().nullish(), }); const DirectoryPage = z.object({ contents: z.array(DirectoryEntry).default([]), next_page_token: z.string().nullish(), }); const RESPONSE = z.object({ headers: z.record(z.string(), z.string()) }); function url(accessor, endpoint, path) { const encoded = path .split('/') .map((part) => encodeURIComponent(part)) .join('/'); return `${accessor.host}/api/2.0/fs/${endpoint}${encoded}`; } async function headers(accessor) { const credential = accessor.config.token; let token; try { token = typeof credential === 'function' ? await credential() : credential; } catch (cause) { if (cause instanceof DOMException && cause.name === 'AbortError') throw cause; throw new DatabricksVolumeCredentialError(cause); } if (typeof token !== 'string' || token.trim() === '' || /[\r\n]/.test(token)) { throw new Error('databricks_volume: token must be a nonblank string without CR or LF'); } return { Authorization: `Bearer ${token}` }; } async function request(accessor, method, endpoint, path, options = {}) { return apiRequest(method, url(accessor, endpoint, path), { ...options, headers: { ...(await headers(accessor)), ...(options.body === undefined ? {} : { 'Content-Type': 'application/octet-stream' }), }, errorOf, retry: NO_RETRY, timeout: accessor.config.timeout, }); } export async function getMetadata(accessor, path) { const response = RESPONSE.parse(await request(accessor, 'HEAD', 'files', path, { read: 'response' })); const length = response.headers['content-length']; const parsed = length === undefined || length === '' ? null : Number(length); return { contentLength: parsed !== null && Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null, lastModified: response.headers['last-modified'] ?? null, }; } export async function getDirectoryMetadata(accessor, path) { await request(accessor, 'HEAD', 'directories', path, { read: 'none' }); } /** * A directory's entries, a page at a time; no page past the one that * brings `limit` entries. Mirrors Python's `list_directory_contents`. */ export async function listDirectoryContents(accessor, path, limit) { const entries = []; let token; do { const page = DirectoryPage.parse(await request(accessor, 'GET', 'directories', path, { params: token === undefined || token === null ? {} : { page_token: token }, })); entries.push(...page.contents.map((entry) => ({ path: entry.path, isDirectory: entry.is_directory, fileSize: entry.file_size ?? null, lastModified: entry.last_modified ?? null, }))); token = page.next_page_token; if (limit !== undefined && entries.length >= limit) break; } while (token !== undefined && token !== null && token !== ''); return entries; } export async function download(accessor, path, window) { return z.instanceof(Uint8Array).parse(await request(accessor, 'GET', 'files', path, { read: 'bytes', window, })); } export async function* downloadStream(accessor, path) { const requestHeaders = await headers(accessor); yield* apiStream(url(accessor, 'files', path), { headers: requestHeaders, errorOf, timeout: accessor.config.timeout, }); } /** * Put one file in a single PUT, at most `MAX_UPLOAD_SIZE` bytes; a spool goes * up as its fetch body, streamed from where it spilled. Mirrors Python's * `upload`. */ export async function upload(accessor, path, data, overwrite) { const body = data instanceof ByteSpool ? await data.body('fetch') : new Uint8Array(data); await request(accessor, 'PUT', 'files', path, { params: { overwrite }, body: body, read: 'none', }); } export async function createDirectory(accessor, path) { await request(accessor, 'PUT', 'directories', path, { read: 'none' }); } export async function deleteFile(accessor, path) { await request(accessor, 'DELETE', 'files', path, { read: 'none' }); } export async function deleteDirectory(accessor, path) { await request(accessor, 'DELETE', 'directories', path, { read: 'none' }); } //# sourceMappingURL=client.js.map