opencode-thinking-fix 1.1.0 → 1.1.2
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 +94 -7
- package/package.json +5 -3
- package/proxy/proxy.js +34 -11
package/README.md
CHANGED
|
@@ -9,7 +9,47 @@
|
|
|
9
9
|
npm install opencode-thinking-fix
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
> Fix for the `reasoning_content` 400 error that kills multi-turn conversations with DeepSeek, Kimi, GLM, and
|
|
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
|
+
---
|
|
15
|
+
|
|
16
|
+
## Quick Install
|
|
17
|
+
|
|
18
|
+
**This is an OpenCode plugin. Install it inside OpenCode — no terminal needed.**
|
|
19
|
+
|
|
20
|
+
### Method 1: TUI (press Ctrl+P while OpenCode is running)
|
|
21
|
+
|
|
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 — done. Restart OpenCode.
|
|
27
|
+
|
|
28
|
+
### Method 2: CLI (shell command)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
opencode plugin opencode-thinking-fix
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For specific version:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
opencode plugin opencode-thinking-fix@1.1.0
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Method 3: Manual config (add to opencode.json)
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"plugin": ["opencode-thinking-fix"]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Config file location:
|
|
49
|
+
- **Linux/macOS:** `~/.config/opencode/opencode.json` (global) or `.opencode/opencode.json` (project)
|
|
50
|
+
- **Windows:** `%APPDATA%/OpenCode/opencode.json` (global) or `.opencode/opencode.json` (project)
|
|
51
|
+
|
|
52
|
+
Restart OpenCode after adding. You should see `[ThinkingFix] Plugin loaded` at startup.
|
|
13
53
|
|
|
14
54
|
## Table of Contents
|
|
15
55
|
|
|
@@ -45,7 +85,13 @@ This repo fixes it. Three layers, pick what you need.
|
|
|
45
85
|
|
|
46
86
|
## Option 1: plugin (stops the crashes)
|
|
47
87
|
|
|
48
|
-
|
|
88
|
+
### Install via npm (recommended)
|
|
89
|
+
|
|
90
|
+
See [Quick Install](#quick-install) above — use OpenCode TUI (`Ctrl+P`) or CLI (`opencode plugin opencode-thinking-fix`).
|
|
91
|
+
|
|
92
|
+
### Manual install (for local development)
|
|
93
|
+
|
|
94
|
+
Drop the plugin file in your OpenCode plugins directory and restart:
|
|
49
95
|
|
|
50
96
|
```bash
|
|
51
97
|
mkdir -p ~/.config/opencode/plugins
|
|
@@ -80,13 +126,15 @@ The proxy runs on **two ports**:
|
|
|
80
126
|
Port 3457 auto-routes based on model name using the built-in route table. Port 3458 is a fixed-upstream proxy specifically for the OpenCode Go provider, which uses `delta.reasoning` (not `reasoning_content`) in its SSE streams. Both are handled by the same `proxy.js` binary — just different environment variables.
|
|
81
127
|
|
|
82
128
|
```bash
|
|
83
|
-
#
|
|
129
|
+
# Linux / macOS / Windows (Node.js required)
|
|
84
130
|
node proxy/proxy.js
|
|
85
131
|
|
|
86
|
-
#
|
|
132
|
+
# OpenCode Go proxy
|
|
87
133
|
PORT=3458 UPSTREAM_URL=https://opencode.ai/zen/go/v1 node proxy/proxy.js
|
|
88
134
|
```
|
|
89
135
|
|
|
136
|
+
> **Windows PowerShell:** use `$env:PORT=3457; node proxy/proxy.js` (PowerShell) or `set PORT=3457 && node proxy/proxy.js` (CMD).
|
|
137
|
+
|
|
90
138
|
### Install as systemd services (auto-start at boot)
|
|
91
139
|
|
|
92
140
|
```bash
|
|
@@ -115,6 +163,8 @@ Then point OpenCode at it — in your `opencode.json`:
|
|
|
115
163
|
|
|
116
164
|
Zero npm dependencies. It uses `http`, `https`, and `url` — nothing else.
|
|
117
165
|
|
|
166
|
+
**Interleaved thinking support:** GLM-5+ and MiniMax-M3 emit reasoning AFTER content in the same turn (interleaved thinking between tool calls). The proxy accumulates ALL reasoning across an entire assistant turn and flushes only on `finish_reason` — never on `delta.content` arrival. This prevents split/lost reasoning blocks.
|
|
167
|
+
|
|
118
168
|
**Kimi K2.7 Code and OpenCode Go need this.** The rest of the models benefit from it but do not technically require it.
|
|
119
169
|
|
|
120
170
|
---
|
|
@@ -153,9 +203,10 @@ The plugin is the safety net. If the proxy goes down, the plugin still injects e
|
|
|
153
203
|
| Kimi K2.5 / K2.6 | Yes | Nice to have | Accepts `""` |
|
|
154
204
|
| **Kimi K2.7 Code** | **Not enough alone** | **Required** | Needs real text |
|
|
155
205
|
| GLM-5.x / Zhipu | Yes | Nice to have | Accepts `""` |
|
|
156
|
-
| MiMo V2.5 / MiniMax | Yes | Nice to have | Accepts `""` |
|
|
206
|
+
| MiMo V2.5 / MiniMax | Yes | Nice to have | Accepts `""` (default mode embeds `<think>` in `content`) |
|
|
207
|
+
| **MiniMax-M3** | Yes | **Recommended** | `reasoning_details[]` array; ~40% quality loss if stripped. Proxy injects `reasoning_split:true` to keep thinking separate from content. |
|
|
157
208
|
| OpenCode Go | Yes | Required | Uses `reasoning` field |
|
|
158
|
-
| Qwen, GPT, Claude, Gemini, Llama, Mistral | No | No |
|
|
209
|
+
| Qwen, GPT, Claude, Gemini, Llama, Mistral | No | No | No reasoning_content |
|
|
159
210
|
|
|
160
211
|
---
|
|
161
212
|
|
|
@@ -277,4 +328,40 @@ systemd/
|
|
|
277
328
|
|
|
278
329
|
## Tested on
|
|
279
330
|
|
|
280
|
-
|
|
331
|
+
| Platform | Plugin | Proxy | Watchdog | Systemd |
|
|
332
|
+
|----------|--------|-------|----------|---------|
|
|
333
|
+
| **Linux** (Kubuntu 24.04) | ✅ | ✅ | ✅ (bash) | ✅ |
|
|
334
|
+
| **macOS** | ✅ | ✅ | ✅ (bash) | ❌ (use launchd) |
|
|
335
|
+
| **Windows** | ✅ | ✅ | ❌ (bash) | ❌ |
|
|
336
|
+
|
|
337
|
+
OpenCode v1.17.9+, DeepSeek V4 Pro, Kimi K2.5/K2.6/K2.7, GLM-5.x, MiMo V2.5, MiniMax-M3, OpenCode Go.
|
|
338
|
+
|
|
339
|
+
### Windows notes
|
|
340
|
+
|
|
341
|
+
**Plugin and proxy work fully on Windows.** The proxy (`proxy.js`) uses only Node.js built-in modules (`http`, `https`, `url`) — zero platform-specific code. Start it with:
|
|
342
|
+
|
|
343
|
+
```powershell
|
|
344
|
+
# PowerShell
|
|
345
|
+
$env:PORT=3457; node proxy\proxy.js
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Watchdog and systemd are Linux-only.** For Windows auto-restart, use **Task Scheduler** or **NSSM** (Non-Sucking Service Manager) to run the proxy as a Windows service:
|
|
349
|
+
|
|
350
|
+
```powershell
|
|
351
|
+
# Using NSSM (install once: winget install nssm)
|
|
352
|
+
nssm install ReasoningCacheProxy node.exe proxy\proxy.js
|
|
353
|
+
nssm set ReasoningCacheProxy AppDirectory C:\path\to\opencode-thinking-fix
|
|
354
|
+
nssm set ReasoningCacheProxy AppEnvironmentExtra PORT=3457
|
|
355
|
+
nssm start ReasoningCacheProxy
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Repeat for the Go proxy on PORT=3458 with `UPSTREAM_URL=https://opencode.ai/zen/go/v1`.
|
|
359
|
+
|
|
360
|
+
**OpenCode config paths on Windows:**
|
|
361
|
+
|
|
362
|
+
| Scope | Path |
|
|
363
|
+
|-------|------|
|
|
364
|
+
| Global | `%APPDATA%\OpenCode\opencode.json` |
|
|
365
|
+
| Project | `<project>\.opencode\opencode.json` |
|
|
366
|
+
| Plugins dir | `%APPDATA%\OpenCode\plugins\` |
|
|
367
|
+
| npm cache | `%LOCALAPPDATA%\opencode\node_modules\` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-thinking-fix",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Fix DeepSeek/Kimi/GLM/MiMo reasoning_content 400 errors in OpenCode. Plugin + proxy + watchdog.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "tbosancheros39",
|
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
],
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/tbosancheros39/opencode-thinking-fix.git"
|
|
20
|
+
"url": "git+https://github.com/tbosancheros39/opencode-thinking-fix.git"
|
|
21
21
|
},
|
|
22
|
-
"oc-plugin": [
|
|
22
|
+
"oc-plugin": [
|
|
23
|
+
"server"
|
|
24
|
+
],
|
|
23
25
|
"main": "./plugins/opencode-thinking-fix-universal.ts",
|
|
24
26
|
"exports": {
|
|
25
27
|
"./server": {
|
package/proxy/proxy.js
CHANGED
|
@@ -115,6 +115,16 @@ function patchRequestBody(body, sessionId) {
|
|
|
115
115
|
modified = true
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// MiniMax-M3 reasoning_details array format (reasoning_split: true)
|
|
119
|
+
if (!msg.reasoning_details && !msg.reasoning_content) {
|
|
120
|
+
const cached = sessionCache.get(assistantIndex)
|
|
121
|
+
if (cached) {
|
|
122
|
+
msg.reasoning_details = [{ text: cached, type: 'thinking' }]
|
|
123
|
+
console.log(`[Cache] session ${sessionId.slice(-8)}: replayed reasoning_details for turn ${assistantIndex} (${cached.length} chars)`)
|
|
124
|
+
modified = true
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
118
128
|
// OpenCode Go uses reasoning instead of reasoning_content
|
|
119
129
|
if (!msg.reasoning) {
|
|
120
130
|
const cached = sessionCache.get(assistantIndex)
|
|
@@ -162,28 +172,31 @@ function createStreamParser(sessionId, onComplete) {
|
|
|
162
172
|
const delta = parsed.choices?.[0]?.delta
|
|
163
173
|
if (!delta) continue
|
|
164
174
|
|
|
165
|
-
// reasoning_content delta (DeepSeek/Kimi/GLM
|
|
175
|
+
// reasoning_content delta (DeepSeek/Kimi/GLM/MiniMax)
|
|
166
176
|
if (typeof delta.reasoning_content === 'string') {
|
|
167
177
|
reasoningBuffer += delta.reasoning_content
|
|
168
178
|
inReasoning = true
|
|
169
179
|
}
|
|
170
180
|
|
|
181
|
+
// reasoning_details array (MiniMax-M3 with reasoning_split: true)
|
|
182
|
+
if (Array.isArray(delta.reasoning_details)) {
|
|
183
|
+
for (const detail of delta.reasoning_details) {
|
|
184
|
+
if (typeof detail.text === 'string') {
|
|
185
|
+
reasoningBuffer += detail.text
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
inReasoning = true
|
|
189
|
+
}
|
|
190
|
+
|
|
171
191
|
// OpenCode Go uses delta.reasoning instead of reasoning_content
|
|
172
192
|
if (typeof delta.reasoning === 'string') {
|
|
173
193
|
reasoningBuffer += delta.reasoning
|
|
174
194
|
inReasoning = true
|
|
175
195
|
}
|
|
176
196
|
|
|
177
|
-
// When content starts after reasoning, the reasoning block is done
|
|
178
|
-
if (typeof delta.content === 'string' && delta.content && inReasoning) {
|
|
179
|
-
if (reasoningBuffer) {
|
|
180
|
-
onComplete(assistantIndex, reasoningBuffer)
|
|
181
|
-
reasoningBuffer = ''
|
|
182
|
-
inReasoning = false
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
197
|
// finish_reason means the assistant turn is complete
|
|
198
|
+
// Only flush here — NOT on content appearance (interleaved thinking
|
|
199
|
+
// emits reasoning AFTER content on GLM-5+ and MiniMax-M3)
|
|
187
200
|
if (parsed.choices?.[0]?.finish_reason) {
|
|
188
201
|
if (reasoningBuffer) {
|
|
189
202
|
onComplete(assistantIndex, reasoningBuffer)
|
|
@@ -244,7 +257,17 @@ const server = http.createServer((req, res) => {
|
|
|
244
257
|
// Patch request body if this is a reasoning provider
|
|
245
258
|
let bodyToSend = rawBody
|
|
246
259
|
if (shouldPatch && rawBody.length > 0) {
|
|
247
|
-
|
|
260
|
+
// Inject reasoning_split for MiniMax models (avoids <think> tags in content)
|
|
261
|
+
const isMiniMax = modelName.toLowerCase().startsWith('minimax') || modelName.toLowerCase().startsWith('mimo')
|
|
262
|
+
let bodyStr = rawBody.toString('utf8')
|
|
263
|
+
if (isMiniMax) {
|
|
264
|
+
try {
|
|
265
|
+
const bj = JSON.parse(bodyStr)
|
|
266
|
+
bj.reasoning_split = true
|
|
267
|
+
bodyStr = JSON.stringify(bj)
|
|
268
|
+
} catch { /* pass */ }
|
|
269
|
+
}
|
|
270
|
+
const patched = patchRequestBody(bodyStr, sessionId)
|
|
248
271
|
bodyToSend = Buffer.from(patched, 'utf8')
|
|
249
272
|
}
|
|
250
273
|
|