@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
137 lines • 5.59 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 { 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