From 2de276761a79abddbabe5b32cd93fbf782a2a0d2 Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Mon, 11 Aug 2025 22:34:43 +0200 Subject: [PATCH] Refactor read_file and ripgrep tools to implement file reading and searching functionalities. The read_file tool now validates paths within a restricted directory and handles line skipping and reading limits. The ripgrep tool is implemented to perform pattern searches with optional flags for line numbers and case sensitivity, enhancing search capabilities and error handling. --- cli.js | 11 ++++----- tools/read_file.js | 60 +++++++++++++++++++++++++++++++++++++++------- tools/ripgrep.js | 49 +++++++++++++++++++++++++++++++++++-- 3 files changed, 104 insertions(+), 16 deletions(-) diff --git a/cli.js b/cli.js index 57ede72..cbae79f 100644 --- a/cli.js +++ b/cli.js @@ -48,8 +48,7 @@ async function loadTools() { return Object.fromEntries(toolEntries); } -streamOnce(new OpenAI({ apiKey: 'csk-8jftdte6r6vf8fdvp9xkyek5t3jnc6jfhh93d3ewfcwxxvh9', baseURL: "https://api.cerebras.ai/v1"}), 'Erstelle eine React Project für eine Abovverwaltung. Mui, Sqllite, Express, Nodejs, KEIN Typescript, Aber ESM import. webpack, kein vite. HRM, nodemon'); -//streamOnce(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), 'Erstelle eine React Project für eine Abovverwaltung. Mui, Sqllite, Express, Nodejs, KEIN Typescript, Aber ESM import. webpack, kein vite. HRM, nodemon'); +streamOnce(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), 'Erstelle eine React Project für eine Abovverwaltung. Mui, Sqllite, Express, Nodejs, KEIN Typescript, Aber ESM import. webpack, kein vite. HRM, nodemon'); let counter = 0; @@ -65,7 +64,7 @@ async function streamOnce(openai, userText) { while(input.length > 0){ const call = { - model: 'gpt-oss-120b', + model: 'gpt-5-mini', input: input, text: { format: { type: 'text' }, verbosity: 'high' }, reasoning: { effort: 'medium', summary: 'detailed' }, @@ -83,7 +82,7 @@ async function streamOnce(openai, userText) { previousResponseId = event.response.id; }); stream.on('response.reasoning_summary_text.delta', (event) => { - ////process.stdout.write(event.delta); + process.stdout.write('o') }); stream.on('response.reasoning_summary_text.done', () => { process.stdout.write('\n'); @@ -91,7 +90,7 @@ async function streamOnce(openai, userText) { }); stream.on('response.output_text.delta', (event) => { - ////process.stdout.write(event.delta); + process.stdout.write('.') }); @@ -101,7 +100,7 @@ async function streamOnce(openai, userText) { } }); stream.on('response.function_call_arguments.delta', (event) => { - ////process.stdout.write(event.delta); + process.stdout.write('x'); }); const functionCalls = []; diff --git a/tools/read_file.js b/tools/read_file.js index 6f00425..5fbb7f1 100644 --- a/tools/read_file.js +++ b/tools/read_file.js @@ -1,14 +1,58 @@ +import { createReadStream } from "node:fs"; +import { createInterface } from "node:readline"; + +import path from "node:path"; + const virtual_chroot = '/home/seb/src/aiTools/tmp'; +// Ensures reads are confined to `virtual_chroot`. + export default { type: "function", name: "read_file", description: "read a file", strict: true, parameters: { - type: "object", required: ["path","linesToSkip","linesToRead"],additionalProperties: false, properties: { - path: { type: "string", description: "The path to the file to read.",}, - linesToSkip: { type: "number", description: "The number of lines to skip. Use 0 to read from the beginning.",}, - linesToRead: { type: "number",description: "1-400 The number of lines to read. Use 0 or more than 400 to read 400 lines.",} -},},}; + type: "object", required: ["path","linesToSkip","linesToRead"], additionalProperties: false, properties: { + path: { type: "string", description: "The path to the file to read.", }, + linesToSkip: { type: "integer", description: "The number of lines to skip. Use 0 to read from the beginning.", minimum: 0 }, + linesToRead: { type: "integer", description: "1-400 The number of lines to read. Use 0 or more than 400 to read 400 lines.", minimum: 0 } + } + } +}; -export async function run() { - return `read_file error (not implemented)` -} +export async function run(args) { + const { path: filePath, linesToSkip, linesToRead } = args; + + // Validate path is within virtual_chroot + const fullPath = path.resolve(virtual_chroot, filePath.replace(/^\//, '')); + if (!fullPath.startsWith(virtual_chroot)) { + return `read_file error: Path outside of allowed directory`; + } + + // Normalize linesToRead (1-400, with 0 or >400 meaning 400) + const maxLines = (linesToRead <= 0 || linesToRead > 400) ? 400 : linesToRead; + + try { + + const rl = createInterface({ + input: createReadStream(fullPath), + crlfDelay: Infinity + }); + + let lines = []; + let skipped = 0; + + for await (const line of rl) { + if (skipped < linesToSkip) { + skipped++; + continue; + } + lines.push(line); + if (lines.length >= maxLines) { + break; + } + } + + return lines.join('\n'); + } catch (error) { + return `read_file error: ${error.message}`; + } +} \ No newline at end of file diff --git a/tools/ripgrep.js b/tools/ripgrep.js index 62e2869..cfa107e 100644 --- a/tools/ripgrep.js +++ b/tools/ripgrep.js @@ -9,8 +9,53 @@ export default { filePattern: { type: "string", description: "'*.js' for only js files, '!*.log' for all files except log files , '' for all files"}, n_flag: { type: "boolean",description: "show line numbers."}, i_flag: { type: "boolean",description: "case insensitive search."} -},},}; + } + } +}; export async function run(args) { - return `ripgrep error (not implemented)` + const { pattern, filePattern, n_flag, i_flag } = args; + + // Build the args array + let rgArgs = []; + + if (n_flag) rgArgs.push('-n'); + if (i_flag) rgArgs.push('-i'); + if (filePattern) { + rgArgs.push('-g', filePattern); + } + + // Add separator and pattern + rgArgs.push('--', pattern); + + try { + const { spawnSync } = require('child_process'); + const proc = spawnSync('rg', rgArgs, { + cwd: virtual_chroot, + encoding: 'utf8', + maxBuffer: 1024 * 1024 * 10 // 10MB buffer + }); + + if (proc.error) { + return `ripgrep error: ${proc.error.message}`; + } + + let output = proc.stdout; + + if (proc.status !== 0) { + if (proc.status === 1) { + // No matches found + return output || ''; + } + return `ripgrep error: exit ${proc.status}, ${proc.stderr}`; + } + + // Limit to 200 lines + const lines = output.split('\n'); + const limitedOutput = lines.slice(0, 200).join('\n'); + + return limitedOutput; + } catch (error) { + return `ripgrep error: ${error.message}`; + } } \ No newline at end of file