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.
- package/FAST_APPLY_INSTRUCTIONS.md +121 -0
- package/LICENSE +21 -0
- package/README.md +194 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +300 -0
- package/package.json +53 -0
|
@@ -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)
|
package/dist/index.d.ts
ADDED
|
@@ -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, "<updated-code>")
|
|
39
|
+
.replace(/<\/updated-code>/g, "</updated-code>");
|
|
40
|
+
}
|
|
41
|
+
function unescapeXmlTags(text) {
|
|
42
|
+
return text
|
|
43
|
+
.replace(/<updated-code>/g, "<updated-code>")
|
|
44
|
+
.replace(/<\/updated-code>/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
|
+
}
|