opencode-fast-apply 2.0.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.
@@ -0,0 +1,121 @@
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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 JRedeker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # opencode-fast-apply
2
+
3
+ OpenCode plugin for Fast Apply - High-performance code editing with OpenAI-compatible APIs (LM Studio, Ollama).
4
+
5
+ ## Features
6
+
7
+ - **High-speed code editing** via OpenAI-compatible Fast Apply API (speed depends on your hardware and model)
8
+ - **Lazy edit markers** (`// ... existing code ...`) - no exact string matching needed
9
+ - **Unified diff output** with context for easy review
10
+ - **Graceful fallback** - suggests native `edit` tool on API failure
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
14
+
15
+ ## Installation
16
+
17
+ ### 1. Clone the repository
18
+
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
23
+ ```
24
+
25
+ ### 2. Configure your API endpoint
26
+
27
+ For **LM Studio** (default):
28
+ ```bash
29
+ export FAST_APPLY_URL="http://localhost:1234/v1"
30
+ export FAST_APPLY_MODEL="fastapply-1.5b"
31
+ export FAST_APPLY_API_KEY="optional-api-key"
32
+ ```
33
+
34
+ For **Ollama**:
35
+ ```bash
36
+ export FAST_APPLY_URL="http://localhost:11434/v1"
37
+ export FAST_APPLY_MODEL="codellama:7b"
38
+ export FAST_APPLY_API_KEY="optional-api-key"
39
+ ```
40
+
41
+ For **OpenAI**:
42
+ ```bash
43
+ export FAST_APPLY_URL="https://api.openai.com/v1"
44
+ export FAST_APPLY_MODEL="gpt-4"
45
+ export FAST_APPLY_API_KEY="sk-your-openai-key"
46
+ ```
47
+
48
+ **Note:** The plugin automatically handles URLs with or without `/v1` suffix.
49
+
50
+ ### 3. Add the plugin to your OpenCode config
51
+
52
+ Add to your global config (`~/.config/opencode/opencode.json`):
53
+
54
+ ```json
55
+ {
56
+ "plugin": [
57
+ "/path/to/fast-apply"
58
+ ],
59
+ "instructions": [
60
+ "/path/to/fast-apply/FAST_APPLY_INSTRUCTIONS.md"
61
+ ]
62
+ }
63
+ ```
64
+
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
+ ```
77
+
78
+ ### 4. Restart OpenCode
79
+
80
+ The `fast_apply_edit` tool will now be available.
81
+
82
+ ## Usage
83
+
84
+ The LLM can use `fast_apply_edit` for efficient partial file edits:
85
+
86
+ ```
87
+ fast_apply_edit({
88
+ target_filepath: "sth.ts",
89
+ instructions: "Add error handling for invalid tokens",
90
+ code_edit: `// ... existing code ...
91
+ function validateToken(token) {
92
+ if (!token) {
93
+ throw new Error("Token is required");
94
+ }
95
+ // ... existing code ...
96
+ }
97
+ // ... existing code ...`
98
+ })
99
+ ```
100
+
101
+ ### When to use `fast_apply_edit` vs `edit`
102
+
103
+ | Situation | Tool | Reason |
104
+ |-----------|------|--------|
105
+ | Small, exact replacement | `edit` | Fast, no API call |
106
+ | Large file (500+ lines) | `fast_apply_edit` | Handles partial snippets |
107
+ | Multiple scattered changes | `fast_apply_edit` | Batch efficiently |
108
+ | Whitespace-sensitive | `fast_apply_edit` | Forgiving with formatting |
109
+
110
+ ## Configuration
111
+
112
+ | Variable | Default | Description |
113
+ |----------|---------|-------------|
114
+ | `FAST_APPLY_API_KEY` | `optional-api-key` | API key (optional for local servers) |
115
+ | `FAST_APPLY_URL` | `http://localhost:1234/v1` | OpenAI-compatible API endpoint |
116
+ | `FAST_APPLY_MODEL` | `fastapply-1.5b` | Model name |
117
+ | `FAST_APPLY_TIMEOUT` | `30000` | Request timeout in ms |
118
+ | `FAST_APPLY_TEMPERATURE` | `0.05` | Temperature (0.0-2.0) |
119
+ | `FAST_APPLY_MAX_TOKENS` | `8000` | Maximum tokens in response |
120
+
121
+ ## How It Works
122
+
123
+ 1. Reads the original file content
124
+ 2. Escapes XML tags in code to prevent conflicts
125
+ 3. Sends system prompt + user prompt with `<instruction>`, `<code>`, and `<update>` to OpenAI-compatible API
126
+ 4. API intelligently merges the lazy edit markers with original code
127
+ 5. Extracts result from `<updated-code>` tags and unescapes XML
128
+ 6. Writes the merged result back to the file
129
+ 7. Returns a unified diff showing what changed
130
+
131
+ ## Performance
132
+
133
+ Performance varies based on your setup:
134
+
135
+ | Setup | Estimated Speed | Hardware Requirement |
136
+ |-------|----------------|---------------------|
137
+ | fastapply-1.5b (Q4) + RTX 4090 | 10,000-15,000 tok/s | High-end GPU |
138
+ | codellama:7b (Q4) + RTX 3060 | 3,000-5,000 tok/s | Mid-range GPU |
139
+ | codellama:7b (Q4) + CPU only | 50-200 tok/s | Modern CPU |
140
+ | OpenAI GPT-4 API | 100-500 tok/s | Network dependent |
141
+
142
+ **Factors affecting performance:**
143
+ - Model size (1.5B vs 7B vs 13B+ parameters)
144
+ - Quantization level (Q4 vs Q5 vs Q8 vs FP16)
145
+ - Hardware (GPU VRAM, CPU cores, RAM)
146
+ - Backend optimization (LM Studio vs Ollama)
147
+
148
+ ## Supported Backends
149
+
150
+ - **LM Studio** - Local inference server with GPU acceleration
151
+ - **Ollama** - Local LLM runtime with easy model management
152
+ - **OpenAI** - Cloud API with high reliability
153
+ - **Any OpenAI-compatible endpoint** - Custom servers and providers
154
+
155
+ ## Edge Cases Handled
156
+
157
+ - ✅ String literals containing `<updated-code>` tags
158
+ - ✅ Multiple XML-like tags in regex patterns
159
+ - ✅ Special characters (quotes, backslashes, unicode, SQL, HTML entities)
160
+ - ✅ Large files (500+ lines)
161
+ - ✅ Multiple scattered changes in single edit
162
+ - ✅ Complex nested structures
163
+ - ✅ Template strings with `${variable}`
164
+ - ✅ Whitespace and indentation preservation
165
+
166
+ ## Troubleshooting
167
+
168
+ ### API Connection Issues
169
+ ```bash
170
+ # Test your endpoint
171
+ curl -X POST http://localhost:1234/v1/chat/completions \
172
+ -H "Content-Type: application/json" \
173
+ -d '{"model":"fastapply-1.5b","messages":[{"role":"user","content":"test"}]}'
174
+ ```
175
+
176
+ ### Slow Performance
177
+ - Use smaller models (1.5B-3B parameters)
178
+ - Enable GPU acceleration in LM Studio/Ollama
179
+ - Use Q4 quantization for faster inference
180
+ - Increase `FAST_APPLY_MAX_TOKENS` if responses are truncated
181
+
182
+ ### Timeout Errors
183
+ ```bash
184
+ # Increase timeout for slower hardware
185
+ export FAST_APPLY_TIMEOUT="60000" # 60 seconds
186
+ ```
187
+
188
+ ## Contributing
189
+
190
+ Contributions welcome! This plugin could potentially be integrated into OpenCode core.
191
+
192
+ ## License
193
+
194
+ [MIT](LICENSE)
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OpenCode Fast Apply Plugin
3
+ *
4
+ * Integrates OpenAI-compatible Fast Apply API for 10x faster code editing.
5
+ * Supports LM Studio, Ollama, and other OpenAI-compatible endpoints.
6
+ * Uses lazy edit markers (// ... existing code ...) for partial file updates.
7
+ *
8
+ * @see https://github.com/tickernelz/opencode-fast-apply
9
+ */
10
+ import { type Plugin } from "@opencode-ai/plugin";
11
+ export declare const FastApplyPlugin: Plugin;
12
+ export default FastApplyPlugin;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
package/dist/index.js ADDED
@@ -0,0 +1,300 @@
1
+ /**
2
+ * OpenCode Fast Apply Plugin
3
+ *
4
+ * Integrates OpenAI-compatible Fast Apply API for 10x faster code editing.
5
+ * Supports LM Studio, Ollama, and other OpenAI-compatible endpoints.
6
+ * Uses lazy edit markers (// ... existing code ...) for partial file updates.
7
+ *
8
+ * @see https://github.com/tickernelz/opencode-fast-apply
9
+ */
10
+ import { tool } from "@opencode-ai/plugin";
11
+ import { createTwoFilesPatch } from "diff";
12
+ import { readFile, writeFile, access } from "fs/promises";
13
+ import { constants } from "fs";
14
+ // Get API key from environment (set in mcpm/jarvis config)
15
+ const FAST_APPLY_API_KEY = process.env.FAST_APPLY_API_KEY || "optional-api-key";
16
+ const FAST_APPLY_URL = (process.env.FAST_APPLY_URL || "http://localhost:1234/v1").replace(/\/v1\/?$/, "");
17
+ const FAST_APPLY_MODEL = process.env.FAST_APPLY_MODEL || "fastapply-1.5b";
18
+ const FAST_APPLY_TIMEOUT = parseInt(process.env.FAST_APPLY_TIMEOUT || "30000", 10);
19
+ const FAST_APPLY_TEMPERATURE = parseFloat(process.env.FAST_APPLY_TEMPERATURE || "0.05");
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.
28
+
29
+ <code>{original_code}</code>
30
+
31
+ <update>{update_snippet}</update>
32
+
33
+ Provide the complete updated code.`;
34
+ const UPDATED_CODE_START = "<updated-code>";
35
+ const UPDATED_CODE_END = "</updated-code>";
36
+ function escapeXmlTags(text) {
37
+ return text
38
+ .replace(/<updated-code>/g, "&lt;updated-code&gt;")
39
+ .replace(/<\/updated-code>/g, "&lt;/updated-code&gt;");
40
+ }
41
+ function unescapeXmlTags(text) {
42
+ return text
43
+ .replace(/&lt;updated-code&gt;/g, "<updated-code>")
44
+ .replace(/&lt;\/updated-code&gt;/g, "</updated-code>");
45
+ }
46
+ function extractUpdatedCode(raw) {
47
+ const stripped = raw.trim();
48
+ const start = stripped.indexOf(UPDATED_CODE_START);
49
+ const end = stripped.lastIndexOf(UPDATED_CODE_END);
50
+ if (start === -1 || end === -1 || end <= start) {
51
+ if (stripped.startsWith("```") && stripped.endsWith("```")) {
52
+ const lines = stripped.split("\n");
53
+ if (lines.length >= 2) {
54
+ return unescapeXmlTags(lines.slice(1, -1).join("\n"));
55
+ }
56
+ }
57
+ return unescapeXmlTags(stripped);
58
+ }
59
+ const inner = stripped.substring(start + UPDATED_CODE_START.length, end);
60
+ if (!inner || inner.trim().length === 0) {
61
+ throw new Error("Empty updated-code block");
62
+ }
63
+ return unescapeXmlTags(inner);
64
+ }
65
+ function generateUnifiedDiff(filepath, original, modified) {
66
+ const patch = createTwoFilesPatch(`a/${filepath}`, `b/${filepath}`, original, modified, "", "", { context: 3 });
67
+ if (!patch.includes("@@")) {
68
+ return "No changes detected";
69
+ }
70
+ return patch;
71
+ }
72
+ /**
73
+ * Count additions and deletions from a unified diff
74
+ */
75
+ function countChanges(diff) {
76
+ const lines = diff.split("\n");
77
+ let added = 0;
78
+ let removed = 0;
79
+ for (const line of lines) {
80
+ if (line.startsWith("+") && !line.startsWith("+++")) {
81
+ added++;
82
+ }
83
+ else if (line.startsWith("-") && !line.startsWith("---")) {
84
+ removed++;
85
+ }
86
+ }
87
+ return { added, removed };
88
+ }
89
+ /**
90
+ * Call OpenAI's Fast Apply API to merge code edits
91
+ */
92
+ async function callFastApply(originalCode, codeEdit, instructions) {
93
+ if (!FAST_APPLY_API_KEY) {
94
+ return {
95
+ success: false,
96
+ error: "FAST_APPLY_API_KEY not set. Get one at https://openai.com/api",
97
+ };
98
+ }
99
+ const controller = new AbortController();
100
+ const timeoutId = setTimeout(() => controller.abort(), FAST_APPLY_TIMEOUT);
101
+ try {
102
+ const escapedOriginalCode = escapeXmlTags(originalCode);
103
+ const escapedCodeEdit = escapeXmlTags(codeEdit);
104
+ const userContent = FAST_APPLY_USER_PROMPT
105
+ .replace("{instruction}", instructions || "Apply the requested code changes.")
106
+ .replace("{original_code}", escapedOriginalCode)
107
+ .replace("{update_snippet}", escapedCodeEdit);
108
+ const response = await fetch(`${FAST_APPLY_URL}/v1/chat/completions`, {
109
+ method: "POST",
110
+ headers: {
111
+ "Content-Type": "application/json",
112
+ Authorization: `Bearer ${FAST_APPLY_API_KEY}`,
113
+ },
114
+ body: JSON.stringify({
115
+ model: FAST_APPLY_MODEL,
116
+ messages: [
117
+ {
118
+ role: "system",
119
+ content: FAST_APPLY_SYSTEM_PROMPT,
120
+ },
121
+ {
122
+ role: "user",
123
+ content: userContent,
124
+ },
125
+ ],
126
+ temperature: FAST_APPLY_TEMPERATURE,
127
+ max_tokens: FAST_APPLY_MAX_TOKENS,
128
+ }),
129
+ signal: controller.signal,
130
+ });
131
+ clearTimeout(timeoutId);
132
+ if (!response.ok) {
133
+ const errorText = await response.text();
134
+ return {
135
+ success: false,
136
+ error: `Fast Apply API error (${response.status}): ${errorText}`,
137
+ };
138
+ }
139
+ const result = (await response.json());
140
+ const rawResponse = result.choices?.[0]?.message?.content;
141
+ if (!rawResponse) {
142
+ return {
143
+ success: false,
144
+ error: "Fast Apply API returned empty response",
145
+ };
146
+ }
147
+ const mergedCode = extractUpdatedCode(rawResponse);
148
+ return {
149
+ success: true,
150
+ content: mergedCode,
151
+ };
152
+ }
153
+ catch (err) {
154
+ clearTimeout(timeoutId);
155
+ const error = err;
156
+ if (error.name === "AbortError") {
157
+ return {
158
+ success: false,
159
+ error: `Fast Apply API timeout after ${FAST_APPLY_TIMEOUT}ms`,
160
+ };
161
+ }
162
+ return {
163
+ success: false,
164
+ error: `Fast Apply API request failed: ${error.message}`,
165
+ };
166
+ }
167
+ }
168
+ export const FastApplyPlugin = async ({ directory }) => {
169
+ if (!FAST_APPLY_API_KEY) {
170
+ console.warn("[fast-apply] FAST_APPLY_API_KEY not set - fast_apply_edit tool will be disabled");
171
+ }
172
+ else {
173
+ console.log(`[fast-apply] Plugin loaded with model: ${FAST_APPLY_MODEL} at ${FAST_APPLY_URL}`);
174
+ }
175
+ return {
176
+ tool: {
177
+ 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.`,
215
+ args: {
216
+ target_filepath: tool.schema
217
+ .string()
218
+ .describe("Path of the file to modify (relative to project root)"),
219
+ instructions: tool.schema
220
+ .string()
221
+ .describe("Brief first-person description of what you're changing (helps disambiguate)"),
222
+ code_edit: tool.schema
223
+ .string()
224
+ .describe('The code changes with "// ... existing code ..." markers for unchanged sections'),
225
+ },
226
+ async execute(args) {
227
+ const { target_filepath, instructions, code_edit } = args;
228
+ // Resolve file path relative to project directory
229
+ const filepath = target_filepath.startsWith("/")
230
+ ? target_filepath
231
+ : `${directory}/${target_filepath}`;
232
+ // Check if API key is available
233
+ if (!FAST_APPLY_API_KEY) {
234
+ return `Error: FAST_APPLY_API_KEY not configured.
235
+
236
+ To use fast_apply_edit, set the FAST_APPLY_API_KEY environment variable.
237
+ Get your API key at: https://openai.com/api
238
+
239
+ Alternatively, use the native 'edit' tool for this change.`;
240
+ }
241
+ // Read the original file
242
+ let originalCode;
243
+ try {
244
+ await access(filepath, constants.R_OK);
245
+ originalCode = await readFile(filepath, "utf-8");
246
+ }
247
+ catch (err) {
248
+ const error = err;
249
+ if (error.message.includes("ENOENT") || error.message.includes("no such file")) {
250
+ return `Error: File not found: ${target_filepath}
251
+
252
+ This tool is for EDITING EXISTING FILES ONLY.
253
+ For new file creation, use the 'write' tool instead.
254
+
255
+ Example:
256
+ write({
257
+ filePath: "${target_filepath}",
258
+ content: "your file content here"
259
+ })`;
260
+ }
261
+ return `Error reading file ${target_filepath}: ${error.message}`;
262
+ }
263
+ // Call OpenAI API to merge the edit
264
+ const result = await callFastApply(originalCode, code_edit, instructions);
265
+ 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.`;
271
+ }
272
+ const mergedCode = result.content;
273
+ // Write the merged result
274
+ try {
275
+ await writeFile(filepath, mergedCode, "utf-8");
276
+ }
277
+ catch (err) {
278
+ const error = err;
279
+ return `Error writing file ${target_filepath}: ${error.message}`;
280
+ }
281
+ // Generate unified diff
282
+ const diff = generateUnifiedDiff(target_filepath, originalCode, mergedCode);
283
+ // Calculate change stats
284
+ 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
+ \`\`\``;
294
+ },
295
+ }),
296
+ },
297
+ };
298
+ };
299
+ // Default export for OpenCode plugin loader
300
+ export default FastApplyPlugin;
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "opencode-fast-apply",
3
+ "version": "2.0.0",
4
+ "description": "OpenCode plugin for Fast Apply - High-performance code editing with OpenAI-compatible APIs (LM Studio, Ollama)",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "FAST_APPLY_INSTRUCTIONS.md",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "typecheck": "tsc --noEmit",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "dependencies": {
20
+ "@opencode-ai/plugin": "latest",
21
+ "diff": "^8.0.2"
22
+ },
23
+ "devDependencies": {
24
+ "@types/diff": "^7.0.2",
25
+ "@types/node": "^20.0.0",
26
+ "typescript": "^5.0.0"
27
+ },
28
+ "keywords": [
29
+ "opencode",
30
+ "plugin",
31
+ "fast-apply",
32
+ "code-editing",
33
+ "openai",
34
+ "lm-studio",
35
+ "ollama",
36
+ "ai",
37
+ "code-assistant",
38
+ "llm"
39
+ ],
40
+ "author": "tickernelz",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/tickernelz/opencode-fast-apply.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/tickernelz/opencode-fast-apply/issues"
47
+ },
48
+ "homepage": "https://github.com/tickernelz/opencode-fast-apply#readme",
49
+ "license": "MIT",
50
+ "engines": {
51
+ "node": ">=18.0.0"
52
+ }
53
+ }