opencode-thinking-fix 1.1.3 → 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 +54 -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
|
|
@@ -262,10 +293,21 @@ const server = http.createServer((req, res) => {
|
|
|
262
293
|
const bj = JSON.parse(bodyStr)
|
|
263
294
|
|
|
264
295
|
// Strip unsupported params for Kimi/Moonshot thinking mode
|
|
296
|
+
// Moonshot hardcodes sampling params for thinking models and rejects
|
|
297
|
+
// explicit values. Docs: https://platform.kimi.ai/docs/api/models-overview
|
|
265
298
|
const isKimi = modelName.toLowerCase().startsWith('kimi') || modelName.toLowerCase().startsWith('moonshot')
|
|
266
299
|
if (isKimi) {
|
|
300
|
+
delete bj.temperature
|
|
267
301
|
delete bj.top_p
|
|
268
302
|
delete bj.top_k
|
|
303
|
+
delete bj.presence_penalty
|
|
304
|
+
delete bj.frequency_penalty
|
|
305
|
+
delete bj.n
|
|
306
|
+
// K2.7 thinking is always on; do not send thinking/reasoning_effort controls
|
|
307
|
+
if (modelName.toLowerCase().startsWith('kimi-k2.7')) {
|
|
308
|
+
delete bj.thinking
|
|
309
|
+
delete bj.reasoning_effort
|
|
310
|
+
}
|
|
269
311
|
}
|
|
270
312
|
|
|
271
313
|
// Inject reasoning_split for MiniMax models (avoids <think> tags in content)
|
|
@@ -318,8 +360,9 @@ const server = http.createServer((req, res) => {
|
|
|
318
360
|
})
|
|
319
361
|
|
|
320
362
|
proxyRes.on('data', chunk => {
|
|
321
|
-
parser.feed(
|
|
322
|
-
|
|
363
|
+
// parser.feed returns the (possibly modified) SSE text to forward
|
|
364
|
+
const forwardChunk = parser.feed(chunk)
|
|
365
|
+
res.write(forwardChunk)
|
|
323
366
|
})
|
|
324
367
|
|
|
325
368
|
proxyRes.on('end', () => {
|