mono/packages/kbot/dist-in/source.js
2025-06-03 20:55:18 +02:00

183 lines
15 KiB
JavaScript

import * as path from 'node:path';
import * as fs from 'node:fs';
import { sync as dir } from '@polymech/fs/dir';
import { createItem as toNode } from '@polymech/fs/inspect';
import { sync as exists } from '@polymech/fs/exists';
import { isFile, forward_slash } from '@polymech/commons';
import { logger } from './index.js';
import { lookup } from 'mime-types';
import { globSync } from 'glob';
import { EXCLUDE_GLOB, MAX_FILE_SIZE } from './constants.js';
import { defaultMimeRegistry } from './mime-handlers.js';
import { supported } from './commands/run-assistant.js';
import { handleWebUrl } from './http.js';
/**
* @todos
* - add support for vector stores : https://platform.openai.com/docs/assistants/tools/file-search?lang=node.js
*/
export const default_filters = {
isFile,
exists,
size: (filePath) => toNode(filePath).size < MAX_FILE_SIZE,
};
const isPathInside = (childPath, parentPath) => {
const relation = path.relative(parentPath, childPath);
return Boolean(relation &&
!relation.startsWith('..') &&
!relation.startsWith('..' + path.sep));
};
export const isPathOutsideSafe = (pathA, pathB) => {
const realA = fs.realpathSync(pathA);
const realB = fs.realpathSync(pathB);
return !isPathInside(realA, realB);
};
export const base64 = (filePath) => {
try {
const fileBuffer = fs.readFileSync(filePath);
const mimeType = lookup(filePath);
if (!mimeType) {
throw new Error('Unable to determine MIME type.');
}
const base64Data = fileBuffer.toString('base64');
return `data:${mimeType};base64,${base64Data}`;
}
catch (error) {
logger.error('fileToBase64 : Error reading file:', error);
return null;
}
};
export const images = (files) => {
return files.map((f) => ({
type: "image_url",
image_url: { url: base64(f) }
}));
};
/**
* Check if a string is a web URL
*/
export const isWebUrl = (str) => {
return /^https?:\/\//.test(str);
};
export const glob = (projectPath, include = [], exclude = []) => {
if (!exists(projectPath)) {
dir(projectPath);
return { files: [], webUrls: new Set() };
}
const filters = new Set();
const absolutePaths = new Set();
const webUrls = new Set();
const ignorePatterns = new Set(EXCLUDE_GLOB);
include.forEach(pattern => {
// Check if the pattern is a web URL
if (isWebUrl(pattern)) {
webUrls.add(pattern);
return;
}
if (path.isAbsolute(pattern)) {
if (isPathInside(pattern, projectPath)) {
filters.add(pattern);
}
else {
absolutePaths.add(pattern);
}
}
else {
filters.add(pattern);
}
});
// Process exclude patterns
exclude.forEach(pattern => {
if (isWebUrl(pattern)) {
// Web URLs are typically not "excluded" in a file glob sense,
// but if there's a use case, it needs clarification.
// For now, we'll assume web URLs in exclude are ignored for globbing.
return;
}
// Add all exclude patterns (absolute or relative) to ignorePatterns
// globSync handles absolute paths correctly in its `ignore` option when `cwd` is set.
ignorePatterns.add(pattern);
});
const globFiles = globSync([...filters], {
cwd: projectPath,
absolute: false,
ignore: [...ignorePatterns]
});
const allFiles = Array.from(new Set([
...globFiles.map(file => path.join(projectPath, file)),
...Array.from(absolutePaths)
]));
let files = allFiles.filter((f) => Object.keys(default_filters).every((key) => default_filters[key](f)));
return { files, webUrls };
};
export async function get(projectPath, include = [], options) {
const { files, webUrls } = glob(projectPath, include, options.exclude);
// Process file contents
const fileResults = files.map((fullPath) => {
try {
const relativePath = forward_slash(path.relative(projectPath, fullPath));
if (isFile(fullPath) && exists(fullPath)) {
const mimeType = lookup(fullPath) || 'text/plain';
const handler = defaultMimeRegistry.getHandler(mimeType);
if (handler) {
return handler.handle(fullPath, relativePath);
}
return defaultMimeRegistry.getHandler('text/*')?.handle(fullPath, relativePath) || null;
}
return null;
}
catch (error) {
logger.error(`Error reading file ${fullPath}:`, error);
return null;
}
});
// Process web URLs
const webUrlPromises = Array.from(webUrls).map(async (url) => {
try {
return await handleWebUrl(url);
}
catch (error) {
logger.error(`Error processing web URL ${url}:`, error);
return null;
}
});
const webResults = await Promise.all(webUrlPromises);
// Combine and filter results
const results = [...fileResults, ...webResults].filter((r) => r !== null);
return results;
}
export async function vectorize(file, options) {
if (!options.client) {
throw new Error('OpenAI client is required for vectorization');
}
const ext = path.extname(file).toLowerCase();
if (!(ext in supported)) {
throw new Error(`Unsupported file format: ${ext}. Supported formats: ${Object.keys(supported).join(', ')}`);
}
try {
// Create a vector store
const vectorStore = await options.client.vectorStores.create({
name: path.basename(file)
});
// Upload file to vector store
const fileStream = fs.createReadStream(file);
await options.client.vectorStores.fileBatches.uploadAndPoll(vectorStore.id, {
files: [fileStream]
});
// Create meta file path by appending .meta.json to the original file path
const metaPath = `${file}.meta.json`;
const metaData = {
vectorStoreId: vectorStore.id,
vectorizedAt: new Date().toISOString(),
originalPath: file,
mimeType: supported[ext]
};
// Write meta data to file
fs.writeFileSync(metaPath, JSON.stringify(metaData, null, 2));
return vectorStore.id;
}
catch (error) {
logger.error(`Failed to vectorize file ${file}:`, error);
throw error;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic291cmNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3NvdXJjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssSUFBSSxNQUFNLFdBQVcsQ0FBQTtBQUNqQyxPQUFPLEtBQUssRUFBRSxNQUFNLFNBQVMsQ0FBQTtBQUU3QixPQUFPLEVBQUUsSUFBSSxJQUFJLEdBQUcsRUFBRSxNQUFNLGtCQUFrQixDQUFBO0FBRTlDLE9BQU8sRUFBRSxVQUFVLElBQUksTUFBTSxFQUFFLE1BQU0sc0JBQXNCLENBQUE7QUFDM0QsT0FBTyxFQUFFLElBQUksSUFBSSxNQUFNLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQTtBQUNwRCxPQUFPLEVBQUUsTUFBTSxFQUFFLGFBQWEsRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQ3pELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDbkMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLFlBQVksQ0FBQTtBQUNuQyxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sTUFBTSxDQUFBO0FBQy9CLE9BQU8sRUFBRSxZQUFZLEVBQUUsYUFBYSxFQUFFLE1BQU0sZ0JBQWdCLENBQUE7QUFDNUQsT0FBTyxFQUFFLG1CQUFtQixFQUFrQixNQUFNLG9CQUFvQixDQUFBO0FBR3hFLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQTtBQUN2RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sV0FBVyxDQUFBO0FBRXhDOzs7R0FHRztBQUVILE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRztJQUM3QixNQUFNO0lBQ04sTUFBTTtJQUNOLElBQUksRUFBRSxDQUFDLFFBQWdCLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQyxJQUFJLEdBQUcsYUFBYTtDQUNsRSxDQUFDO0FBRUYsTUFBTSxZQUFZLEdBQUcsQ0FBQyxTQUFpQixFQUFFLFVBQWtCLEVBQVcsRUFBRTtJ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