Project Files
dist / toolsProvider.js
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.toolsProvider = toolsProvider;
const sdk_1 = require("@lmstudio/sdk");
const zod_1 = require("zod");
const fs_1 = require("fs");
const promises_1 = require("fs/promises");
const child_process_1 = require("child_process");
const path = __importStar(require("path"));
const config_1 = require("./config");
const utils_1 = require("./utils");
async function toolsProvider(ctl) {
const config = ctl.getPluginConfig(config_1.configSchematics);
const enableFileSystem = config.get("enableFileSystem");
const enableConsole = config.get("enableConsole");
const enableInternet = config.get("enableInternet");
const commandTimeoutSec = config.get("commandTimeoutSec");
const maxWebpageLength = config.get("maxWebpageLength");
const tools = [];
// ------------------------------------------------------------------
// 1. ФАЙЛОВАЯ СИСТЕМА
// ------------------------------------------------------------------
if (enableFileSystem) {
// --- list_files ---
tools.push((0, sdk_1.tool)({
name: "list_files",
description: `
<role>Вы — AI-агент, исследующий файловую систему.</role>
Используйте этот инструмент, чтобы получить список всех файлов (не папок) в базовой директории.
<usage_rules>
1. Возвращает массив имён файлов. Папки игнорируются.
2. Если базовая директория не существует или пуста, возвращается пустой массив.
3. Используйте этот инструмент перед чтением файлов, чтобы узнать, что доступно.
</usage_rules>
`,
parameters: {},
implementation: async (_, { signal }) => {
signal.throwIfAborted();
try {
const baseDir = config.get("baseDirectory")?.trim();
let rootDir;
if (baseDir) {
rootDir = path.resolve(baseDir);
}
else {
rootDir = ctl.getWorkingDirectory();
}
if (!(0, fs_1.existsSync)(rootDir)) {
return `❌ Базовая директория не существует: ${rootDir}`;
}
const entries = (0, fs_1.readdirSync)(rootDir, { withFileTypes: true });
const files = entries
.filter(entry => entry.isFile())
.map(entry => entry.name);
if (files.length === 0) {
return "В базовой директории нет файлов.";
}
return `📁 Файлы в директории (${files.length}):\n` + files.map(f => `- ${f}`).join("\n");
}
catch (error) {
return `❌ Ошибка при получении списка файлов: ${error.message}`;
}
},
}));
// --- create_file ---
tools.push((0, sdk_1.tool)({
name: "create_file",
description: `
<role>Вы — AI-агент, пишущий код и документы.</role>
Используйте этот инструмент для создания или перезаписи файлов в базовой директории.
<usage_rules>
1. Указывайте полное имя файла с расширением (например, 'script.ts').
2. Содержимое должно быть корректным текстом или кодом.
3. Если файл существует, он будет ПЕРЕЗАПИСАН.
4. Файл будет создан внутри базовой директории, заданной в настройках плагина.
5. Чтобы узнать, какие файлы уже существуют, сначала используйте list_files.
</usage_rules>
`,
parameters: {
file_name: zod_1.z.string().describe("Имя файла (например, 'main.ts')"),
content: zod_1.z.string().describe("Содержимое файла"),
},
implementation: async ({ file_name, content }, { status, signal }) => {
signal.throwIfAborted();
status(`Подготовка к созданию файла: ${file_name}`);
try {
const filePath = (0, utils_1.getSecureFilePath)(ctl, file_name);
const dir = path.dirname(filePath);
await (0, promises_1.mkdir)(dir, { recursive: true });
status(`Запись ${content.length} символов...`);
await (0, promises_1.writeFile)(filePath, content, "utf-8");
return `✅ Файл "${file_name}" успешно создан в ${path.dirname(filePath)}`;
}
catch (error) {
return `❌ Ошибка при создании файла: ${error.message}`;
}
},
}));
// --- read_file ---
tools.push((0, sdk_1.tool)({
name: "read_file",
description: `
<role>Вы — AI-агент, анализирующий существующий код и данные.</role>
Используйте этот инструмент для чтения содержимого файлов из базовой директории.
<usage_rules>
1. Указывайте точное имя файла (с расширением).
2. Не используйте для бинарных файлов.
3. Сначала вызовите list_files, чтобы узнать имена доступных файлов.
</usage_rules>
`,
parameters: {
file_name: zod_1.z.string().describe("Имя файла для чтения"),
},
implementation: async ({ file_name }, { signal }) => {
signal.throwIfAborted();
try {
const filePath = (0, utils_1.getSecureFilePath)(ctl, file_name);
if (!(0, fs_1.existsSync)(filePath)) {
return `❌ Файл "${file_name}" не найден в базовой директории. Используйте list_files, чтобы увидеть список доступных файлов.`;
}
const content = await (0, promises_1.readFile)(filePath, "utf-8");
return content;
}
catch (error) {
return `❌ Ошибка чтения файла: ${error.message}`;
}
},
}));
}
// ------------------------------------------------------------------
// 2. КОНСОЛЬНЫЕ КОМАНДЫ
// ------------------------------------------------------------------
if (enableConsole) {
tools.push((0, sdk_1.tool)({
name: "run_command",
description: `
<role>Вы — AI-агент с доступом к терминалу.</role>
Используйте этот инструмент для выполнения консольных команд в рабочей директории чата.
<usage_rules>
1. Команды выполняются в подоболочке (bash/sh или cmd.exe).
2. Максимальное время выполнения задано в настройках (по умолчанию 30 сек).
3. Не используйте интерактивные команды (vim, top).
4. Для получения списка файлов предпочтительнее использовать list_files.
</usage_rules>
`,
parameters: {
command: zod_1.z.string().describe("Команда для запуска (например, 'ls -la')"),
},
implementation: async ({ command }, { status, signal }) => {
signal.throwIfAborted();
status(`Запуск команды: ${command}`);
const maxBuffer = 100 * 1024; // 100 КБ
const execPromiseWithCancel = () => {
return new Promise((resolve, reject) => {
const child = (0, child_process_1.exec)(command, {
cwd: ctl.getWorkingDirectory(),
timeout: commandTimeoutSec * 1000,
maxBuffer,
}, (error, stdout, stderr) => {
if (error) {
reject(new Error(stderr || error.message));
}
else {
resolve({ stdout, stderr });
}
});
const abortHandler = () => {
child.kill("SIGTERM");
reject(new Error("Операция отменена пользователем."));
};
signal.addEventListener("abort", abortHandler, { once: true });
child.on("close", () => {
signal.removeEventListener("abort", abortHandler);
});
});
};
try {
const { stdout, stderr } = await execPromiseWithCancel();
status("Команда выполнена");
if (stderr && !stdout) {
return `⚠️ Команда выполнена с предупреждениями:\n${stderr}`;
}
return stdout.trim() || "Команда выполнена успешно (нет вывода).";
}
catch (error) {
return `❌ Ошибка выполнения команды: ${error.message}`;
}
},
}));
}
// ------------------------------------------------------------------
// 3. ИНТЕРНЕТ (fetch_webpage на базе Playwright)
// ------------------------------------------------------------------
if (enableInternet) {
tools.push((0, sdk_1.tool)({
name: "fetch_webpage",
description: `
<role>Вы — AI-агент с доступом к браузеру Playwright.</role>
Используйте этот инструмент для загрузки любой веб-страницы, включая поиск Google.
<usage_rules>
1. Указывайте полный URL (включая https://).
2. Для поиска в Google сформируйте URL самостоятельно: https://www.google.com/search?q=запрос (пробелы → +).
3. Возвращает заголовок, очищенный текст (до ${maxWebpageLength} символов), первые 20 ссылок и 20 изображений.
4. Страница загружается в настоящем браузере Chromium, поэтому работает даже с защищёнными сайтами.
</usage_rules>
`,
parameters: {
url: zod_1.z.string().url().describe("Полный URL-адрес для загрузки"),
},
implementation: async ({ url }, { status, signal }) => {
signal.throwIfAborted();
status(`Запуск браузера для ${url}...`);
let browser;
try {
const { chromium } = await Promise.resolve().then(() => __importStar(require('playwright')));
browser = await chromium.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
],
});
const page = await browser.newPage();
await page.setExtraHTTPHeaders({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
});
status(`Загрузка страницы...`);
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
// Ждём появления основного контента (для Google – результатов)
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { });
status(`Извлечение данных...`);
const title = await page.title();
// Извлекаем текст страницы
const bodyText = await page.evaluate(() => document.body.innerText || '');
let text = bodyText.replace(/\s+/g, ' ').trim();
if (text.length > maxWebpageLength) {
text = text.substring(0, maxWebpageLength) + '... (текст обрезан)';
}
// Извлекаем ссылки
const links = await page.$$eval('a[href]', (elements) => {
return elements.slice(0, 20).map(el => {
const a = el;
return a.href;
}).filter(href => href && !href.startsWith('javascript:'));
});
// Извлекаем изображения
const imageUrls = await page.$$eval('img[src]', (elements) => {
return elements.slice(0, 20).map(el => {
const img = el;
return img.src;
}).filter(src => src);
});
await browser.close();
let result = `🌐 **${title}**\nURL: ${url}\n\n`;
result += `📄 **Основной текст** (${text.length} символов):\n${text}\n\n`;
if (links.length > 0) {
result += `🔗 **Ссылки** (первые ${links.length}):\n${links.map(l => `- ${l}`).join('\n')}\n\n`;
}
if (imageUrls.length > 0) {
result += `🖼️ **Изображения** (первые ${imageUrls.length}):\n${imageUrls.map(i => `- ${i}`).join('\n')}`;
}
return result;
}
catch (error) {
if (browser)
await browser.close().catch(() => { });
if (error.name === 'AbortError' || signal.aborted) {
return '⏹️ Загрузка страницы отменена пользователем.';
}
return `❌ Ошибка при загрузке страницы: ${error.message}`;
}
},
}));
}
return tools;
}
//# sourceMappingURL=toolsProvider.js.map