Skip to main content
Glama
by microsoft
filecache.ts4.29 kB
import { resolveBufferLikeAndExt } from "./bufferlike" import { hash } from "./crypto" import { TraceOptions } from "./trace" import { basename, dirname, join, relative } from "node:path" import { stat, writeFile } from "fs/promises" import { ensureDir } from "fs-extra" import { CancellationOptions, checkCancelled } from "./cancellation" import { dotGenaiscriptPath } from "./workdir" import { prettyBytes } from "./pretty" import debug from "debug" import { FILE_HASH_LENGTH, HTTPS_REGEX } from "./constants" import { tryStat } from "./fs" import { filenameOrFileToFilename } from "./unwrappers" import { genaiscriptDebug } from "./debug" const dbg = genaiscriptDebug("cache") /** * Caches a file by writing it to a specified directory. If the file exists, it simply returns the path. * * @param dir - The directory where the file will be cached. * @param bufferLike - The data to be written, can be a buffer-like object. * @param options - Optional configurations, including tracing options and cancellation options. * - cancellationToken - Token to support operation cancellation. * * @returns The path to the cached file. */ export async function fileWriteCached( dir: string, bufferLike: BufferLike, options?: TraceOptions & CancellationOptions & { /** * Generate file name extension */ ext?: string } ): Promise<string> { const { bytes, ext: sourceExt } = await resolveBufferLikeAndExt( bufferLike, options ) if (!bytes) { // file empty return undefined } const { cancellationToken, ext = sourceExt } = options || {} checkCancelled(cancellationToken) const filename = await hash(bytes, { length: FILE_HASH_LENGTH }) checkCancelled(cancellationToken) const f = filename + "." + ext.replace(/^\./, "") dbg(`cache: %s`, f) const fn = join(dir, f) const r = await tryStat(fn) if (r?.isFile()) { dbg(`hit %s`, fn) return fn } dbg(`miss %s`, fn) await ensureDir(dirname(fn)) await writeFile(fn, bytes) return fn } export async function fileWriteCachedJSON(dir: string, data: any) { const bytes = Buffer.from(JSON.stringify(data, null, 2)) const filename = await hash(bytes, { length: FILE_HASH_LENGTH }) const fn = join(dir, filename + ".json") const stat = await tryStat(fn) if (stat && stat.isFile()) return fn dbg(`json cache: ${fn} (${prettyBytes(bytes.length)})`) await ensureDir(dirname(fn)) await writeFile(fn, bytes) return fn } /** * Caches an image locally if it is not a URL. Returns the path to the cached file or the original URL. * * @param url - The source of the image. If it is a URL, it is returned as is. If it is a local file path, it will be cached. * @param options - Optional settings for tracing, cancellation, and output directory. * - dir: Custom directory to store the cached file. Defaults to a pre-defined image cache directory. * - trace: Trace option for debugging or logging purposes. * - cancellationToken: Token to handle operation cancellation. * * @returns The relative path to the cached file or the original URL if it is a remote target. */ export async function fileCacheImage( url: BufferLike, options?: TraceOptions & CancellationOptions & { dir?: string } ): Promise<string> { if (!url) return "" const filename = filenameOrFileToFilename(url as any) if (typeof filename === "string" && HTTPS_REGEX.test(filename)) return filename const { dir = dotGenaiscriptPath("images"), trace, cancellationToken, } = options || {} const fn = await fileWriteCached( dir, url, { trace, cancellationToken } // TODO: add trace ) if (!fn) { dbg(`no file cached`) return undefined } const res = options?.dir ? `./${basename(fn)}` : relative(process.cwd(), fn) dbg(`image: ${res}`) return res } export function patchCachedImages( text: string, patcher: (url: string) => string ) { const IMG_RX = /\!\[(?<alt>[^\]]*)\]\((?<url>\.genaiscript\/images\/[^)]+)\)/g return text.replace(IMG_RX, (_, alt, url) => `![${alt}](${patcher(url)})`) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/microsoft/genaiscript'

If you have feedback or need assistance with the MCP directory API, please join our Discord server