mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-31 06:12:26 +00:00
feat: apply_patch tool for openai models (#9127)
This commit is contained in:
@@ -177,8 +177,18 @@ export namespace Patch {
|
||||
return { content, nextIdx: i }
|
||||
}
|
||||
|
||||
function stripHeredoc(input: string): string {
|
||||
// Match heredoc patterns like: cat <<'EOF'\n...\nEOF or <<EOF\n...\nEOF
|
||||
const heredocMatch = input.match(/^(?:cat\s+)?<<['"]?(\w+)['"]?\s*\n([\s\S]*?)\n\1\s*$/)
|
||||
if (heredocMatch) {
|
||||
return heredocMatch[2]
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
export function parsePatch(patchText: string): { hunks: Hunk[] } {
|
||||
const lines = patchText.split("\n")
|
||||
const cleaned = stripHeredoc(patchText.trim())
|
||||
const lines = cleaned.split("\n")
|
||||
const hunks: Hunk[] = []
|
||||
let i = 0
|
||||
|
||||
@@ -363,7 +373,7 @@ export namespace Patch {
|
||||
// Try to match old lines in the file
|
||||
let pattern = chunk.old_lines
|
||||
let newSlice = chunk.new_lines
|
||||
let found = seekSequence(originalLines, pattern, lineIndex)
|
||||
let found = seekSequence(originalLines, pattern, lineIndex, chunk.is_end_of_file)
|
||||
|
||||
// Retry without trailing empty line if not found
|
||||
if (found === -1 && pattern.length > 0 && pattern[pattern.length - 1] === "") {
|
||||
@@ -371,7 +381,7 @@ export namespace Patch {
|
||||
if (newSlice.length > 0 && newSlice[newSlice.length - 1] === "") {
|
||||
newSlice = newSlice.slice(0, -1)
|
||||
}
|
||||
found = seekSequence(originalLines, pattern, lineIndex)
|
||||
found = seekSequence(originalLines, pattern, lineIndex, chunk.is_end_of_file)
|
||||
}
|
||||
|
||||
if (found !== -1) {
|
||||
@@ -407,28 +417,75 @@ export namespace Patch {
|
||||
return result
|
||||
}
|
||||
|
||||
function seekSequence(lines: string[], pattern: string[], startIndex: number): number {
|
||||
if (pattern.length === 0) return -1
|
||||
// Normalize Unicode punctuation to ASCII equivalents (like Rust's normalize_unicode)
|
||||
function normalizeUnicode(str: string): string {
|
||||
return str
|
||||
.replace(/[\u2018\u2019\u201A\u201B]/g, "'") // single quotes
|
||||
.replace(/[\u201C\u201D\u201E\u201F]/g, '"') // double quotes
|
||||
.replace(/[\u2010\u2011\u2012\u2013\u2014\u2015]/g, "-") // dashes
|
||||
.replace(/\u2026/g, "...") // ellipsis
|
||||
.replace(/\u00A0/g, " ") // non-breaking space
|
||||
}
|
||||
|
||||
// Simple substring search implementation
|
||||
type Comparator = (a: string, b: string) => boolean
|
||||
|
||||
function tryMatch(lines: string[], pattern: string[], startIndex: number, compare: Comparator, eof: boolean): number {
|
||||
// If EOF anchor, try matching from end of file first
|
||||
if (eof) {
|
||||
const fromEnd = lines.length - pattern.length
|
||||
if (fromEnd >= startIndex) {
|
||||
let matches = true
|
||||
for (let j = 0; j < pattern.length; j++) {
|
||||
if (!compare(lines[fromEnd + j], pattern[j])) {
|
||||
matches = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (matches) return fromEnd
|
||||
}
|
||||
}
|
||||
|
||||
// Forward search from startIndex
|
||||
for (let i = startIndex; i <= lines.length - pattern.length; i++) {
|
||||
let matches = true
|
||||
|
||||
for (let j = 0; j < pattern.length; j++) {
|
||||
if (lines[i + j] !== pattern[j]) {
|
||||
if (!compare(lines[i + j], pattern[j])) {
|
||||
matches = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (matches) {
|
||||
return i
|
||||
}
|
||||
if (matches) return i
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
function seekSequence(lines: string[], pattern: string[], startIndex: number, eof = false): number {
|
||||
if (pattern.length === 0) return -1
|
||||
|
||||
// Pass 1: exact match
|
||||
const exact = tryMatch(lines, pattern, startIndex, (a, b) => a === b, eof)
|
||||
if (exact !== -1) return exact
|
||||
|
||||
// Pass 2: rstrip (trim trailing whitespace)
|
||||
const rstrip = tryMatch(lines, pattern, startIndex, (a, b) => a.trimEnd() === b.trimEnd(), eof)
|
||||
if (rstrip !== -1) return rstrip
|
||||
|
||||
// Pass 3: trim (both ends)
|
||||
const trim = tryMatch(lines, pattern, startIndex, (a, b) => a.trim() === b.trim(), eof)
|
||||
if (trim !== -1) return trim
|
||||
|
||||
// Pass 4: normalized (Unicode punctuation to ASCII)
|
||||
const normalized = tryMatch(
|
||||
lines,
|
||||
pattern,
|
||||
startIndex,
|
||||
(a, b) => normalizeUnicode(a.trim()) === normalizeUnicode(b.trim()),
|
||||
eof,
|
||||
)
|
||||
return normalized
|
||||
}
|
||||
|
||||
function generateUnifiedDiff(oldContent: string, newContent: string): string {
|
||||
const oldLines = oldContent.split("\n")
|
||||
const newLines = newContent.split("\n")
|
||||
|
||||
Reference in New Issue
Block a user