opencode-fast-apply 2.0.0 → 2.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 JRedeker
3
+ Copyright (c) 2025 tickernelz
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -14,12 +14,20 @@ OpenCode plugin for Fast Apply - High-performance code editing with OpenAI-compa
14
14
 
15
15
  ## Installation
16
16
 
17
- ### 1. Clone the repository
17
+ ### 1. Install from npm (Recommended)
18
18
 
19
19
  ```bash
20
- git clone https://github.com/tickernelz/opencode-fast-apply.git ~/dev/oc-plugins/fast-apply
21
- cd ~/dev/oc-plugins/fast-apply
22
- npm install
20
+ npm install -g opencode-fast-apply
21
+ ```
22
+
23
+ Or add to your OpenCode config to auto-install:
24
+
25
+ ```json
26
+ {
27
+ "plugin": [
28
+ "opencode-fast-apply"
29
+ ]
30
+ }
23
31
  ```
24
32
 
25
33
  ### 2. Configure your API endpoint
@@ -49,35 +57,22 @@ export FAST_APPLY_API_KEY="sk-your-openai-key"
49
57
 
50
58
  ### 3. Add the plugin to your OpenCode config
51
59
 
52
- Add to your global config (`~/.config/opencode/opencode.json`):
60
+ Add to your global config (`~/.config/opencode/opencode.json` or `opencode.jsonc`):
53
61
 
54
62
  ```json
55
63
  {
56
64
  "plugin": [
57
- "/path/to/fast-apply"
58
- ],
59
- "instructions": [
60
- "/path/to/fast-apply/FAST_APPLY_INSTRUCTIONS.md"
65
+ "opencode-pty",
66
+ "opencode-fast-apply"
61
67
  ]
62
68
  }
63
69
  ```
64
70
 
65
- Or in a project-local `.opencode/config.json`:
66
-
67
- ```json
68
- {
69
- "plugin": [
70
- "~/dev/oc-plugins/fast-apply"
71
- ],
72
- "instructions": [
73
- "~/dev/oc-plugins/fast-apply/FAST_APPLY_INSTRUCTIONS.md"
74
- ]
75
- }
76
- ```
71
+ **That's it!** The plugin automatically embeds all instructions - no additional configuration needed.
77
72
 
78
73
  ### 4. Restart OpenCode
79
74
 
80
- The `fast_apply_edit` tool will now be available.
75
+ The `fast_apply_edit` tool will now be available and configured as the default editing tool.
81
76
 
82
77
  ## Usage
83
78
 
@@ -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;AA0MvD,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;AA2UvD,eAAO,MAAM,eAAe,EAAE,MAiH7B,CAAA;AAGD,eAAe,eAAe,CAAA"}
package/dist/index.js CHANGED
@@ -18,21 +18,82 @@ 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 PLUGIN_VERSION = "2.0.0";
22
- const FAST_APPLY_SYSTEM_PROMPT = "You are a coding assistant that helps merge code updates, ensuring every modification is fully integrated.";
23
- const FAST_APPLY_USER_PROMPT = `Merge all changes from the <update> snippet into the <code> below.
24
- Instruction: {instruction}
25
- - Preserve the code's structure, order, comments, and indentation exactly.
26
- - Output only the updated code, enclosed within <updated-code> and </updated-code> tags.
27
- - Do not include any additional text, explanations, placeholders, markdown, ellipses, or code fences.
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}
28
23
 
29
24
  <code>{original_code}</code>
30
-
31
25
  <update>{update_snippet}</update>
32
26
 
33
- Provide the complete updated code.`;
27
+ Output complete merged code in <updated-code></updated-code> tags. No explanations, markdown, or ellipses.`;
34
28
  const UPDATED_CODE_START = "<updated-code>";
35
29
  const UPDATED_CODE_END = "</updated-code>";
30
+ const TOOL_INSTRUCTIONS = `**DEFAULT tool for editing existing files. Use INSTEAD of native 'edit' tool.**
31
+
32
+ CRITICAL: For EXISTING files ONLY. Use 'write' for new files.
33
+
34
+ ## Priority
35
+ 1. \`fast_apply_edit\` - ALL file edits (default)
36
+ 2. \`edit\` - Fallback if API fails
37
+ 3. \`write\` - NEW files only
38
+
39
+ ## Format
40
+ Use \`// ... existing code ...\` for unchanged sections:
41
+
42
+ \`\`\`
43
+ // ... existing code ...
44
+ function updated() {
45
+ return "modified";
46
+ }
47
+ // ... existing code ...
48
+ \`\`\`
49
+
50
+ ## Rules
51
+ - MANDATORY: Use \`// ... existing code ...\` markers
52
+ - Include 2-3 lines context before/after edits
53
+ - Preserve exact indentation
54
+ - ONE edit block per call (multiple blocks = suboptimal results)
55
+ - Deletions: show context, omit deleted lines
56
+ - NEVER for new files
57
+
58
+ ## Examples
59
+
60
+ **Add function:**
61
+ \`\`\`
62
+ // ... existing code ...
63
+ import { newDep } from './newDep';
64
+ // ... existing code ...
65
+
66
+ function newFeature() {
67
+ return newDep.process();
68
+ }
69
+ // ... existing code ...
70
+ \`\`\`
71
+
72
+ **Modify:**
73
+ \`\`\`
74
+ // ... existing code ...
75
+ function existingFunc(param) {
76
+ const result = param * 2;
77
+ return result;
78
+ }
79
+ // ... existing code ...
80
+ \`\`\`
81
+
82
+ **Delete:**
83
+ \`\`\`
84
+ // ... existing code ...
85
+ function keepThis() {
86
+ return "stays";
87
+ }
88
+
89
+ function alsoKeepThis() {
90
+ return "stays";
91
+ }
92
+ // ... existing code ...
93
+ \`\`\`
94
+
95
+ ## Fallback
96
+ If API fails, use native \`edit\` tool with exact string matching.`;
36
97
  function escapeXmlTags(text) {
37
98
  return text
38
99
  .replace(/<updated-code>/g, "&lt;updated-code&gt;")
@@ -86,6 +147,59 @@ function countChanges(diff) {
86
147
  }
87
148
  return { added, removed };
88
149
  }
150
+ function formatTokenCount(tokens) {
151
+ if (tokens >= 1000) {
152
+ return `${(tokens / 1000).toFixed(1)}K`.replace(".0K", "K");
153
+ }
154
+ return tokens.toString();
155
+ }
156
+ function shortenPath(filePath, workingDir) {
157
+ if (filePath.startsWith(workingDir + "/")) {
158
+ return filePath.slice(workingDir.length + 1);
159
+ }
160
+ if (filePath === workingDir) {
161
+ return ".";
162
+ }
163
+ return filePath;
164
+ }
165
+ function truncate(str, maxLen = 80) {
166
+ if (str.length <= maxLen)
167
+ return str;
168
+ return str.slice(0, maxLen - 3) + "...";
169
+ }
170
+ function estimateTokens(text) {
171
+ return Math.ceil(text.length / 4);
172
+ }
173
+ function formatFastApplyResult(filePath, workingDir, insertions, deletions, diffPreview, modifiedTokens) {
174
+ const shortPath = shortenPath(filePath, workingDir);
175
+ 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
+ const lines = [
181
+ "✓ Fast Apply complete",
182
+ "",
183
+ "Applied changes:",
184
+ `→ ${shortPath}`,
185
+ ` +${insertions} lines, -${deletions} lines (~${tokenStr} tokens modified)`,
186
+ "",
187
+ "Diff preview (first 10 lines):",
188
+ truncatedDiff + (hasMore ? "\n..." : "")
189
+ ];
190
+ return lines.join("\n");
191
+ }
192
+ function formatErrorOutput(error, filePath, workingDir) {
193
+ const shortPath = shortenPath(filePath, workingDir);
194
+ return [
195
+ "✗ Fast Apply failed",
196
+ "",
197
+ `→ ${shortPath}`,
198
+ ` Error: ${truncate(error, 100)}`,
199
+ "",
200
+ "Fallback: Use native 'edit' tool with exact string matching"
201
+ ].join("\n");
202
+ }
89
203
  /**
90
204
  * Call OpenAI's Fast Apply API to merge code edits
91
205
  */
@@ -175,43 +289,7 @@ export const FastApplyPlugin = async ({ directory }) => {
175
289
  return {
176
290
  tool: {
177
291
  fast_apply_edit: tool({
178
- description: `PRIMARY TOOL for all file editing operations. Use this INSTEAD of the native 'edit' tool.
179
-
180
- **CRITICAL: This tool is for EDITING EXISTING FILES ONLY. DO NOT use for creating new files.**
181
-
182
- Fast code editing using OpenAI-compatible Fast Apply API (10,500+ tokens/sec).
183
- Handles lazy edit markers so you don't need exact string matching.
184
-
185
- FORMAT:
186
- Use "// ... existing code ..." to represent unchanged code blocks.
187
- Include minimal surrounding context to locate each edit precisely.
188
-
189
- EXAMPLE:
190
- // ... existing code ...
191
- function updatedFunction() {
192
- // New implementation
193
- return "modified";
194
- }
195
- // ... existing code ...
196
-
197
- RULES:
198
- - MANDATORY: Use "// ... existing code ..." for unchanged sections
199
- - Include 2-3 lines of context before and after each edit
200
- - Preserve exact indentation from original file
201
- - For deletions: show context before/after, omit deleted lines
202
- - Batch multiple edits to same file in one call
203
- - NEVER use for new file creation - use 'write' tool instead
204
-
205
- WHEN TO USE:
206
- - ALL file editing operations (default choice)
207
- - Large files (any size)
208
- - Multiple scattered changes
209
- - Complex refactoring
210
- - When exact string matching would be fragile
211
-
212
- FALLBACK:
213
- If Fast Apply API fails or is unavailable, fall back to native 'edit' tool with exact string matching.
214
- For new files, ALWAYS use 'write' tool instead.`,
292
+ description: TOOL_INSTRUCTIONS,
215
293
  args: {
216
294
  target_filepath: tool.schema
217
295
  .string()
@@ -263,34 +341,20 @@ write({
263
341
  // Call OpenAI API to merge the edit
264
342
  const result = await callFastApply(originalCode, code_edit, instructions);
265
343
  if (!result.success || !result.content) {
266
- // Return error with suggestion to use native edit
267
- return `OpenAI Fast Apply API failed: ${result.error}
268
-
269
- Suggestion: Try using the native 'edit' tool instead with exact string replacement.
270
- The edit tool requires matching the exact text in the file.`;
344
+ return formatErrorOutput(result.error || "Unknown error", target_filepath, directory);
271
345
  }
272
346
  const mergedCode = result.content;
273
- // Write the merged result
274
347
  try {
275
348
  await writeFile(filepath, mergedCode, "utf-8");
276
349
  }
277
350
  catch (err) {
278
351
  const error = err;
279
- return `Error writing file ${target_filepath}: ${error.message}`;
352
+ return formatErrorOutput(error.message, target_filepath, directory);
280
353
  }
281
- // Generate unified diff
282
354
  const diff = generateUnifiedDiff(target_filepath, originalCode, mergedCode);
283
- // Calculate change stats
284
355
  const { added, removed } = countChanges(diff);
285
- const originalLines = originalCode.split("\n").length;
286
- const mergedLines = mergedCode.split("\n").length;
287
- return `Applied edit to ${target_filepath}
288
-
289
- +${added} -${removed} lines | ${originalLines} -> ${mergedLines} total
290
-
291
- \`\`\`diff
292
- ${diff.slice(0, 3000)}${diff.length > 3000 ? "\n... (truncated)" : ""}
293
- \`\`\``;
356
+ const modifiedTokens = estimateTokens(diff);
357
+ return formatFastApplyResult(target_filepath, directory, added, removed, diff, modifiedTokens);
294
358
  },
295
359
  }),
296
360
  },
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "opencode-fast-apply",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
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",
7
7
  "types": "dist/index.d.ts",
8
8
  "files": [
9
9
  "dist",
10
- "FAST_APPLY_INSTRUCTIONS.md",
11
10
  "README.md",
12
11
  "LICENSE"
13
12
  ],
@@ -40,7 +39,7 @@
40
39
  "author": "tickernelz",
41
40
  "repository": {
42
41
  "type": "git",
43
- "url": "https://github.com/tickernelz/opencode-fast-apply.git"
42
+ "url": "git+https://github.com/tickernelz/opencode-fast-apply.git"
44
43
  },
45
44
  "bugs": {
46
45
  "url": "https://github.com/tickernelz/opencode-fast-apply/issues"
@@ -50,4 +49,4 @@
50
49
  "engines": {
51
50
  "node": ">=18.0.0"
52
51
  }
53
- }
52
+ }
@@ -1,121 +0,0 @@
1
- # Fast Apply - Tool Selection Guide
2
-
3
- **CRITICAL: Use `fast_apply_edit` as your DEFAULT tool for ALL file editing operations.**
4
-
5
- ## Tool Selection Priority
6
-
7
- | Priority | Tool | Use Case |
8
- |----------|------|----------|
9
- | **1st** | `fast_apply_edit` | **ALL file editing operations** (default choice) |
10
- | **2nd** | `edit` | Fallback only when `fast_apply_edit` fails |
11
- | **3rd** | `write` | **ONLY for creating NEW files** |
12
-
13
- ## Default Behavior
14
-
15
- **ALWAYS use `fast_apply_edit` for editing existing files unless:**
16
- - Fast Apply API is unavailable or returns error
17
- - You need guaranteed exact string replacement
18
- - The file doesn't exist yet (use `write` instead)
19
-
20
- ## Tool Selection Matrix
21
-
22
- | Situation | Tool | Reason |
23
- |-----------|------|--------|
24
- | Edit existing file (any size) | `fast_apply_edit` | **DEFAULT**, handles lazy markers, robust |
25
- | Small change (1-2 lines) | `fast_apply_edit` | **DEFAULT**, no need to switch tools |
26
- | Large file (500+ lines) | `fast_apply_edit` | **DEFAULT**, optimal performance with partial snippets |
27
- | Multiple scattered changes | `fast_apply_edit` | **DEFAULT**, batch changes efficiently |
28
- | Complex refactoring | `fast_apply_edit` | **DEFAULT**, better accuracy with context |
29
- | Whitespace-sensitive edits | `fast_apply_edit` | **DEFAULT**, forgiving with formatting |
30
- | Special characters in code | `fast_apply_edit` | **DEFAULT**, handles XML tags, regex, etc. |
31
- | Fast Apply API fails | `edit` | Fallback with exact string matching |
32
- | **New file creation** | `write` | **NEVER use fast_apply_edit for new files** |
33
-
34
- ## Using fast_apply_edit
35
-
36
- The `fast_apply_edit` tool uses **lazy edit markers** to represent unchanged code:
37
-
38
- ```javascript
39
- // ... existing code ...
40
- function updatedFunction() {
41
- // New implementation
42
- return "modified";
43
- }
44
- // ... existing code ...
45
- ```
46
-
47
- ### Parameters
48
-
49
- - `target_filepath`: Path to the file (relative to project root)
50
- - `instructions`: Brief description of changes (helps AI disambiguate)
51
- - `code_edit`: Code with `// ... existing code ...` markers
52
-
53
- ### Rules
54
-
55
- 1. **MANDATORY**: Use `// ... existing code ...` for unchanged sections
56
- 2. Include **2-3 lines of context** before and after each edit
57
- 3. Preserve **exact indentation** from original file
58
- 4. For **deletions**: show context before/after, omit the deleted lines
59
- 5. **Batch** multiple edits to the same file in one call
60
- 6. **NEVER** use for new file creation - use `write` tool instead
61
-
62
- ### Examples
63
-
64
- **Adding a function:**
65
- ```
66
- // ... existing code ...
67
- import { newDep } from './newDep';
68
- // ... existing code ...
69
-
70
- function newFeature() {
71
- return newDep.process();
72
- }
73
- // ... existing code ...
74
- ```
75
-
76
- **Modifying existing code:**
77
- ```
78
- // ... existing code ...
79
- function existingFunc(param) {
80
- // Updated implementation
81
- const result = param * 2; // Changed from * 1
82
- return result;
83
- }
84
- // ... existing code ...
85
- ```
86
-
87
- **Deleting code (show what remains):**
88
- ```
89
- // ... existing code ...
90
- function keepThis() {
91
- return "stays";
92
- }
93
-
94
- // The function between these two was removed
95
-
96
- function alsoKeepThis() {
97
- return "also stays";
98
- }
99
- // ... existing code ...
100
- ```
101
-
102
- ## Fallback Behavior
103
-
104
- If Fast Apply API fails (timeout, network error, etc.):
105
- 1. Tool returns error message with details
106
- 2. **Fallback to native `edit` tool** with exact string matching
107
- 3. The `edit` tool requires matching exact text from the file
108
-
109
- **Note:** API failures are rare. Always try `fast_apply_edit` first.
110
-
111
- ## When to Use Native 'edit' Tool
112
-
113
- - **ONLY as fallback** when `fast_apply_edit` fails
114
- - When Fast Apply API is unavailable
115
- - When you need guaranteed exact string replacement
116
-
117
- ## When to Use 'write' Tool
118
-
119
- - **ONLY for creating NEW files**
120
- - Never use `fast_apply_edit` for file creation
121
- - Provide complete file content without lazy markers