opencode-fast-apply 2.1.6 → 2.1.7
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/README.md +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,8 +9,9 @@ OpenCode plugin for Fast Apply - High-performance code editing with OpenAI-compa
|
|
|
9
9
|
- **Unified diff output** with context for easy review
|
|
10
10
|
- **Graceful fallback** - suggests native `edit` tool on API failure
|
|
11
11
|
- **Multi-backend support** - LM Studio, Ollama, OpenAI, and any OpenAI-compatible endpoint
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
12
|
+
- **Unique delimiter system** - generates random delimiters per request (`<<<RESULT_xxxx>>>`) to eliminate parsing conflicts
|
|
13
|
+
- **Zero escaping overhead** - no XML tag processing needed, handles all code patterns safely
|
|
14
|
+
- **Collision-resistant parsing** - 1.6M+ unique delimiter combinations prevent conflicts with file content
|
|
14
15
|
|
|
15
16
|
## Installation
|
|
16
17
|
|
|
@@ -103,13 +104,15 @@ function validateToken(token) {
|
|
|
103
104
|
## How It Works
|
|
104
105
|
|
|
105
106
|
1. Reads the original file content
|
|
106
|
-
2.
|
|
107
|
-
3. Sends system prompt + user prompt with
|
|
107
|
+
2. Generates unique random delimiters for this request (e.g., `<<<RESULT_a3f9>>>`)
|
|
108
|
+
3. Sends system prompt + user prompt with unique delimiters to OpenAI-compatible API
|
|
108
109
|
4. API intelligently merges the lazy edit markers with original code
|
|
109
|
-
5. Extracts result
|
|
110
|
+
5. Extracts result using the unique delimiters with fallback pattern detection
|
|
110
111
|
6. Writes the merged result back to the file
|
|
111
112
|
7. Returns a unified diff showing what changed
|
|
112
113
|
|
|
114
|
+
**Delimiter Design:** Each request generates unique 4-character alphanumeric IDs (1,679,616 combinations) for delimiters like `<<<RESULT_xxxx>>>` and `<<<END_RESULT_xxxx>>>`. This eliminates parsing conflicts even if your code contains similar patterns. Fallback logic detects any `<<<RESULT_*>>>` pattern for maximum robustness.
|
|
115
|
+
|
|
113
116
|
## Performance
|
|
114
117
|
|
|
115
118
|
Performance varies based on your setup:
|
|
@@ -136,7 +139,8 @@ Performance varies based on your setup:
|
|
|
136
139
|
|
|
137
140
|
## Edge Cases Handled
|
|
138
141
|
|
|
139
|
-
- ✅
|
|
142
|
+
- ✅ Code containing XML-like tags (`<update>`, `<code>`, `<result>`) in strings
|
|
143
|
+
- ✅ Code containing triple-angle-bracket patterns (`<<<RESULT>>>`) in comments or strings
|
|
140
144
|
- ✅ Multiple XML-like tags in regex patterns
|
|
141
145
|
- ✅ Special characters (quotes, backslashes, unicode, SQL, HTML entities)
|
|
142
146
|
- ✅ Large files (500+ lines)
|
|
@@ -144,6 +148,8 @@ Performance varies based on your setup:
|
|
|
144
148
|
- ✅ Complex nested structures
|
|
145
149
|
- ✅ Template strings with `${variable}`
|
|
146
150
|
- ✅ Whitespace and indentation preservation
|
|
151
|
+
- ✅ Unique delimiters per request prevent all parsing conflicts (1.6M+ combinations)
|
|
152
|
+
- ✅ Fallback pattern detection for robust delimiter parsing
|
|
147
153
|
|
|
148
154
|
## Troubleshooting
|
|
149
155
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,qBAAqB,CAAA;AAgbvD,eAAO,MAAM,eAAe,EAAE,MA4J7B,CAAA;AAGD,eAAe,eAAe,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -18,18 +18,6 @@ const FAST_APPLY_URL = (process.env.FAST_APPLY_URL || "http://localhost:1234/v1"
|
|
|
18
18
|
const FAST_APPLY_MODEL = process.env.FAST_APPLY_MODEL || "fastapply-1.5b";
|
|
19
19
|
const FAST_APPLY_TEMPERATURE = parseFloat(process.env.FAST_APPLY_TEMPERATURE || "0.05");
|
|
20
20
|
const FAST_APPLY_SYSTEM_PROMPT = "You are a coding assistant that helps merge code updates, ensuring every modification is fully integrated.";
|
|
21
|
-
const FAST_APPLY_USER_PROMPT = `Merge all changes from the <update> snippet into the <code> below.
|
|
22
|
-
- Preserve the code's structure, order, comments, and indentation exactly.
|
|
23
|
-
- Output only the updated code, enclosed within <updated-code> and </updated-code> tags.
|
|
24
|
-
- Do not include any additional text, explanations, placeholders, ellipses, or code fences.
|
|
25
|
-
|
|
26
|
-
<code>{original_code}</code>
|
|
27
|
-
|
|
28
|
-
<update>{update_snippet}</update>
|
|
29
|
-
|
|
30
|
-
Provide the complete updated code.`;
|
|
31
|
-
const UPDATED_CODE_START = "<updated-code>";
|
|
32
|
-
const UPDATED_CODE_END = "</updated-code>";
|
|
33
21
|
const TOOL_INSTRUCTIONS = `**DEFAULT tool for editing existing files. Use INSTEAD of native 'edit' tool.**
|
|
34
22
|
|
|
35
23
|
CRITICAL: For EXISTING files ONLY. Use 'write' for new files.
|
|
@@ -97,59 +85,69 @@ function alsoKeepThis() {
|
|
|
97
85
|
|
|
98
86
|
## Fallback
|
|
99
87
|
If API fails, use native \`edit\` tool with exact string matching.`;
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
88
|
+
function generateRandomId() {
|
|
89
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
90
|
+
let result = '';
|
|
91
|
+
for (let i = 0; i < 4; i++) {
|
|
92
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
104
95
|
}
|
|
105
|
-
function
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
96
|
+
function generateUniqueDelimiters() {
|
|
97
|
+
const id = generateRandomId();
|
|
98
|
+
return {
|
|
99
|
+
ORIGINAL_START: `<<<ORIGINAL_${id}>>>`,
|
|
100
|
+
ORIGINAL_END: `<<<END_ORIGINAL_${id}>>>`,
|
|
101
|
+
UPDATE_START: `<<<UPDATE_${id}>>>`,
|
|
102
|
+
UPDATE_END: `<<<END_UPDATE_${id}>>>`,
|
|
103
|
+
RESULT_START: `<<<RESULT_${id}>>>`,
|
|
104
|
+
RESULT_END: `<<<END_RESULT_${id}>>>`
|
|
105
|
+
};
|
|
109
106
|
}
|
|
110
|
-
function extractUpdatedCode(raw) {
|
|
107
|
+
function extractUpdatedCode(raw, resultStart, resultEnd) {
|
|
111
108
|
const stripped = raw.trim();
|
|
112
|
-
|
|
113
|
-
const endTag = UPDATED_CODE_END;
|
|
114
|
-
let startIdx = stripped.indexOf(startTag);
|
|
109
|
+
let startIdx = stripped.indexOf(resultStart);
|
|
115
110
|
if (startIdx === -1) {
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
const closeTagIdx = stripped.indexOf("
|
|
111
|
+
const genericStart = stripped.indexOf("<<<RESULT_");
|
|
112
|
+
if (genericStart !== -1) {
|
|
113
|
+
const closeTagIdx = stripped.indexOf(">>>", genericStart);
|
|
119
114
|
if (closeTagIdx !== -1) {
|
|
120
|
-
startIdx = closeTagIdx +
|
|
115
|
+
startIdx = closeTagIdx + 3;
|
|
121
116
|
}
|
|
122
117
|
}
|
|
123
118
|
}
|
|
124
119
|
else {
|
|
125
|
-
startIdx +=
|
|
120
|
+
startIdx += resultStart.length;
|
|
126
121
|
}
|
|
127
|
-
if (startIdx === -1 || startIdx ===
|
|
122
|
+
if (startIdx === -1 || startIdx === resultStart.length - 1) {
|
|
128
123
|
if (stripped.startsWith("```") && stripped.endsWith("```")) {
|
|
129
124
|
const lines = stripped.split("\n");
|
|
130
125
|
if (lines.length >= 2) {
|
|
131
|
-
return
|
|
126
|
+
return lines.slice(1, -1).join("\n");
|
|
132
127
|
}
|
|
133
128
|
}
|
|
134
|
-
return
|
|
129
|
+
return stripped;
|
|
135
130
|
}
|
|
136
|
-
let endIdx = stripped.indexOf(
|
|
131
|
+
let endIdx = stripped.indexOf(resultEnd, startIdx);
|
|
137
132
|
if (endIdx === -1) {
|
|
138
|
-
|
|
133
|
+
const genericEnd = stripped.indexOf("<<<END_RESULT_", startIdx);
|
|
134
|
+
if (genericEnd !== -1) {
|
|
135
|
+
endIdx = genericEnd;
|
|
136
|
+
}
|
|
139
137
|
}
|
|
140
138
|
if (endIdx === -1) {
|
|
141
139
|
const extracted = stripped.slice(startIdx).trim();
|
|
142
|
-
const lastCloseTag = extracted.lastIndexOf("
|
|
143
|
-
if (lastCloseTag !== -1 && extracted.slice(lastCloseTag).toLowerCase().includes("
|
|
144
|
-
return
|
|
140
|
+
const lastCloseTag = extracted.lastIndexOf("<<<");
|
|
141
|
+
if (lastCloseTag !== -1 && extracted.slice(lastCloseTag).toLowerCase().includes("end")) {
|
|
142
|
+
return extracted.slice(0, lastCloseTag).trim();
|
|
145
143
|
}
|
|
146
|
-
return
|
|
144
|
+
return extracted;
|
|
147
145
|
}
|
|
148
146
|
const inner = stripped.substring(startIdx, endIdx);
|
|
149
147
|
if (!inner || inner.trim().length === 0) {
|
|
150
|
-
throw new Error("Empty
|
|
148
|
+
throw new Error("Empty result block");
|
|
151
149
|
}
|
|
152
|
-
return
|
|
150
|
+
return inner;
|
|
153
151
|
}
|
|
154
152
|
function generateUnifiedDiff(filepath, original, modified) {
|
|
155
153
|
const patch = createTwoFilesPatch(`a/${filepath}`, `b/${filepath}`, original, modified, "", "", { context: 3 });
|
|
@@ -229,11 +227,21 @@ async function callFastApply(originalCode, codeEdit, instructions) {
|
|
|
229
227
|
};
|
|
230
228
|
}
|
|
231
229
|
try {
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
230
|
+
const delimiters = generateUniqueDelimiters();
|
|
231
|
+
const userContent = `Merge all changes from the UPDATE_BLOCK into the ORIGINAL_BLOCK below.
|
|
232
|
+
- Preserve the code's structure, order, comments, and indentation exactly.
|
|
233
|
+
- Output only the updated code, enclosed within ${delimiters.RESULT_START} and ${delimiters.RESULT_END} delimiters.
|
|
234
|
+
- Do not include any additional text, explanations, placeholders, ellipses, or code fences.
|
|
235
|
+
|
|
236
|
+
${delimiters.ORIGINAL_START}
|
|
237
|
+
${originalCode}
|
|
238
|
+
${delimiters.ORIGINAL_END}
|
|
239
|
+
|
|
240
|
+
${delimiters.UPDATE_START}
|
|
241
|
+
${codeEdit}
|
|
242
|
+
${delimiters.UPDATE_END}
|
|
243
|
+
|
|
244
|
+
Provide the complete updated code wrapped in ${delimiters.RESULT_START} and ${delimiters.RESULT_END}.`;
|
|
237
245
|
const response = await fetch(`${FAST_APPLY_URL}/v1/chat/completions`, {
|
|
238
246
|
method: "POST",
|
|
239
247
|
headers: {
|
|
@@ -270,7 +278,7 @@ async function callFastApply(originalCode, codeEdit, instructions) {
|
|
|
270
278
|
error: "Fast Apply API returned empty response",
|
|
271
279
|
};
|
|
272
280
|
}
|
|
273
|
-
const mergedCode = extractUpdatedCode(rawResponse);
|
|
281
|
+
const mergedCode = extractUpdatedCode(rawResponse, delimiters.RESULT_START, delimiters.RESULT_END);
|
|
274
282
|
return {
|
|
275
283
|
success: true,
|
|
276
284
|
content: mergedCode,
|
package/package.json
CHANGED