opencode-fast-apply 2.1.0 → 2.1.2

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
@@ -34,21 +34,21 @@ Or add to your OpenCode config to auto-install:
34
34
 
35
35
  For **LM Studio** (default):
36
36
  ```bash
37
- export FAST_APPLY_URL="http://localhost:1234/v1"
37
+ export FAST_APPLY_URL="http://localhost:1234"
38
38
  export FAST_APPLY_MODEL="fastapply-1.5b"
39
39
  export FAST_APPLY_API_KEY="optional-api-key"
40
40
  ```
41
41
 
42
42
  For **Ollama**:
43
43
  ```bash
44
- export FAST_APPLY_URL="http://localhost:11434/v1"
44
+ export FAST_APPLY_URL="http://localhost:11434"
45
45
  export FAST_APPLY_MODEL="codellama:7b"
46
46
  export FAST_APPLY_API_KEY="optional-api-key"
47
47
  ```
48
48
 
49
49
  For **OpenAI**:
50
50
  ```bash
51
- export FAST_APPLY_URL="https://api.openai.com/v1"
51
+ export FAST_APPLY_URL="https://api.openai.com"
52
52
  export FAST_APPLY_MODEL="gpt-4"
53
53
  export FAST_APPLY_API_KEY="sk-your-openai-key"
54
54
  ```
@@ -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;AA2UvD,eAAO,MAAM,eAAe,EAAE,MAiH7B,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;AA0YvD,eAAO,MAAM,eAAe,EAAE,MA2H7B,CAAA;AAGD,eAAe,eAAe,CAAA"}
package/dist/index.js CHANGED
@@ -18,13 +18,17 @@ const FAST_APPLY_MODEL = process.env.FAST_APPLY_MODEL || "fastapply-1.5b";
18
18
  const FAST_APPLY_TIMEOUT = parseInt(process.env.FAST_APPLY_TIMEOUT || "30000", 10);
19
19
  const FAST_APPLY_TEMPERATURE = parseFloat(process.env.FAST_APPLY_TEMPERATURE || "0.05");
20
20
  const FAST_APPLY_MAX_TOKENS = parseInt(process.env.FAST_APPLY_MAX_TOKENS || "8000", 10);
21
- const FAST_APPLY_SYSTEM_PROMPT = "Merge code edits into original files. Preserve structure, indentation, and comments exactly.";
22
- const FAST_APPLY_USER_PROMPT = `Task: {instruction}
21
+ const FAST_APPLY_SYSTEM_PROMPT = "You are a coding assistant that helps merge code updates, ensuring every modification is fully integrated.";
22
+ const FAST_APPLY_USER_PROMPT = `Merge all changes from the <update> snippet into the <code> below.
23
+ - Preserve the code's structure, order, comments, and indentation exactly.
24
+ - Output only the updated code, enclosed within <updated-code> and </updated-code> tags.
25
+ - Do not include any additional text, explanations, placeholders, ellipses, or code fences.
23
26
 
24
27
  <code>{original_code}</code>
28
+
25
29
  <update>{update_snippet}</update>
26
30
 
27
- Output complete merged code in <updated-code></updated-code> tags. No explanations, markdown, or ellipses.`;
31
+ Provide the complete updated code.`;
28
32
  const UPDATED_CODE_START = "<updated-code>";
29
33
  const UPDATED_CODE_END = "</updated-code>";
30
34
  const TOOL_INSTRUCTIONS = `**DEFAULT tool for editing existing files. Use INSTEAD of native 'edit' tool.**
@@ -106,9 +110,22 @@ function unescapeXmlTags(text) {
106
110
  }
107
111
  function extractUpdatedCode(raw) {
108
112
  const stripped = raw.trim();
109
- const start = stripped.indexOf(UPDATED_CODE_START);
110
- const end = stripped.lastIndexOf(UPDATED_CODE_END);
111
- if (start === -1 || end === -1 || end <= start) {
113
+ const startTag = UPDATED_CODE_START;
114
+ const endTag = UPDATED_CODE_END;
115
+ let startIdx = stripped.indexOf(startTag);
116
+ if (startIdx === -1) {
117
+ startIdx = stripped.indexOf("<updated-code");
118
+ if (startIdx !== -1) {
119
+ const closeTagIdx = stripped.indexOf(">", startIdx);
120
+ if (closeTagIdx !== -1) {
121
+ startIdx = closeTagIdx + 1;
122
+ }
123
+ }
124
+ }
125
+ else {
126
+ startIdx += startTag.length;
127
+ }
128
+ if (startIdx === -1 || startIdx === startTag.length - 1) {
112
129
  if (stripped.startsWith("```") && stripped.endsWith("```")) {
113
130
  const lines = stripped.split("\n");
114
131
  if (lines.length >= 2) {
@@ -117,7 +134,19 @@ function extractUpdatedCode(raw) {
117
134
  }
118
135
  return unescapeXmlTags(stripped);
119
136
  }
120
- const inner = stripped.substring(start + UPDATED_CODE_START.length, end);
137
+ let endIdx = stripped.indexOf(endTag, startIdx);
138
+ if (endIdx === -1) {
139
+ endIdx = stripped.indexOf("</updated-code", startIdx);
140
+ }
141
+ if (endIdx === -1) {
142
+ const extracted = stripped.slice(startIdx).trim();
143
+ const lastCloseTag = extracted.lastIndexOf("</");
144
+ if (lastCloseTag !== -1 && extracted.slice(lastCloseTag).toLowerCase().includes("update")) {
145
+ return unescapeXmlTags(extracted.slice(0, lastCloseTag).trim());
146
+ }
147
+ return unescapeXmlTags(extracted);
148
+ }
149
+ const inner = stripped.substring(startIdx, endIdx);
121
150
  if (!inner || inner.trim().length === 0) {
122
151
  throw new Error("Empty updated-code block");
123
152
  }
@@ -173,19 +202,14 @@ function estimateTokens(text) {
173
202
  function formatFastApplyResult(filePath, workingDir, insertions, deletions, diffPreview, modifiedTokens) {
174
203
  const shortPath = shortenPath(filePath, workingDir);
175
204
  const tokenStr = formatTokenCount(modifiedTokens);
176
- const diffLines = diffPreview.split("\n");
177
- const previewLines = diffLines.slice(0, 10);
178
- const truncatedDiff = previewLines.join("\n");
179
- const hasMore = diffLines.length > 10;
180
205
  const lines = [
181
206
  "✓ Fast Apply complete",
182
207
  "",
183
- "Applied changes:",
184
- `→ ${shortPath}`,
185
- ` +${insertions} lines, -${deletions} lines (~${tokenStr} tokens modified)`,
208
+ `File: ${shortPath}`,
209
+ `Changes: +${insertions} -${deletions} (~${tokenStr} tokens)`,
186
210
  "",
187
- "Diff preview (first 10 lines):",
188
- truncatedDiff + (hasMore ? "\n..." : "")
211
+ "Unified diff:",
212
+ diffPreview
189
213
  ];
190
214
  return lines.join("\n");
191
215
  }
@@ -194,8 +218,8 @@ function formatErrorOutput(error, filePath, workingDir) {
194
218
  return [
195
219
  "✗ Fast Apply failed",
196
220
  "",
197
- `→ ${shortPath}`,
198
- ` Error: ${truncate(error, 100)}`,
221
+ `File: ${shortPath}`,
222
+ `Error: ${error}`,
199
223
  "",
200
224
  "Fallback: Use native 'edit' tool with exact string matching"
201
225
  ].join("\n");
@@ -216,7 +240,6 @@ async function callFastApply(originalCode, codeEdit, instructions) {
216
240
  const escapedOriginalCode = escapeXmlTags(originalCode);
217
241
  const escapedCodeEdit = escapeXmlTags(codeEdit);
218
242
  const userContent = FAST_APPLY_USER_PROMPT
219
- .replace("{instruction}", instructions || "Apply the requested code changes.")
220
243
  .replace("{original_code}", escapedOriginalCode)
221
244
  .replace("{update_snippet}", escapedCodeEdit);
222
245
  const response = await fetch(`${FAST_APPLY_URL}/v1/chat/completions`, {
@@ -279,7 +302,35 @@ async function callFastApply(originalCode, codeEdit, instructions) {
279
302
  };
280
303
  }
281
304
  }
282
- export const FastApplyPlugin = async ({ directory }) => {
305
+ async function sendTUINotification(client, sessionID, filePath, workingDir, insertions, deletions, modifiedTokens) {
306
+ const shortPath = shortenPath(filePath, workingDir);
307
+ const tokenStr = formatTokenCount(modifiedTokens);
308
+ const message = [
309
+ `▣ Fast Apply | ~${tokenStr} tokens modified`,
310
+ "",
311
+ "Applied changes:",
312
+ `→ ${shortPath}: +${insertions} -${deletions}`
313
+ ].join("\n");
314
+ try {
315
+ await client.session.prompt({
316
+ path: { id: sessionID },
317
+ body: {
318
+ noReply: true,
319
+ parts: [
320
+ {
321
+ type: "text",
322
+ text: message,
323
+ ignored: true,
324
+ },
325
+ ],
326
+ },
327
+ });
328
+ }
329
+ catch (error) {
330
+ console.error("[fast-apply] Failed to send TUI notification:", error.message);
331
+ }
332
+ }
333
+ export const FastApplyPlugin = async ({ directory, client }) => {
283
334
  if (!FAST_APPLY_API_KEY) {
284
335
  console.warn("[fast-apply] FAST_APPLY_API_KEY not set - fast_apply_edit tool will be disabled");
285
336
  }
@@ -301,7 +352,7 @@ export const FastApplyPlugin = async ({ directory }) => {
301
352
  .string()
302
353
  .describe('The code changes with "// ... existing code ..." markers for unchanged sections'),
303
354
  },
304
- async execute(args) {
355
+ async execute(args, toolCtx) {
305
356
  const { target_filepath, instructions, code_edit } = args;
306
357
  // Resolve file path relative to project directory
307
358
  const filepath = target_filepath.startsWith("/")
@@ -354,6 +405,7 @@ write({
354
405
  const diff = generateUnifiedDiff(target_filepath, originalCode, mergedCode);
355
406
  const { added, removed } = countChanges(diff);
356
407
  const modifiedTokens = estimateTokens(diff);
408
+ await sendTUINotification(client, toolCtx.sessionID, target_filepath, directory, added, removed, modifiedTokens);
357
409
  return formatFastApplyResult(target_filepath, directory, added, removed, diff, modifiedTokens);
358
410
  },
359
411
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-fast-apply",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
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",