UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

158 lines (157 loc) • 5.67 kB
import { c as extractArchive, n as ArchiveLimitError, t as ARCHIVE_LIMIT_ERROR_CODE } from "./archive-R2ClviiB.mjs"; import { n as textResult } from "./tool-results-BCM3fdVS.mjs"; import { f as saveMediaBuffer } from "./store-7jnF8fA5.mjs"; import "./archive-D3lhZQjN.mjs"; import "./media-store-BbouPwWH.mjs"; import { a as FILE_TRANSFER_SUBDIR, t as DIR_FETCH_TOOL_DESCRIPTOR } from "./descriptors-BNnHhU6g.mjs"; import { i as DIR_FETCH_HARD_MAX_BYTES, r as DIR_FETCH_DEFAULT_MAX_BYTES, t as DIR_FETCH_ARCHIVE_POLICY } from "./dir-fetch-archive-WQvCNXYk.mjs"; import { r as mimeFromExtension, t as IMAGE_MIME_INLINE_SET } from "./mime-YhVRnsMm.mjs"; import { i as readClampedInt, n as readRequiredNodePath, t as invokeNodeToolPayload } from "./node-tool-invoke-DAwwwonl.mjs"; import { t as renderDirectoryText } from "./directory-text-DECpN6xv.mjs"; import { t as decodeFetchPayload } from "./fetch-payload-mhJgiC4_.mjs"; import path from "node:path"; import fs from "node:fs/promises"; import { walkDirectory } from "@openclaw/fs-safe/walk"; import { sha256File } from "@openclaw/fs-safe/durability"; //#region extensions/file-transfer/src/tools/dir-fetch-tool.ts const MEDIA_URL_CAP = 25; const TAR_UNPACK_TIMEOUT_MS = 6e4; function classifyArchiveFailure(error) { const reason = error instanceof Error ? error.message : String(error); if (error instanceof ArchiveLimitError && error.code !== ARCHIVE_LIMIT_ERROR_CODE.ENTRY_COUNT_EXCEEDS_LIMIT) return { auditCode: "TREE_TOO_LARGE", publicCode: "UNCOMPRESSED_TOO_LARGE", reason }; return { auditCode: "UNSAFE_ARCHIVE", publicCode: "UNSAFE_ARCHIVE", reason }; } function savedDirectoryText(rootDir, files) { const header = JSON.stringify({ rootDir, fileCount: files.length }).slice(0, -1); return renderDirectoryText({ entries: files.toSorted((a, b) => a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0), project: ({ relPath, size }) => ({ relPath, size }), render: (visible) => { const manifest = `${header},"displayedCount":${visible.length},"files":[${visible.join(",")}]}`; const note = `${files.length - visible.length} saved files omitted from this text (byte limit or reserved path markers). All remain under rootDir; inspect them with available local file or directory capabilities.`; return { manifest, text: `Fetched ${files.length} files.\n${manifest}\n${note}` }; }, fallback: `Fetched ${files.length} files. Saved paths omitted: rootDir cannot be represented safely within the 8192-byte text limit. No usable local path is shown.` }); } function createDirFetchTool() { return { ...DIR_FETCH_TOOL_DESCRIPTOR, execute: async (_toolCallId, args) => { const params = args; const { node, requestedPath: dirPath } = readRequiredNodePath(params); const maxBytes = readClampedInt({ input: params, key: "maxBytes", defaultValue: DIR_FETCH_DEFAULT_MAX_BYTES, hardMax: DIR_FETCH_HARD_MAX_BYTES }); const { audit, payload } = await invokeNodeToolPayload({ node, params, command: "dir.fetch", commandParams: { path: dirPath, maxBytes }, requestedPath: dirPath }); const { canonicalPath, size: tarBytes, sha256, buffer: tarBuffer } = decodeFetchPayload("dir.fetch", payload); const savedTar = await saveMediaBuffer(tarBuffer, "application/gzip", FILE_TRANSFER_SUBDIR, DIR_FETCH_HARD_MAX_BYTES); const tarDir = path.dirname(savedTar.path); const unpackId = `dir-fetch-${path.basename(savedTar.path, path.extname(savedTar.path))}`; const rootDir = path.join(tarDir, unpackId); await fs.mkdir(rootDir, { recursive: true, mode: 448 }); try { await extractArchive({ archivePath: savedTar.path, destDir: rootDir, kind: "tar", tarGzip: true, timeoutMs: TAR_UNPACK_TIMEOUT_MS, entryModes: "clamp", ...DIR_FETCH_ARCHIVE_POLICY }); } catch (error) { await Promise.all([fs.rm(rootDir, { recursive: true, force: true }).catch(() => void 0), fs.rm(savedTar.path, { force: true }).catch(() => void 0)]); const failure = classifyArchiveFailure(error); await audit({ canonicalPath, decision: "error", errorCode: failure.auditCode, errorMessage: failure.reason, sizeBytes: tarBytes, sha256 }); throw new Error(`dir.fetch ${failure.publicCode}: ${failure.reason}`, { cause: error }); } const walked = await walkDirectory(rootDir, { symlinks: "skip", include: ({ kind }) => kind === "file" }); if (walked.failedDirs.length > 0) throw walked.failedDirs[0].error; const files = []; for (const { relativePath: relPath, path: absPath } of walked.entries) { let size; try { size = (await fs.stat(absPath)).size; } catch { continue; } const mimeType = mimeFromExtension(relPath); const fileSha256 = (await sha256File(absPath)).digest; files.push({ relPath, size, mimeType, sha256: fileSha256, localPath: absPath }); } const fileCount = files.length; const imageFiles = files.filter((f) => IMAGE_MIME_INLINE_SET.has(f.mimeType)); const nonImageFiles = files.filter((f) => !IMAGE_MIME_INLINE_SET.has(f.mimeType)); const mediaUrls = [...imageFiles, ...nonImageFiles].slice(0, MEDIA_URL_CAP).map((f) => f.localPath); await audit({ canonicalPath, decision: "allowed", sizeBytes: tarBytes, sha256 }); return textResult(savedDirectoryText(rootDir, files), { path: canonicalPath, rootDir, fileCount, tarBytes, sha256, files, media: { mediaUrls } }); } }; } //#endregion export { createDirFetchTool };