patchling 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/package.json +62 -0
- package/src/applydiff.d.ts +39 -0
- package/src/applydiff.js +404 -0
- package/src/env.d.ts +16 -0
- package/src/env.js +40 -0
- package/src/gptdiff.d.ts +161 -0
- package/src/gptdiff.js +352 -0
- package/src/index.d.ts +6 -0
- package/src/index.js +13 -0
- package/src/llm.d.ts +60 -0
- package/src/llm.js +296 -0
- package/src/oauth.d.ts +111 -0
- package/src/oauth.js +266 -0
- package/src/text.d.ts +69 -0
- package/src/text.js +218 -0
package/src/text.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure text utilities ported from gptdiff's Python implementation.
|
|
3
|
+
*
|
|
4
|
+
* These have no I/O and run identically in the browser and Node.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Split a string into lines while keeping the line-ending characters attached,
|
|
8
|
+
* mirroring Python's `str.splitlines(keepends=True)` for `\n`, `\r`, `\r\n`.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} s
|
|
11
|
+
* @returns {string[]}
|
|
12
|
+
*/
|
|
13
|
+
export function splitLinesKeepEnds(s: string): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Split a string into lines WITHOUT keeping line endings, mirroring Python's
|
|
16
|
+
* `str.splitlines()` — notably, a single trailing newline does not produce a
|
|
17
|
+
* trailing empty element.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} s
|
|
20
|
+
* @returns {string[]}
|
|
21
|
+
*/
|
|
22
|
+
export function splitLines(s: string): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Color-code a diff for terminal output: lines starting with '-' in red and
|
|
25
|
+
* lines starting with '+' in green. Other lines are untouched.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} diffText
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
export function colorCodeDiff(diffText: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Extract and remove the chain-of-thought reasoning block from an LLM response.
|
|
33
|
+
*
|
|
34
|
+
* The reasoning block begins with a line containing "> Reasoning" and ends with
|
|
35
|
+
* a "Reasoned ... seconds" marker. Multiple blocks are supported.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} fullResponse
|
|
38
|
+
* @returns {[string, string]} tuple of [finalContent, reasoning]
|
|
39
|
+
*/
|
|
40
|
+
export function swallowReasoning(fullResponse: string): [string, string];
|
|
41
|
+
/**
|
|
42
|
+
* If the LLM wrapped the file content in a Markdown code fence, unwrap it.
|
|
43
|
+
*
|
|
44
|
+
* Uses line-based fence detection (open on the first ``` line, close on the
|
|
45
|
+
* LAST ``` line) so inner triple-backticks inside the file content do not
|
|
46
|
+
* truncate the result. File content — including a trailing newline — is
|
|
47
|
+
* preserved verbatim.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} updated
|
|
50
|
+
* @param {string} original
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export function stripBadOutput(updated: string, original: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Extract the bodies of all ```diff fenced code blocks from `text`.
|
|
56
|
+
* Mirrors the MarkdownParser + "diff" tool collection in the Python source.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} text
|
|
59
|
+
* @returns {string[]} block bodies in document order
|
|
60
|
+
*/
|
|
61
|
+
export function extractDiffBlocks(text: string): string[];
|
|
62
|
+
/**
|
|
63
|
+
* Remove `<think>...</think>` blocks from a response, returning the content
|
|
64
|
+
* outside any think tool. Mirrors call_llm_for_apply_with_think_tool_available.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} response
|
|
67
|
+
* @returns {string}
|
|
68
|
+
*/
|
|
69
|
+
export function stripThinkTags(response: string): string;
|
package/src/text.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure text utilities ported from gptdiff's Python implementation.
|
|
3
|
+
*
|
|
4
|
+
* These have no I/O and run identically in the browser and Node.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Split a string into lines while keeping the line-ending characters attached,
|
|
9
|
+
* mirroring Python's `str.splitlines(keepends=True)` for `\n`, `\r`, `\r\n`.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} s
|
|
12
|
+
* @returns {string[]}
|
|
13
|
+
*/
|
|
14
|
+
export function splitLinesKeepEnds(s) {
|
|
15
|
+
if (s === '') return [];
|
|
16
|
+
const lines = [];
|
|
17
|
+
let i = 0;
|
|
18
|
+
let start = 0;
|
|
19
|
+
while (i < s.length) {
|
|
20
|
+
const ch = s[i];
|
|
21
|
+
if (ch === '\n') {
|
|
22
|
+
lines.push(s.slice(start, i + 1));
|
|
23
|
+
i += 1;
|
|
24
|
+
start = i;
|
|
25
|
+
} else if (ch === '\r') {
|
|
26
|
+
if (s[i + 1] === '\n') {
|
|
27
|
+
lines.push(s.slice(start, i + 2));
|
|
28
|
+
i += 2;
|
|
29
|
+
} else {
|
|
30
|
+
lines.push(s.slice(start, i + 1));
|
|
31
|
+
i += 1;
|
|
32
|
+
}
|
|
33
|
+
start = i;
|
|
34
|
+
} else {
|
|
35
|
+
i += 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (start < s.length) lines.push(s.slice(start));
|
|
39
|
+
return lines;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Split a string into lines WITHOUT keeping line endings, mirroring Python's
|
|
44
|
+
* `str.splitlines()` — notably, a single trailing newline does not produce a
|
|
45
|
+
* trailing empty element.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} s
|
|
48
|
+
* @returns {string[]}
|
|
49
|
+
*/
|
|
50
|
+
export function splitLines(s) {
|
|
51
|
+
if (s === '') return [];
|
|
52
|
+
const parts = s.split(/\r\n|\r|\n/);
|
|
53
|
+
if (parts.length && parts[parts.length - 1] === '') parts.pop();
|
|
54
|
+
return parts;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Color-code a diff for terminal output: lines starting with '-' in red and
|
|
59
|
+
* lines starting with '+' in green. Other lines are untouched.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} diffText
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
export function colorCodeDiff(diffText) {
|
|
65
|
+
const red = '\x1b[31m';
|
|
66
|
+
const green = '\x1b[32m';
|
|
67
|
+
const reset = '\x1b[0m';
|
|
68
|
+
|
|
69
|
+
return diffText
|
|
70
|
+
.split('\n')
|
|
71
|
+
.map((line) => {
|
|
72
|
+
if (line.startsWith('-')) return `${red}${line}${reset}`;
|
|
73
|
+
if (line.startsWith('+')) return `${green}${line}${reset}`;
|
|
74
|
+
return line;
|
|
75
|
+
})
|
|
76
|
+
.join('\n');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Extract and remove the chain-of-thought reasoning block from an LLM response.
|
|
81
|
+
*
|
|
82
|
+
* The reasoning block begins with a line containing "> Reasoning" and ends with
|
|
83
|
+
* a "Reasoned ... seconds" marker. Multiple blocks are supported.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} fullResponse
|
|
86
|
+
* @returns {[string, string]} tuple of [finalContent, reasoning]
|
|
87
|
+
*/
|
|
88
|
+
export function swallowReasoning(fullResponse) {
|
|
89
|
+
const pattern = />\s*Reasoning[\s\S]*?Reasoned[\s\S]*?seconds/g;
|
|
90
|
+
const reasoningList = [];
|
|
91
|
+
|
|
92
|
+
const finalContent = fullResponse.replace(pattern, (match) => {
|
|
93
|
+
const reasoning = match
|
|
94
|
+
.split(/\r\n|\r|\n/)
|
|
95
|
+
.map((line) => line.replace(/^\++/, '').trim())
|
|
96
|
+
.join('\n')
|
|
97
|
+
.trim();
|
|
98
|
+
reasoningList.push(reasoning);
|
|
99
|
+
return '';
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return [finalContent.trim(), reasoningList.join('\n')];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* If the LLM wrapped the file content in a Markdown code fence, unwrap it.
|
|
107
|
+
*
|
|
108
|
+
* Uses line-based fence detection (open on the first ``` line, close on the
|
|
109
|
+
* LAST ``` line) so inner triple-backticks inside the file content do not
|
|
110
|
+
* truncate the result. File content — including a trailing newline — is
|
|
111
|
+
* preserved verbatim.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} updated
|
|
114
|
+
* @param {string} original
|
|
115
|
+
* @returns {string}
|
|
116
|
+
*/
|
|
117
|
+
export function stripBadOutput(updated, original) {
|
|
118
|
+
// If the file itself starts with a code fence, don't try to unwrap.
|
|
119
|
+
if (original.replace(/^\s+/, '').startsWith('```')) {
|
|
120
|
+
return updated;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const lines = splitLinesKeepEnds(updated);
|
|
124
|
+
|
|
125
|
+
// Find the first line that opens a fence (possibly after a preamble).
|
|
126
|
+
let openIdx = null;
|
|
127
|
+
for (let i = 0; i < lines.length; i++) {
|
|
128
|
+
if (lines[i].replace(/^\s+/, '').startsWith('```')) {
|
|
129
|
+
openIdx = i;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (openIdx === null) return updated;
|
|
134
|
+
|
|
135
|
+
// Find the LAST line that is exactly a closing fence after the opener.
|
|
136
|
+
let closeIdx = null;
|
|
137
|
+
for (let i = lines.length - 1; i > openIdx; i--) {
|
|
138
|
+
if (lines[i].trim() === '```') {
|
|
139
|
+
closeIdx = i;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (closeIdx === null) return updated;
|
|
144
|
+
|
|
145
|
+
let inner = lines.slice(openIdx + 1, closeIdx).join('');
|
|
146
|
+
|
|
147
|
+
// If the opener was a bare ``` and the model put the language on the next
|
|
148
|
+
// line instead (e.g. "```\npython\n..."), drop that stray language tag.
|
|
149
|
+
if (lines[openIdx].trim() === '```') {
|
|
150
|
+
const innerLines = splitLinesKeepEnds(inner);
|
|
151
|
+
if (innerLines.length > 1) {
|
|
152
|
+
const firstInner = innerLines[0].trim();
|
|
153
|
+
if (firstInner && !firstInner.includes(' ') && firstInner.toLowerCase() !== 'diff') {
|
|
154
|
+
inner = innerLines.slice(1).join('');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return inner;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Extract the bodies of all ```diff fenced code blocks from `text`.
|
|
164
|
+
* Mirrors the MarkdownParser + "diff" tool collection in the Python source.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} text
|
|
167
|
+
* @returns {string[]} block bodies in document order
|
|
168
|
+
*/
|
|
169
|
+
export function extractDiffBlocks(text) {
|
|
170
|
+
const blocks = [];
|
|
171
|
+
const lines = text.split(/\r\n|\r|\n/);
|
|
172
|
+
let inBlock = false;
|
|
173
|
+
let current = [];
|
|
174
|
+
const flush = () => {
|
|
175
|
+
// Mirror the Markdown tool parser: the captured block body keeps the
|
|
176
|
+
// newline that precedes the closing fence.
|
|
177
|
+
blocks.push(current.length ? current.join('\n') + '\n' : '');
|
|
178
|
+
};
|
|
179
|
+
for (const line of lines) {
|
|
180
|
+
const stripped = line.trim();
|
|
181
|
+
if (!inBlock) {
|
|
182
|
+
// Only fences whose language is exactly "diff" open a collected block,
|
|
183
|
+
// matching the Python source (which routes solely the "diff" tool).
|
|
184
|
+
if (stripped.startsWith('```') && stripped.slice(3).trim() === 'diff') {
|
|
185
|
+
inBlock = true;
|
|
186
|
+
current = [];
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (stripped.startsWith('```')) {
|
|
191
|
+
flush();
|
|
192
|
+
inBlock = false;
|
|
193
|
+
current = [];
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
current.push(line);
|
|
197
|
+
}
|
|
198
|
+
// Force-close an unterminated block (the parser keeps the trailing buffer).
|
|
199
|
+
if (inBlock) flush();
|
|
200
|
+
return blocks;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Remove `<think>...</think>` blocks from a response, returning the content
|
|
205
|
+
* outside any think tool. Mirrors call_llm_for_apply_with_think_tool_available.
|
|
206
|
+
*
|
|
207
|
+
* @param {string} response
|
|
208
|
+
* @returns {string}
|
|
209
|
+
*/
|
|
210
|
+
export function stripThinkTags(response) {
|
|
211
|
+
const [withoutReasoning] = swallowReasoning(response);
|
|
212
|
+
let withoutThink = withoutReasoning.replace(/<think>[\s\S]*?<\/think>/g, '');
|
|
213
|
+
// Force-close a dangling, unterminated <think> tag (mirrors the XML parser,
|
|
214
|
+
// which swallows everything after an unclosed tag) so chain-of-thought is not
|
|
215
|
+
// written into the file.
|
|
216
|
+
withoutThink = withoutThink.replace(/<think>[\s\S]*$/, '');
|
|
217
|
+
return withoutThink.trim();
|
|
218
|
+
}
|