opencode-fast-apply 2.1.5 → 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 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
- - **Robust XML tag handling** - safely handles code containing `<updated-code>` tags
13
- - **Special character support** - preserves all string literals, regex patterns, and escape sequences
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. Escapes XML tags in code to prevent conflicts
107
- 3. Sends system prompt + user prompt with `<instruction>`, `<code>`, and `<update>` to OpenAI-compatible API
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 from `<updated-code>` tags and unescapes XML
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
- - ✅ String literals containing `<updated-code>` tags
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
 
@@ -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;AA2ZvD,eAAO,MAAM,eAAe,EAAE,MA4J7B,CAAA;AAGD,eAAe,eAAe,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,24 +85,41 @@ function alsoKeepThis() {
97
85
 
98
86
  ## Fallback
99
87
  If API fails, use native \`edit\` tool with exact string matching.`;
100
- function extractUpdatedCode(raw) {
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;
95
+ }
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
+ };
106
+ }
107
+ function extractUpdatedCode(raw, resultStart, resultEnd) {
101
108
  const stripped = raw.trim();
102
- const startTag = UPDATED_CODE_START;
103
- const endTag = UPDATED_CODE_END;
104
- let startIdx = stripped.indexOf(startTag);
109
+ let startIdx = stripped.indexOf(resultStart);
105
110
  if (startIdx === -1) {
106
- startIdx = stripped.indexOf("<<<UPDATED_CODE");
107
- if (startIdx !== -1) {
108
- const closeTagIdx = stripped.indexOf(">>>", startIdx);
111
+ const genericStart = stripped.indexOf("<<<RESULT_");
112
+ if (genericStart !== -1) {
113
+ const closeTagIdx = stripped.indexOf(">>>", genericStart);
109
114
  if (closeTagIdx !== -1) {
110
115
  startIdx = closeTagIdx + 3;
111
116
  }
112
117
  }
113
118
  }
114
119
  else {
115
- startIdx += startTag.length;
120
+ startIdx += resultStart.length;
116
121
  }
117
- if (startIdx === -1 || startIdx === startTag.length - 1) {
122
+ if (startIdx === -1 || startIdx === resultStart.length - 1) {
118
123
  if (stripped.startsWith("```") && stripped.endsWith("```")) {
119
124
  const lines = stripped.split("\n");
120
125
  if (lines.length >= 2) {
@@ -123,21 +128,24 @@ function extractUpdatedCode(raw) {
123
128
  }
124
129
  return stripped;
125
130
  }
126
- let endIdx = stripped.indexOf(endTag, startIdx);
131
+ let endIdx = stripped.indexOf(resultEnd, startIdx);
127
132
  if (endIdx === -1) {
128
- endIdx = stripped.indexOf("<<</UPDATED_CODE", startIdx);
133
+ const genericEnd = stripped.indexOf("<<<END_RESULT_", startIdx);
134
+ if (genericEnd !== -1) {
135
+ endIdx = genericEnd;
136
+ }
129
137
  }
130
138
  if (endIdx === -1) {
131
139
  const extracted = stripped.slice(startIdx).trim();
132
140
  const lastCloseTag = extracted.lastIndexOf("<<<");
133
- if (lastCloseTag !== -1 && extracted.slice(lastCloseTag).toLowerCase().includes("update")) {
141
+ if (lastCloseTag !== -1 && extracted.slice(lastCloseTag).toLowerCase().includes("end")) {
134
142
  return extracted.slice(0, lastCloseTag).trim();
135
143
  }
136
144
  return extracted;
137
145
  }
138
146
  const inner = stripped.substring(startIdx, endIdx);
139
147
  if (!inner || inner.trim().length === 0) {
140
- throw new Error("Empty updated-code block");
148
+ throw new Error("Empty result block");
141
149
  }
142
150
  return inner;
143
151
  }
@@ -219,9 +227,21 @@ async function callFastApply(originalCode, codeEdit, instructions) {
219
227
  };
220
228
  }
221
229
  try {
222
- const userContent = FAST_APPLY_USER_PROMPT
223
- .replace("{original_code}", originalCode)
224
- .replace("{update_snippet}", codeEdit);
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}.`;
225
245
  const response = await fetch(`${FAST_APPLY_URL}/v1/chat/completions`, {
226
246
  method: "POST",
227
247
  headers: {
@@ -258,7 +278,7 @@ async function callFastApply(originalCode, codeEdit, instructions) {
258
278
  error: "Fast Apply API returned empty response",
259
279
  };
260
280
  }
261
- const mergedCode = extractUpdatedCode(rawResponse);
281
+ const mergedCode = extractUpdatedCode(rawResponse, delimiters.RESULT_START, delimiters.RESULT_END);
262
282
  return {
263
283
  success: true,
264
284
  content: mergedCode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-fast-apply",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
4
4
  "description": "OpenCode plugin for Fast Apply - High-performance code editing with OpenAI-compatible APIs (LM Studio, Ollama)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",