import { parsePatchFiles } from '@pierre/diffs'; export type DiffPatchEntry = { id: string; title: string; filePath?: string; patch: string; renderMode: 'diff' | 'text'; }; const APPLY_PATCH_ENVELOPE_PATTERN = /^\*\*\*\s+(?:Begin Patch|End Patch|Add File:|Update File:|Delete File:|Move to:)/m; const HUNK_HEADER_PATTERN = /^@@\s+-\d+(?:,\d+)?\s+\+\d+(?:,\d+)?\s+@@/m; const GIT_DIFF_FILE_BREAK_PATTERN = /(?=^diff --git\s+)/gm; const GIT_DIFF_FILE_BREAK_TEST = /^diff --git\s+/m; const UNIFIED_DIFF_FILE_BREAK_PATTERN = /(?=^---\s+\S)/gm; const UNIFIED_DIFF_FILE_BREAK_TEST = /^---\s+\S/m; const isRecord = (value: unknown): value is Record => { return typeof value === 'object' && value !== null; }; const normalizePatchText = (patch: string): string => { return patch.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim(); }; const normalizeBareUnifiedHeaderLines = (patch: string): string => { let reachedHunk = false; return patch.split('\n').map((line) => { if (line.startsWith('@@')) { reachedHunk = true; return line; } if (reachedHunk) { return line; } const headerMatch = line.match(/^\s*(---|\+\+\+)\s+(.+)$/); if (!headerMatch) { return line; } const marker = headerMatch[1]; const rawPath = headerMatch[2] ?? ''; if (rawPath.trim() === '/dev/null') { return `${marker} /dev/null`; } return `${marker} ${rawPath.replace(/\\/g, '/')}`; }).join('\n'); }; const normalizeLooseUnifiedHunkBody = (patch: string): string => { let inHunk = false; return patch.split('\n').map((line) => { if (line.startsWith('@@')) { inHunk = true; return line; } if (!inHunk || line.startsWith('--- ') || line.startsWith('+++ ') || line.startsWith('diff --git')) { return line; } if (line.length === 0) { return ' '; } const first = line[0]; if (first === ' ' || first === '+' || first === '-' || first === '\\') { return line; } return ` ${line}`; }).join('\n'); }; const formatHunkRange = (start: string, count: number): string => { return count === 1 ? start : `${start},${count}`; }; const recountUnifiedHunkHeaders = (patch: string): string => { const lines = patch.split('\n'); const result = [...lines]; for (let index = 0; index < lines.length; index += 1) { const header = lines[index] ?? ''; const match = header.match(/^@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@(.*)$/); if (!match) { continue; } let oldCount = 0; let newCount = 0; for (let bodyIndex = index + 1; bodyIndex < lines.length; bodyIndex += 1) { const line = lines[bodyIndex] ?? ''; if (line.startsWith('@@') || line.startsWith('--- ') || line.startsWith('diff --git')) { break; } if (line.startsWith('\\')) { continue; } if (line.startsWith('+')) { newCount += 1; continue; } if (line.startsWith('-')) { oldCount += 1; continue; } oldCount += 1; newCount += 1; } result[index] = `@@ -${formatHunkRange(match[1] ?? '0', oldCount)} +${formatHunkRange(match[2] ?? '0', newCount)} @@${match[3] ?? ''}`; } return result.join('\n'); }; const normalizeLooseUnifiedPatch = (patch: string): string => { return recountUnifiedHunkHeaders(normalizeLooseUnifiedHunkBody(normalizeBareUnifiedHeaderLines(normalizePatchText(patch)))); }; export const getPatchText = (value: unknown): string | undefined => { if (typeof value === 'string') { return /\S/.test(value) ? value : undefined; } if (isRecord(value)) { const patch = value.patch; if (typeof patch === 'string') { return /\S/.test(patch) ? patch : undefined; } } return undefined; }; export const getApplyPatchFilePath = (file: unknown): string | null => { if (!isRecord(file)) { return null; } return typeof file.movePath === 'string' ? file.movePath : typeof file.filePath === 'string' ? file.filePath : typeof file.relativePath === 'string' ? file.relativePath : null; }; export const getPrimaryToolPath = ( toolName: string, input: Record | undefined, metadata: Record | undefined, ): string | null => { if (toolName === 'apply_patch') { const files = Array.isArray(metadata?.files) ? metadata.files : []; for (const file of files) { if (isRecord(file) && file.type !== 'delete') { const filePath = getApplyPatchFilePath(file); if (filePath) { return filePath; } } } return null; } if (toolName === 'edit' || toolName === 'multiedit') { const fileDiff = isRecord(metadata?.filediff) ? metadata.filediff : undefined; if (fileDiff && typeof fileDiff.file === 'string') { return fileDiff.file; } return typeof input?.filePath === 'string' ? input.filePath : typeof input?.file_path === 'string' ? input.file_path : typeof input?.path === 'string' ? input.path : null; } if (toolName === 'write') { return typeof input?.filePath === 'string' ? input.filePath : typeof input?.file_path === 'string' ? input.file_path : typeof input?.path === 'string' ? input.path : null; } return null; }; const supportsDiffMetadata = (toolName: string): boolean => ( toolName === 'edit' || toolName === 'multiedit' || toolName === 'apply_patch' ); const getMetadataFileForPath = ( metadata: Record, preferredPath?: string, ): Record | undefined => { const files = Array.isArray(metadata.files) ? metadata.files : []; if (!preferredPath) { const first = files[0]; return isRecord(first) ? first : undefined; } return files.find((file): file is Record => ( isRecord(file) && (file.relativePath === preferredPath || file.filePath === preferredPath || file.movePath === preferredPath) )); }; export const getPrimaryDiffFromMetadata = ( toolName: string, metadata?: Record, preferredPath?: string, ): string | undefined => { if (!metadata || !supportsDiffMetadata(toolName)) { return undefined; } const matchedFile = getMetadataFileForPath(metadata, preferredPath); const filePatch = getPatchText(matchedFile?.patch) ?? getPatchText(matchedFile?.diff); if (filePatch) { return filePatch; } return getPatchText(metadata.patch) ?? getPatchText(metadata.diff); }; export const extractFirstChangedLineFromDiff = (diffText: string): number | undefined => { if (!diffText) { return undefined; } let currentNewLine: number | undefined; let firstHunkStart: number | undefined; for (const rawLine of diffText.split('\n')) { const line = rawLine.replace(/\r$/, ''); const hunkMatch = line.match(/^@@\s+-\d+(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/); if (hunkMatch) { const parsed = Number.parseInt(hunkMatch[1] ?? '', 10); if (Number.isFinite(parsed)) { currentNewLine = Math.max(1, parsed); firstHunkStart ??= currentNewLine; } continue; } if (currentNewLine === undefined) { continue; } if (line.startsWith('+++') || line.startsWith('---') || line.startsWith('diff ')) { continue; } if (line.startsWith('+')) { return currentNewLine; } if (line.startsWith(' ')) { currentNewLine += 1; } } return firstHunkStart; }; export const getFirstChangedLineFromMetadata = ( toolName: string, metadata?: Record, preferredPath?: string, ): number | undefined => { if (!metadata || !supportsDiffMetadata(toolName)) { return undefined; } if (preferredPath) { const matchedFile = getMetadataFileForPath(metadata, preferredPath); const matchedPatch = getPatchText(matchedFile?.patch) ?? getPatchText(matchedFile?.diff); if (matchedPatch) { const matchedLine = extractFirstChangedLineFromDiff(matchedPatch); if (matchedLine !== undefined) { return matchedLine; } } } const topLevelPatch = getPatchText(metadata.patch) ?? getPatchText(metadata.diff); if (topLevelPatch) { const topLevelLine = extractFirstChangedLineFromDiff(topLevelPatch); if (topLevelLine !== undefined) { return topLevelLine; } } const firstFile = getMetadataFileForPath(metadata); const firstPatch = getPatchText(firstFile?.patch) ?? getPatchText(firstFile?.diff); return firstPatch ? extractFirstChangedLineFromDiff(firstPatch) : undefined; }; const normalizeParsedPath = (path: string | undefined): string => { const trimmed = (path ?? '').trim().replace(/\t.*$/, ''); if (!trimmed || trimmed === '/dev/null') { return ''; } return trimmed.replace(/^[ab]\//, ''); }; const makeSyntheticPath = (title: string): string => { const normalized = title.trim().replace(/\s+/g, '-'); return normalized.length > 0 ? normalized : 'file'; }; const hasOnlyUnifiedDiffBodyLines = (patch: string): boolean => { let inHunk = false; for (const line of patch.split('\n')) { if (line.startsWith('@@')) { if (!HUNK_HEADER_PATTERN.test(line)) { return false; } inHunk = true; continue; } if (!inHunk || line.length === 0) { continue; } const first = line[0]; if (first !== ' ' && first !== '+' && first !== '-' && first !== '\\') { return false; } } return true; }; export const getRenderablePatchInfo = (patch: string): { patch: string; title?: string } | null => { const normalized = normalizeLooseUnifiedPatch(patch); if ( !normalized || APPLY_PATCH_ENVELOPE_PATTERN.test(normalized) || !HUNK_HEADER_PATTERN.test(normalized) || !hasOnlyUnifiedDiffBodyLines(normalized) ) { return null; } try { const parsedPatches = parsePatchFiles(normalized, undefined, true); if (parsedPatches.length !== 1) { return null; } const files = parsedPatches[0]?.files ?? []; const file = files[0]; if (files.length !== 1 || !file || file.hunks.length === 0) { return null; } return { patch: normalized, title: normalizeParsedPath(file.name), }; } catch { return null; } }; const getPatchChunks = (patch: string): string[] => { const isGitDiff = GIT_DIFF_FILE_BREAK_TEST.test(patch); const hasUnifiedDiff = UNIFIED_DIFF_FILE_BREAK_TEST.test(patch); if (!isGitDiff && !hasUnifiedDiff) { return []; } return patch .split(isGitDiff ? GIT_DIFF_FILE_BREAK_PATTERN : UNIFIED_DIFF_FILE_BREAK_PATTERN) .map((chunk) => chunk.trim()) .filter((chunk) => chunk.length > 0); }; const getPatchEntriesFromText = ( patch: string, fallbackTitle: string, idPrefix: string, resolveTitle: (path: string) => string, ): DiffPatchEntry[] => { const normalized = normalizeLooseUnifiedPatch(patch); if (!normalized) { return []; } const direct = getRenderablePatchInfo(normalized); if (direct) { const title = direct.title ? resolveTitle(direct.title) : resolveTitle(fallbackTitle); return [{ id: `${idPrefix}-0`, title, patch: direct.patch, renderMode: 'diff' }]; } const chunkEntries: DiffPatchEntry[] = []; for (const chunk of getPatchChunks(normalized)) { const info = getRenderablePatchInfo(chunk); const title = info?.title ? resolveTitle(info.title) : resolveTitle(fallbackTitle); if (!info) { if (HUNK_HEADER_PATTERN.test(chunk) || GIT_DIFF_FILE_BREAK_TEST.test(chunk) || UNIFIED_DIFF_FILE_BREAK_TEST.test(chunk)) { chunkEntries.push({ id: `${idPrefix}-${chunkEntries.length}`, title, patch: chunk, renderMode: 'text', }); } continue; } chunkEntries.push({ id: `${idPrefix}-${chunkEntries.length}`, title, patch: info.patch, renderMode: 'diff', }); } if (chunkEntries.length > 0) { return chunkEntries; } if (!APPLY_PATCH_ENVELOPE_PATTERN.test(normalized) && HUNK_HEADER_PATTERN.test(normalized)) { const syntheticPath = makeSyntheticPath(fallbackTitle); const synthetic = getRenderablePatchInfo(`--- ${syntheticPath}\n+++ ${syntheticPath}\n${normalized}`); if (synthetic) { return [{ id: `${idPrefix}-0`, title: resolveTitle(fallbackTitle), patch: synthetic.patch, renderMode: 'diff', }]; } } return [{ id: `${idPrefix}-0`, title: resolveTitle(fallbackTitle), patch: normalized, renderMode: 'text', }]; }; const getFilePatch = (file: unknown): { filePath?: string; patch: string; title: string } | null => { if (!isRecord(file)) { return null; } const patch = getPatchText(file.patch) ?? getPatchText(file.diff); if (!patch) { return null; } const rawPath = typeof file.relativePath === 'string' ? file.relativePath : typeof file.filePath === 'string' ? file.filePath : ''; return { filePath: getApplyPatchFilePath(file) ?? undefined, patch, title: rawPath, }; }; export const getDiffPatchEntries = ( metadata: Record | undefined, fallbackDiff: string | undefined, resolveTitle: (path: string) => string, ): DiffPatchEntry[] => { const files = Array.isArray(metadata?.files) ? metadata.files : []; const fileEntries = files.flatMap((file, index) => { const filePatch = getFilePatch(file); if (!filePatch) { return []; } return getPatchEntriesFromText( filePatch.patch, filePatch.title || `File ${index + 1}`, `file-${index}`, resolveTitle, ).map((entry) => ({ ...entry, filePath: filePatch.filePath })); }); if (fileEntries.length > 0) { return fileEntries; } const diff = typeof fallbackDiff === 'string' ? fallbackDiff : ''; return getPatchEntriesFromText(diff, 'Diff', 'fallback', resolveTitle); };