432 lines
34 KiB
JavaScript
432 lines
34 KiB
JavaScript
import * as path from 'path';
|
||
import { sync as rm } from '@polymech/fs/remove';
|
||
import { isString } from '@polymech/core/primitives';
|
||
import { sync as write } from '@polymech/fs/write';
|
||
import { sync as read } from '@polymech/fs/read';
|
||
import { sync as rename } from '@polymech/fs/rename';
|
||
import { sync as exists } from '@polymech/fs/exists';
|
||
import { sanitize } from "@polymech/fs/utils";
|
||
import { filesEx } from '@polymech/commons';
|
||
import { toolLogger } from '../../index.js';
|
||
import { EXCLUDE_GLOB } from '../../constants.js';
|
||
import { glob } from 'glob';
|
||
const isBase64 = (str) => {
|
||
// 1. Quick checks for length & allowed characters:
|
||
// - Must be multiple of 4 in length
|
||
// - Must match Base64 charset (A-Z, a-z, 0-9, +, /) plus optional "=" padding
|
||
if (!str || str.length % 4 !== 0) {
|
||
return false;
|
||
}
|
||
const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/;
|
||
if (!base64Regex.test(str)) {
|
||
return false;
|
||
}
|
||
// 2. Attempt decode–re-encode to confirm validity:
|
||
try {
|
||
const decoded = atob(str); // Decode from Base64
|
||
const reencoded = btoa(decoded); // Re-encode to Base64
|
||
// Compare the re-encoded string to original
|
||
return reencoded === str;
|
||
}
|
||
catch {
|
||
return false;
|
||
}
|
||
};
|
||
export const decode_base64 = (base64) => {
|
||
try {
|
||
if (!isBase64(base64)) {
|
||
return base64;
|
||
}
|
||
return Buffer.from(base64, 'base64').toString('utf-8');
|
||
}
|
||
catch (error) {
|
||
throw new Error('Failed to decode base64 string');
|
||
}
|
||
};
|
||
// Helper function for smart Base64 decoding
|
||
const decodeContentSmart = (content, logger, identifier) => {
|
||
if (!content || typeof content !== 'string') {
|
||
return content; // Return original content if null, undefined, or not a string
|
||
}
|
||
const lines = content.split(/\r?\n/);
|
||
const processedLines = lines.map(line => {
|
||
const trimmedLine = line.trim();
|
||
if (!trimmedLine) {
|
||
return ''; // Preserve empty lines between potential blocks but decode the blocks themselves
|
||
}
|
||
try {
|
||
// Attempt to decode Base64
|
||
const decodedLine = Buffer.from(trimmedLine, 'base64').toString('utf-8');
|
||
// Validate if it was actually Base64 by re-encoding
|
||
const reEncodedLine = Buffer.from(decodedLine, 'utf-8').toString('base64');
|
||
// Revised Validation Check:
|
||
// Compare original trimmed line with re-encoded line.
|
||
// Allow for potential padding differences by checking both exact match and no-pad match.
|
||
const originalNoPad = trimmedLine.replace(/={1,2}$/, '');
|
||
const reEncodedNoPad = reEncodedLine.replace(/={1,2}$/, '');
|
||
if (reEncodedLine === trimmedLine || reEncodedNoPad === originalNoPad) {
|
||
logger.debug(`Successfully decoded Base64 line for ${identifier}`);
|
||
return decodedLine;
|
||
}
|
||
// If validation fails, treat as plain text
|
||
logger.debug(`Re-encoding mismatch for ${identifier}. Original: '${trimmedLine}', Re-encoded: '${reEncodedLine}', using original trimmed line.`);
|
||
return trimmedLine;
|
||
}
|
||
catch (decodeError) {
|
||
// If decoding throws an error, assume it's plain text
|
||
// Use debug level as this is expected for non-base64 lines
|
||
logger.debug(`Base64 decoding failed for line in ${identifier}, assuming plain text. Line: ${trimmedLine}`);
|
||
return trimmedLine; // Return original trimmed line
|
||
}
|
||
});
|
||
// Join the processed lines back together
|
||
return processedLines.join('\n');
|
||
};
|
||
export const tools = (target, options) => {
|
||
const logger = toolLogger('fs', options);
|
||
return [
|
||
{
|
||
type: 'function',
|
||
function: {
|
||
name: 'list_files',
|
||
description: 'List all files in a directory',
|
||
parameters: {
|
||
type: 'object',
|
||
properties: {
|
||
directory: { type: 'string' },
|
||
pattern: { type: 'string', optional: true }
|
||
},
|
||
required: ['directory']
|
||
},
|
||
function: async (params) => {
|
||
try {
|
||
const directory = path.join(target, sanitize(params.directory));
|
||
if (!exists(directory)) {
|
||
logger.debug(`Tool::ListFiles Directory ${directory} does not exist`);
|
||
return [];
|
||
}
|
||
let pattern = params.pattern || '**/*';
|
||
logger.debug(`Tool::ListFiles Listing files in ${directory} with pattern ${pattern}`);
|
||
pattern = [
|
||
...EXCLUDE_GLOB,
|
||
pattern
|
||
];
|
||
const ret = await glob(pattern, {
|
||
cwd: directory,
|
||
absolute: false,
|
||
ignore: EXCLUDE_GLOB
|
||
});
|
||
return ret;
|
||
}
|
||
catch (error) {
|
||
logger.error('Error listing files', error);
|
||
throw error;
|
||
}
|
||
},
|
||
parse: JSON.parse
|
||
}
|
||
},
|
||
{
|
||
type: 'function',
|
||
function: {
|
||
name: 'read_files',
|
||
description: 'Reads files in a directory with a given pattern',
|
||
parameters: {
|
||
type: 'object',
|
||
properties: {
|
||
directory: { type: 'string' },
|
||
pattern: { type: 'string', optional: true }
|
||
},
|
||
required: ['directory']
|
||
},
|
||
function: async (params) => {
|
||
try {
|
||
const pattern = params.pattern || '**/*';
|
||
let entries = filesEx(target, pattern);
|
||
let ret = entries.map((entry) => {
|
||
try {
|
||
let content = read(entry);
|
||
return {
|
||
path: path.relative(target, entry).replace(/\\/g, '/'),
|
||
content: content.toString()
|
||
};
|
||
}
|
||
catch (error) {
|
||
logger.error(`Error reading file ${entry}:`, error);
|
||
return null;
|
||
}
|
||
});
|
||
ret = ret.filter((entry) => (entry !== null && entry.content));
|
||
logger.debug(`Tool::ReadFiles Reading files in ${target} with pattern ${pattern} : ${ret.length} files`, ret.map((entry) => entry.path));
|
||
return ret;
|
||
}
|
||
catch (error) {
|
||
logger.error('Error listing files', error);
|
||
throw error;
|
||
}
|
||
},
|
||
parse: JSON.parse
|
||
}
|
||
},
|
||
{
|
||
type: 'function',
|
||
function: {
|
||
name: 'remove_file',
|
||
description: 'Remove a file at given path',
|
||
parameters: {
|
||
type: 'object',
|
||
properties: {
|
||
path: { type: 'string' }
|
||
},
|
||
required: ['path']
|
||
},
|
||
function: async (params) => {
|
||
try {
|
||
const filePath = path.join(target, sanitize(params.path));
|
||
logger.debug(`Tool::RemoveFile Removing file ${filePath}`);
|
||
rm(filePath);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
logger.error('Error removing file', error);
|
||
throw error;
|
||
}
|
||
},
|
||
parse: JSON.parse
|
||
}
|
||
},
|
||
{
|
||
type: 'function',
|
||
function: {
|
||
name: 'rename_file',
|
||
description: 'Rename or move a file or directory',
|
||
parameters: {
|
||
type: 'object',
|
||
properties: {
|
||
src: { type: 'string' },
|
||
dst: { type: 'string' }
|
||
},
|
||
required: ['path']
|
||
},
|
||
function: async (params) => {
|
||
try {
|
||
const src = path.join(target, sanitize(params.src));
|
||
const dst = path.join(target, sanitize(params.dst));
|
||
logger.debug(`Tool::Rename file ${src} to ${dst}`);
|
||
rename(src, dst);
|
||
rm(src);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
logger.error('Error removing file', error);
|
||
throw error;
|
||
}
|
||
},
|
||
parse: JSON.parse
|
||
}
|
||
},
|
||
{
|
||
type: 'function',
|
||
function: {
|
||
name: "modify_project_files",
|
||
description: "Create or modify existing project files in one shot, preferably used for creating project structure)",
|
||
parameters: {
|
||
type: "object",
|
||
properties: {
|
||
files: {
|
||
type: "array",
|
||
items: {
|
||
type: "object",
|
||
properties: {
|
||
path: { type: "string" },
|
||
content: { type: "string", description: "new file content (Part of JSON payload)" }
|
||
},
|
||
required: ["path", "content"]
|
||
}
|
||
}
|
||
},
|
||
required: ["files"],
|
||
},
|
||
function: async (ret) => {
|
||
try {
|
||
if (!target) {
|
||
logger.error(`Tool::FS:modify_project_files : Root path required`);
|
||
return;
|
||
}
|
||
let { files } = ret;
|
||
if (isString(files)) {
|
||
try {
|
||
files = JSON.parse(files);
|
||
}
|
||
catch (error) {
|
||
logger.error(`Tool::modify_project_files : Structure Error parsing files`, error, ret);
|
||
// Consider writing the raw input for debugging if JSON parsing fails
|
||
// write(path.join(target, 'tools-output-error.json'), files)
|
||
return error.message;
|
||
}
|
||
}
|
||
for (const file of files) {
|
||
const sanitizedPath = sanitize(file.path);
|
||
const filePath = path.join(target, sanitizedPath);
|
||
logger.debug(`Tool:modify_project_files writing file ${filePath}`);
|
||
try {
|
||
// const contentToWrite = decodeContentSmart(file.content, logger, sanitizedPath);
|
||
try {
|
||
await write(filePath, file.content);
|
||
}
|
||
catch (writeError) {
|
||
logger.error(`Tool:modify_project_files Error writing file ${filePath}`, writeError);
|
||
}
|
||
}
|
||
catch (error) {
|
||
logger.error(`Tool:modify_project_files Error processing file content for ${filePath}`, error);
|
||
}
|
||
}
|
||
}
|
||
catch (error) {
|
||
logger.error(`Error creating project structure`, error);
|
||
}
|
||
},
|
||
parse: JSON.parse,
|
||
},
|
||
},
|
||
{
|
||
type: 'function',
|
||
function: {
|
||
name: "write_file",
|
||
description: "Writes to a file, given a path and content (Part of JSON payload). No directory or file exists check needed!",
|
||
parameters: {
|
||
type: "object",
|
||
properties: {
|
||
file: {
|
||
type: "object",
|
||
properties: {
|
||
path: { type: "string" },
|
||
content: { type: "string", description: "new file content (Part of JSON payload)" }
|
||
}
|
||
}
|
||
},
|
||
required: ["file"],
|
||
},
|
||
function: async (params) => {
|
||
let fileInfo;
|
||
try {
|
||
if (isString(params)) {
|
||
try {
|
||
params = JSON.parse(params);
|
||
}
|
||
catch (error) {
|
||
logger.error(`Tool::write_file : Structure Error parsing JSON`, error, params);
|
||
return error.message;
|
||
}
|
||
}
|
||
fileInfo = params.file; // Keep fileInfo accessible
|
||
if (!target || !fileInfo || !fileInfo.path || typeof fileInfo.content === 'undefined') {
|
||
logger.error(`Tool::write_file : Path/Target/Content are required`, fileInfo);
|
||
return false; // Indicate failure
|
||
}
|
||
const sanitizedPath = sanitize(fileInfo.path);
|
||
const filePath = path.join(target, sanitizedPath);
|
||
logger.debug(`Tool::write_file Writing file ${filePath}`);
|
||
try {
|
||
// Use the smart decoding helper function
|
||
// const contentToWrite = decodeContentSmart(fileInfo.content, logger, sanitizedPath);
|
||
await write(filePath, fileInfo.content);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
// Log error related to processing or writing the file
|
||
logger.error(`Tool:write_file Error processing or writing file ${sanitizedPath}`, error);
|
||
return false; // Indicate failure
|
||
}
|
||
}
|
||
catch (error) {
|
||
logger.error(`Tool:write_file Error writing file ${fileInfo?.path ? sanitize(fileInfo.path) : 'unknown'}`, error);
|
||
return false; // Indicate failure
|
||
}
|
||
},
|
||
parse: JSON.parse,
|
||
},
|
||
},
|
||
{
|
||
type: 'function',
|
||
function: {
|
||
name: "file_exists",
|
||
description: "check if a file or folder exists",
|
||
parameters: {
|
||
type: "object",
|
||
properties: {
|
||
file: {
|
||
type: "object",
|
||
properties: {
|
||
path: { type: "string" }
|
||
}
|
||
}
|
||
},
|
||
required: ["file"],
|
||
},
|
||
function: async (ret) => {
|
||
try {
|
||
if (isString(ret)) {
|
||
try {
|
||
ret = JSON.parse(ret);
|
||
}
|
||
catch (error) {
|
||
logger.error(`Tool::file_exists : Structure Error parsing files`, error, ret);
|
||
return error.message;
|
||
}
|
||
}
|
||
const { file } = ret;
|
||
if (!target || !file.path) {
|
||
logger.error(`Tool::file_exists : Path is required`, ret);
|
||
return;
|
||
}
|
||
const sanitizedPath = sanitize(file.path);
|
||
const filePath = path.join(target, sanitizedPath);
|
||
const res = exists(filePath);
|
||
logger.debug(`Tool::file_exists ${filePath} exists: ${res}`);
|
||
return res ? true : false;
|
||
}
|
||
catch (error) {
|
||
logger.error(`Tool:file_exists error`, error);
|
||
return false;
|
||
}
|
||
},
|
||
parse: JSON.parse,
|
||
},
|
||
},
|
||
{
|
||
type: 'function',
|
||
function: {
|
||
name: "read_file",
|
||
description: "read a file, at given a path",
|
||
parameters: {
|
||
type: "object",
|
||
properties: {
|
||
file: {
|
||
type: "object",
|
||
properties: {
|
||
path: { type: "string" }
|
||
}
|
||
}
|
||
},
|
||
required: ["file"],
|
||
},
|
||
function: async (ret) => {
|
||
try {
|
||
const { file } = ret;
|
||
const sanitizedPath = sanitize(file.path);
|
||
const filePath = path.join(target, sanitizedPath);
|
||
logger.debug(`Tool::ReadFile Reading file ${filePath}`);
|
||
return read(filePath, 'string');
|
||
}
|
||
catch (error) {
|
||
logger.error(`Error reading file`, error);
|
||
}
|
||
},
|
||
parse: JSON.parse
|
||
}
|
||
}
|
||
];
|
||
};
|
||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbGliL3Rvb2xzL2ZzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFBO0FBRTVCLE9BQU8sRUFBRSxJQUFJLElBQUksRUFBRSxFQUFFLE1BQU0scUJBQXFCLENBQUE7QUFDaEQsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLDJCQUEyQixDQUFBO0FBQ3BELE9BQU8sRUFBRSxJQUFJLElBQUksS0FBSyxFQUFFLE1BQU0sb0JBQW9CLENBQUE7QUFDbEQsT0FBTyxFQUFFLElBQUksSUFBSSxJQUFJLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUNoRCxPQUFPLEVBQUUsSUFBSSxJQUFJLE1BQU0sRUFBRSxNQUFNLHFCQUFxQixDQUFBO0FBQ3BELE9BQU8sRUFBRSxJQUFJLElBQUksTUFBTSxFQUFFLE1BQU0scUJBQXFCLENBQUE7QUFDcEQsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLG9CQUFvQixDQUFBO0FBQzdDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUUzQyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sZ0JBQWdCLENBQUE7QUFFM0MsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFBO0FBRWpELE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxNQUFNLENBQUE7QUFFM0IsTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFXLEVBQVcsRUFBRTtJQUN0QyxtREFBbUQ7SUFDbkQsdUNBQXVDO0lBQ3ZDLGlGQUFpRjtJQUNqRixJQUFJLENBQUMsR0FBRyxJQUFJLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxLQUFLLENBQUMsRUFBRTtRQUNoQyxPQUFPLEtBQUssQ0FBQztLQUNkO0lBRUQsTUFBTSxXQUFXLEdBQUcsd0JBQXdCLENBQUM7SUFDN0MsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJ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
|