1 // Meta parser for `read_file` tool calls. Reads the file path and an
2 // optional line range (either `start_line`+`end_line` or
3 // `start_line`+`line_count`). Args are parsed partially so a header
4 // can render incrementally as the file path streams in.
6 import { BuiltInTool } from '$lib/enums';
9 FILE_PATH_SEPARATOR_REGEX,
10 TEXT_LANGUAGE_PREFIX_REGEX
11 } from '$lib/constants';
12 import { getFileTypeByExtension, type AgenticSection } from '$lib/utils';
13 import { parseToolArgs } from './_shared';
15 export type ReadFileMeta = {
17 lineRange: { start: number; end: number } | null;
21 export function parseReadFileMeta(section: AgenticSection): ReadFileMeta | null {
22 const args = parseToolArgs(BuiltInTool.READ_FILE, section, { partial: true });
23 if (!args) return null;
25 const rawPath = args.path ?? args.file_path ?? args.filePath;
26 if (typeof rawPath !== 'string' || !rawPath) return null;
28 const fileName = rawPath.split(FILE_PATH_SEPARATOR_REGEX).pop() || rawPath;
30 // Models emit range arguments under several aliases. Accept all to
31 // stay forgiving across prompt variations.
32 const startRaw = args.start_line ?? args.line_start ?? args.startLine ?? args.from_line;
33 const endRaw = args.end_line ?? args.line_end ?? args.endLine ?? args.to_line;
34 const countRaw = args.line_count ?? args.count ?? args.num_lines;
36 let lineRange: { start: number; end: number } | null = null;
37 const sNum = Number(startRaw);
38 const eNum = Number(endRaw);
39 if (startRaw != null && endRaw != null && Number.isFinite(sNum) && Number.isFinite(eNum)) {
40 lineRange = { start: sNum, end: eNum };
41 } else if (startRaw != null && countRaw != null) {
42 const cNum = Number(countRaw);
43 if (Number.isFinite(sNum) && Number.isFinite(cNum)) {
44 lineRange = { start: sNum, end: sNum + cNum - 1 };
48 const fileType = getFileTypeByExtension(fileName);
49 const language = fileType ? fileType.replace(TEXT_LANGUAGE_PREFIX_REGEX, '') : DEFAULT_LANGUAGE;
51 return { fileName, lineRange, language };