UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

131 lines • 5.61 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 { GitHubAccessor, GitHubAccount } from "../../accessor/github.js"; import { fetchRepoInfo as fetchGitHubRepoInfo } from "../../core/github/client.js"; import { GitHubConfig } from "../../core/github/config.js"; import { COMMIT_SHA, WALK_LIMIT } from "../../core/github/constants.js"; import { readMany as githubReadMany, readStream as githubStream } from "../../core/github/read.js"; import { readdir as githubReaddir } from "../../core/github/readdir.js"; import { filesContaining as githubFilesContaining } from "../../core/github/search.js"; import { stat as githubStat, tree as githubTree } from "../../core/github/stat.js"; import { fetchTree as fetchGitHubTree, buildTreeMap as githubBuildTreeMap, } from "../../core/github/tree.js"; import {} from "../../io/types.js"; import { ListingVersion, VFSName } from "../../types.js"; import { BaseVFS } from "../base.js"; /** * The commit a ref pins every listing at, when it names one outright. Only a * full 40- or 64-hex string can be a commit; GitHub answers shas lowercase, * so the pin is lowercased to compare with what it stores. Listings are * stored at the head their tree answered, so a ref is served unchecked only * when its listing was fetched at that sha. github.com refuses a branch or * tag named with 40 or 64 hex characters (HTTP 422), so such a ref always * names a commit, which cannot move; a GitHub Enterprise host behind * `baseUrl` is assumed to refuse them too. */ function pinOf(ref) { if (ref === undefined) return null; const lowered = ref.toLowerCase(); return COMMIT_SHA.test(lowered) ? lowered : null; } export class GitHubVFS extends BaseVFS { name = VFSName.GITHUB; cachesReads = true; // The git tree API reports the exact blob size for every file; the // blob read returns those same bytes, and submodule gitlinks (which // have no size and no blob) are excluded from the tree. sizesAlwaysKnown = true; // stat and a read both stamp the content-addressed blob sha. supportsSnapshot = true; readRevalidatable = true; // One version covers every listing: the head commit the ref resolves to, // which the tree response names as its top-level sha and the root stat // answers with one shallow request. listingVersion = ListingVersion.MOUNT; listingsPin; indexTtl = 86_400; wholeWordSearch = true; config; accessor; walkLimit = WALK_LIMIT; constructor(config, accessor) { super(); this.config = config; this.accessor = accessor; this.listingsPin = pinOf(config.ref); } static async create(config) { const account = new GitHubAccount(config.baseUrl === undefined ? { token: config.token } : { token: config.token, baseUrl: config.baseUrl }); const repoInfo = await fetchGitHubRepoInfo(account.transport, config.owner, config.repo); const ref = config.ref ?? repoInfo.default_branch; const { tree, truncated } = await fetchGitHubTree(account.transport, config.owner, config.repo, ref); const treeMap = githubBuildTreeMap(tree); const accessor = new GitHubAccessor({ account, owner: config.owner, repo: config.repo, ref, defaultBranch: repoInfo.default_branch, truncated, tree: treeMap, }); return new GitHubVFS(config, accessor); } handed = { readdir: ['entry'], read: ['entry'], readMany: ['entry'], stat: ['entry', 'index'], }; readdir(path, handed) { return githubReaddir(this.accessor, path, handed?.entry); } // eslint-disable-next-line @typescript-eslint/require-await async read(path, handed) { return githubStream(this.accessor, path, handed?.entry); } readMany(paths, limit, handed) { return githubReadMany(this.accessor, paths, limit, handed); } stat(path, handed) { return githubStat(this.accessor, path, handed?.entry, handed?.index); } readTree(path) { return githubTree(this.accessor, path); } /** * Whether this VFS answers `name`; with no walk limit it reads every * file rather than ask the service's search. */ supports(name) { if (name === 'files_containing' && this.walkLimit < 0) return false; return super.supports(name); } filesContaining(path, text, opts) { if (!opts.wholeWord || this.walkLimit < 0) return Promise.resolve(null); return githubFilesContaining(this.accessor, text, [path]); } getState() { return Promise.resolve({ ...this.configState(GitHubConfig, this.config), defaultBranch: this.accessor.defaultBranch, truncated: this.accessor.truncated, }); } } //# sourceMappingURL=github.js.map