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.

This commit is contained in:
sebseb7
2025-08-11 22:34:43 +02:00
parent 8d23ec53e1
commit 2de276761a
3 changed files with 104 additions and 16 deletions

11
cli.js
View File

@@ -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 = [];

View File

@@ -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.",}
},},};
export async function run() {
return `read_file error (not implemented)`
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(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}`;
}
}

View File

@@ -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}`;
}
}