opencode-thinking-fix 1.1.4 → 1.1.5
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 +18 -10
- package/package.json +1 -1
- package/proxy/proxy.js +43 -11
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ npm install opencode-thinking-fix
|
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
> Fix for the `reasoning_content` 400 error that kills multi-turn conversations with DeepSeek, Kimi, GLM, MiMo, and MiniMax-M3 in OpenCode.
|
|
13
|
+
>
|
|
14
|
+
> Docs: [OpenCode Plugins](https://opencode.ai/docs/plugins)
|
|
13
15
|
|
|
14
16
|
---
|
|
15
17
|
|
|
@@ -17,13 +19,15 @@ npm install opencode-thinking-fix
|
|
|
17
19
|
|
|
18
20
|
**This is an OpenCode plugin. Install it inside OpenCode — no terminal needed.**
|
|
19
21
|
|
|
20
|
-
### Method 1: TUI (press Ctrl+P while OpenCode is running)
|
|
22
|
+
### Method 1: TUI (press `Ctrl+P` while OpenCode is running)
|
|
21
23
|
|
|
22
|
-
1. Press `Ctrl+P` to open the command palette
|
|
23
|
-
2. Type `install plugin` and press Enter
|
|
24
|
-
3. Press `Tab` to switch scope to **Global** (recommended — works across all projects)
|
|
25
|
-
4. Type `opencode-thinking-fix
|
|
26
|
-
5. Press Enter
|
|
24
|
+
1. Press `Ctrl+P` to open the command palette.
|
|
25
|
+
2. Type `install plugin` and press `Enter`.
|
|
26
|
+
3. Press `Tab` to switch the install scope to **Global** (recommended — works across all projects).
|
|
27
|
+
4. Type `opencode-thinking-fix`.
|
|
28
|
+
5. Press `Enter`. Restart OpenCode.
|
|
29
|
+
|
|
30
|
+
You should see `[ThinkingFix] Plugin loaded — universal reasoning_content fix active` at startup.
|
|
27
31
|
|
|
28
32
|
### Method 2: CLI (shell command)
|
|
29
33
|
|
|
@@ -31,13 +35,15 @@ npm install opencode-thinking-fix
|
|
|
31
35
|
opencode plugin opencode-thinking-fix
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
For specific version:
|
|
38
|
+
For a specific version:
|
|
35
39
|
|
|
36
40
|
```bash
|
|
37
|
-
opencode plugin opencode-thinking-fix@1.1.
|
|
41
|
+
opencode plugin opencode-thinking-fix@1.1.4
|
|
38
42
|
```
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
Restart OpenCode after installing.
|
|
45
|
+
|
|
46
|
+
### Method 3: Manual config (add to `opencode.json`)
|
|
41
47
|
|
|
42
48
|
```json
|
|
43
49
|
{
|
|
@@ -51,7 +57,9 @@ Config file location:
|
|
|
51
57
|
|
|
52
58
|
Restart OpenCode after adding. You should see `[ThinkingFix] Plugin loaded` at startup.
|
|
53
59
|
|
|
54
|
-
|
|
60
|
+
See also: [OpenCode plugin docs](https://opencode.ai/docs/plugins)
|
|
61
|
+
|
|
62
|
+
---
|
|
55
63
|
|
|
56
64
|
- [What problem this fixes](#what-problem-this-fixes)
|
|
57
65
|
- [Option 1: Plugin (stops the crashes)](#option-1-plugin-stops-the-crashes)
|
package/package.json
CHANGED
package/proxy/proxy.js
CHANGED
|
@@ -155,9 +155,13 @@ function createStreamParser(sessionId, onComplete) {
|
|
|
155
155
|
buffer += chunk.toString()
|
|
156
156
|
const lines = buffer.split('\n')
|
|
157
157
|
buffer = lines.pop() // keep incomplete last line
|
|
158
|
+
const outLines = []
|
|
158
159
|
|
|
159
160
|
for (const line of lines) {
|
|
160
|
-
if (!line.startsWith('data: '))
|
|
161
|
+
if (!line.startsWith('data: ')) {
|
|
162
|
+
outLines.push(line)
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
161
165
|
const data = line.slice(6).trim()
|
|
162
166
|
if (data === '[DONE]') {
|
|
163
167
|
// Flush accumulated reasoning for this turn
|
|
@@ -165,12 +169,16 @@ function createStreamParser(sessionId, onComplete) {
|
|
|
165
169
|
onComplete(assistantIndex, reasoningBuffer)
|
|
166
170
|
reasoningBuffer = ''
|
|
167
171
|
}
|
|
172
|
+
outLines.push(line)
|
|
168
173
|
continue
|
|
169
174
|
}
|
|
170
175
|
try {
|
|
171
176
|
const parsed = JSON.parse(data)
|
|
172
177
|
const delta = parsed.choices?.[0]?.delta
|
|
173
|
-
if (!delta)
|
|
178
|
+
if (!delta) {
|
|
179
|
+
outLines.push(line)
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
174
182
|
|
|
175
183
|
// reasoning_content delta (DeepSeek/Kimi/GLM/MiniMax)
|
|
176
184
|
if (typeof delta.reasoning_content === 'string') {
|
|
@@ -194,6 +202,17 @@ function createStreamParser(sessionId, onComplete) {
|
|
|
194
202
|
inReasoning = true
|
|
195
203
|
}
|
|
196
204
|
|
|
205
|
+
// content may contain embedded <think> tags when reasoning_split is not
|
|
206
|
+
// honored (MiniMax fallback). Extract them into reasoningBuffer.
|
|
207
|
+
if (typeof delta.content === 'string') {
|
|
208
|
+
const thinkMatch = delta.content.match(/<think>([\s\S]*?)<\/think>/)
|
|
209
|
+
if (thinkMatch) {
|
|
210
|
+
reasoningBuffer += thinkMatch[1]
|
|
211
|
+
inReasoning = true
|
|
212
|
+
delta.content = delta.content.replace(/<think>[\s\S]*?<\/think>/, '').trimStart()
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
197
216
|
// finish_reason means the assistant turn is complete
|
|
198
217
|
// Only flush here — NOT on content appearance (interleaved thinking
|
|
199
218
|
// emits reasoning AFTER content on GLM-5+ and MiniMax-M3)
|
|
@@ -205,10 +224,16 @@ function createStreamParser(sessionId, onComplete) {
|
|
|
205
224
|
}
|
|
206
225
|
assistantIndex++
|
|
207
226
|
}
|
|
227
|
+
|
|
228
|
+
// Re-serialize the possibly-modified chunk and forward it
|
|
229
|
+
outLines.push('data: ' + JSON.stringify(parsed))
|
|
208
230
|
} catch {
|
|
209
|
-
// malformed SSE chunk,
|
|
231
|
+
// malformed SSE chunk, forward unchanged
|
|
232
|
+
outLines.push(line)
|
|
210
233
|
}
|
|
211
234
|
}
|
|
235
|
+
|
|
236
|
+
return outLines.join('\n') + '\n'
|
|
212
237
|
},
|
|
213
238
|
flush() {
|
|
214
239
|
if (reasoningBuffer) {
|
|
@@ -240,16 +265,22 @@ const server = http.createServer((req, res) => {
|
|
|
240
265
|
// Determine upstream route
|
|
241
266
|
let modelName = ''
|
|
242
267
|
let routeTarget = DEFAULT_ROUTE
|
|
268
|
+
|
|
269
|
+
// Always parse model name from body so provider-specific patches work
|
|
270
|
+
// even in fixed-upstream mode (OpenCode Go)
|
|
271
|
+
if (isPost && isJson && rawBody.length > 0) {
|
|
272
|
+
try {
|
|
273
|
+
const bodyJson = JSON.parse(rawBody.toString('utf8'))
|
|
274
|
+
modelName = bodyJson.model || ''
|
|
275
|
+
} catch { /* body not JSON yet */ }
|
|
276
|
+
}
|
|
277
|
+
|
|
243
278
|
if (UPSTREAM_URL) {
|
|
244
279
|
// Fixed upstream mode (e.g. OpenCode Go)
|
|
245
280
|
routeTarget = { base: UPSTREAM_URL, reasoning: true }
|
|
246
|
-
} else
|
|
281
|
+
} else {
|
|
247
282
|
// Model-based routing mode
|
|
248
|
-
|
|
249
|
-
const bodyJson = JSON.parse(rawBody.toString('utf8'))
|
|
250
|
-
modelName = bodyJson.model || ''
|
|
251
|
-
routeTarget = route(modelName)
|
|
252
|
-
} catch { /* body not JSON yet, use defaults */ }
|
|
283
|
+
routeTarget = route(modelName)
|
|
253
284
|
}
|
|
254
285
|
const upstream = url.parse(routeTarget.base)
|
|
255
286
|
const shouldPatch = isPost && isJson && routeTarget.reasoning
|
|
@@ -329,8 +360,9 @@ const server = http.createServer((req, res) => {
|
|
|
329
360
|
})
|
|
330
361
|
|
|
331
362
|
proxyRes.on('data', chunk => {
|
|
332
|
-
parser.feed(
|
|
333
|
-
|
|
363
|
+
// parser.feed returns the (possibly modified) SSE text to forward
|
|
364
|
+
const forwardChunk = parser.feed(chunk)
|
|
365
|
+
res.write(forwardChunk)
|
|
334
366
|
})
|
|
335
367
|
|
|
336
368
|
proxyRes.on('end', () => {
|