'use strict'; const fs = require('fs'); const path = require('path'); const { getContainerContext, resolveContainerPath, execContainerAction } = require('./container_bridge'); function resolvePath(workspace, p) { if (path.isAbsolute(p)) return p; return path.join(workspace, p); } function readLines(content) { return content.split(/\r?\n/); } function applyMaxChars(text, maxChars) { if (!maxChars || text.length <= maxChars) return { text, truncated: false }; return { text: text.slice(0, maxChars), truncated: true }; } function readFileTool(workspace, args) { const target = resolvePath(workspace, args.path); const type = args.type || 'read'; const ctx = getContainerContext(); if (ctx) { const resolved = resolveContainerPath(workspace, args.path || '.', ctx); if (resolved.error) return { success: false, error: resolved.error }; const rel = resolved.relativePath; if (type === 'read') { const resp = execContainerAction(ctx, 'read_text_segment', { path: rel, start_line: args.start_line, end_line: args.end_line, }); if (!resp.success) return { success: false, error: resp.error || '读取失败' }; const capped = applyMaxChars(resp.content || '', args.max_chars); return { success: true, type: 'read', path: resolved.containerPath, line_start: resp.line_start, line_end: resp.line_end, content: capped.text, truncated: capped.truncated, }; } if (type === 'search') { const resp = execContainerAction(ctx, 'search_text', { path: rel, query: args.query, case_sensitive: !!args.case_sensitive, max_matches: args.max_matches || 20, context_before: args.context_before || 0, context_after: args.context_after || 0, }); if (!resp.success) return { success: false, error: resp.error || '搜索失败' }; const matches = (resp.matches || []).map((item, idx) => ({ id: `match_${idx + 1}`, line_start: item.line_start, line_end: item.line_end, hits: item.hits || [], snippet: item.snippet || '', })); return { success: true, type: 'search', path: resolved.containerPath, query: args.query || '', case_sensitive: !!args.case_sensitive, matches, }; } if (type === 'extract') { const resp = execContainerAction(ctx, 'extract_segments', { path: rel, segments: args.segments || [], }); if (!resp.success) return { success: false, error: resp.error || '提取失败' }; return { success: true, type: 'extract', path: resolved.containerPath, segments: resp.segments || [], }; } return { success: false, error: '未知 read_file type' }; } try { const raw = fs.readFileSync(target, 'utf8'); if (type === 'read') { const lines = readLines(raw); const start = Math.max(1, args.start_line || 1); const end = Math.min(lines.length, args.end_line || lines.length); const slice = lines.slice(start - 1, end).join('\n'); const capped = applyMaxChars(slice, args.max_chars); return { success: true, type: 'read', path: target, line_start: start, line_end: end, content: capped.text, truncated: capped.truncated, }; } if (type === 'search') { const lines = readLines(raw); const query = args.query || ''; const caseSensitive = !!args.case_sensitive; const maxMatches = args.max_matches || 20; const before = args.context_before || 0; const after = args.context_after || 0; const matches = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const hay = caseSensitive ? line : line.toLowerCase(); const needle = caseSensitive ? query : query.toLowerCase(); if (!needle) break; if (hay.includes(needle)) { const start = Math.max(0, i - before); const end = Math.min(lines.length - 1, i + after); const snippet = lines.slice(start, end + 1).join('\n'); matches.push({ id: `match_${matches.length + 1}`, line_start: start + 1, line_end: end + 1, hits: [i + 1], snippet, }); if (matches.length >= maxMatches) break; } } return { success: true, type: 'search', path: target, query, case_sensitive: caseSensitive, matches, }; } if (type === 'extract') { const lines = readLines(raw); const segments = Array.isArray(args.segments) ? args.segments : []; const extracted = segments.map((seg, idx) => { const start = Math.max(1, seg.start_line || 1); const end = Math.min(lines.length, seg.end_line || lines.length); return { label: seg.label || `segment_${idx + 1}`, line_start: start, line_end: end, content: lines.slice(start - 1, end).join('\n'), }; }); return { success: true, type: 'extract', path: target, segments: extracted, }; } return { success: false, error: '未知 read_file type' }; } catch (err) { return { success: false, error: err.message || String(err) }; } } module.exports = { readFileTool, resolvePath };