opencode-thinking-fix 1.0.0 → 1.1.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/LICENSE.md +21 -0
- package/README.md +45 -17
- package/package.json +13 -1
- package/plugins/opencode-thinking-fix-universal.ts +2 -2
- package/proxy/proxy.js +1 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tbosancheros39
|
|
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
CHANGED
|
@@ -1,22 +1,49 @@
|
|
|
1
1
|
# opencode-thinking-fix
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-thinking-fix)
|
|
4
|
+
[](https://www.npmjs.com/package/opencode-thinking-fix)
|
|
5
|
+
[](https://github.com/tbosancheros39/opencode-thinking-fix/blob/main/LICENSE.md)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install opencode-thinking-fix
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
> Fix for the `reasoning_content` 400 error that kills multi-turn conversations with DeepSeek, Kimi, GLM, and MiMo in OpenCode.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [What problem this fixes](#what-problem-this-fixes)
|
|
17
|
+
- [Option 1: Plugin (stops the crashes)](#option-1-plugin-stops-the-crashes)
|
|
18
|
+
- [Option 2: Proxy (replays real reasoning)](#option-2-proxy-replays-real-reasoning)
|
|
19
|
+
- [Option 3: Watchdog (auto-recovery)](#option-3-watchdog-auto-recovery)
|
|
20
|
+
- [How they work together](#how-they-work-together)
|
|
21
|
+
- [Affected models](#affected-models)
|
|
22
|
+
- [Model routing](#model-routing)
|
|
23
|
+
- [Is it working?](#is-it-working)
|
|
24
|
+
- [Running tests](#running-tests)
|
|
25
|
+
- [This bug is everywhere](#this-bug-is-everywhere)
|
|
26
|
+
- [Files in this repo](#files-in-this-repo)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## What problem this fixes
|
|
31
|
+
|
|
32
|
+
You ask DeepSeek a question. It picks a tool, calls it, works fine. Then you ask a follow-up and you get this:
|
|
4
33
|
|
|
5
34
|
```
|
|
6
35
|
HTTP 400: The reasoning_content in the thinking mode must be passed back to the API
|
|
7
36
|
```
|
|
8
37
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
> *"Between two user messages, if the model performed a tool call, the intermediate assistant's reasoning_content must participate in the context concatenation and must be passed back to the API in all subsequent user interaction turns. If your code does not correctly pass back reasoning_content, the API will return a 400 error."*
|
|
38
|
+
DeepSeek V4 (and Kimi K2.7, GLM 5.x, MiMo V2.5) require that `reasoning_content` from every prior assistant turn gets included in subsequent API requests. The [docs](https://api-docs.deepseek.com/guides/thinking_mode) say it clearly: if you do not pass back `reasoning_content` correctly, the API returns a 400 error.
|
|
12
39
|
|
|
13
|
-
OpenCode's provider layer
|
|
40
|
+
OpenCode's provider layer drops this field. Three upstream PRs ([#24250](https://github.com/anomalyco/opencode/pull/24250), [#24428](https://github.com/anomalyco/opencode/pull/24428), [#24895](https://github.com/anomalyco/opencode/pull/24895)) tried to fix it. None merged. The field is non-standard per OpenAI, so both OpenCode and the AI SDK ignore it.
|
|
14
41
|
|
|
15
42
|
This repo fixes it. Three layers, pick what you need.
|
|
16
43
|
|
|
17
44
|
---
|
|
18
45
|
|
|
19
|
-
## Option 1:
|
|
46
|
+
## Option 1: plugin (stops the crashes)
|
|
20
47
|
|
|
21
48
|
One file. Drop it in `~/.config/opencode/plugins/` and restart:
|
|
22
49
|
|
|
@@ -25,21 +52,21 @@ mkdir -p ~/.config/opencode/plugins
|
|
|
25
52
|
cp plugins/opencode-thinking-fix-universal.ts ~/.config/opencode/plugins/
|
|
26
53
|
```
|
|
27
54
|
|
|
28
|
-
|
|
55
|
+
It scans outgoing messages for any assistant turn that already has `reasoning_content`. If it finds one (meaning you are using a reasoning model), it adds `reasoning_content: ""` to every assistant turn missing it. If it finds nothing (Qwen, GPT, Claude — they never produce this field), it does nothing.
|
|
29
56
|
|
|
30
57
|
It also handles `reasoning` for the OpenCode Go provider, and patches empty `content` fields that OpenAI-compatible SDKs sometimes omit.
|
|
31
58
|
|
|
32
59
|
No config file changes. No build step. OpenCode compiles `.ts` plugins when it starts.
|
|
33
60
|
|
|
34
|
-
**The catch:** the plugin fills in empty strings, not your model's actual prior thinking. DeepSeek, Kimi K2.5/K2.6, GLM, and MiMo accept empty strings fine — your conversation works but the model
|
|
61
|
+
**The catch:** the plugin fills in empty strings, not your model's actual prior thinking. DeepSeek, Kimi K2.5/K2.6, GLM, and MiMo accept empty strings fine — your conversation works but the model does not see its earlier reasoning. Kimi K2.7 Code rejects empty strings entirely, it needs the real text.
|
|
35
62
|
|
|
36
63
|
---
|
|
37
64
|
|
|
38
|
-
## Option 2:
|
|
65
|
+
## Option 2: proxy (replays real reasoning)
|
|
39
66
|
|
|
40
67
|
A Node.js proxy that catches API responses as they come back, pulls out the actual `reasoning_content` text, and caches it in memory. On the next request, it injects that real text back into the conversation history instead of empty strings.
|
|
41
68
|
|
|
42
|
-
Your model sees its full chain-of-thought from turn 1 on every subsequent turn.
|
|
69
|
+
Your model sees its full chain-of-thought from turn 1 on every subsequent turn. The difference is noticeable on complex multi-turn coding sessions.
|
|
43
70
|
|
|
44
71
|
### Two-proxy architecture
|
|
45
72
|
|
|
@@ -88,7 +115,7 @@ Then point OpenCode at it — in your `opencode.json`:
|
|
|
88
115
|
|
|
89
116
|
Zero npm dependencies. It uses `http`, `https`, and `url` — nothing else.
|
|
90
117
|
|
|
91
|
-
**Kimi K2.7 Code and OpenCode Go need this.** The rest of the models benefit from it but
|
|
118
|
+
**Kimi K2.7 Code and OpenCode Go need this.** The rest of the models benefit from it but do not technically require it.
|
|
92
119
|
|
|
93
120
|
---
|
|
94
121
|
|
|
@@ -114,7 +141,7 @@ OpenCode → [plugin patches missing reasoning_content/reasoning]
|
|
|
114
141
|
→ API
|
|
115
142
|
```
|
|
116
143
|
|
|
117
|
-
The plugin is the safety net. If the proxy goes down, the plugin still injects empty strings so you
|
|
144
|
+
The plugin is the safety net. If the proxy goes down, the plugin still injects empty strings so you do not get 400s. If the proxy is up, its cached text takes priority because the plugin sees the field is already filled in. Either way, your conversation does not break.
|
|
118
145
|
|
|
119
146
|
---
|
|
120
147
|
|
|
@@ -128,7 +155,7 @@ The plugin is the safety net. If the proxy goes down, the plugin still injects e
|
|
|
128
155
|
| GLM-5.x / Zhipu | Yes | Nice to have | Accepts `""` |
|
|
129
156
|
| MiMo V2.5 / MiniMax | Yes | Nice to have | Accepts `""` |
|
|
130
157
|
| OpenCode Go | Yes | Required | Uses `reasoning` field |
|
|
131
|
-
| Qwen, GPT, Claude, Gemini, Llama, Mistral | No | No |
|
|
158
|
+
| Qwen, GPT, Claude, Gemini, Llama, Mistral | No | No | Do not use reasoning_content |
|
|
132
159
|
|
|
133
160
|
---
|
|
134
161
|
|
|
@@ -138,6 +165,7 @@ The proxy auto-routes by model name prefix. All 15 supported prefixes:
|
|
|
138
165
|
|
|
139
166
|
| Prefix | Upstream | Reasoning |
|
|
140
167
|
|---|---|---|
|
|
168
|
+
| `deepseek-v4-pro` | `https://api.deepseek.com` | Yes |
|
|
141
169
|
| `deepseek` | `https://api.deepseek.com` | Yes |
|
|
142
170
|
| `kimi`, `moonshot` | `https://api.moonshot.ai/v1` | Yes |
|
|
143
171
|
| `glm`, `zhipu` | `https://open.bigmodel.cn/api/paas/v4` | Yes |
|
|
@@ -155,7 +183,7 @@ Unknown models fall back to `https://api.deepseek.com` with reasoning disabled.
|
|
|
155
183
|
|
|
156
184
|
## Is it working?
|
|
157
185
|
|
|
158
|
-
Turn 1
|
|
186
|
+
Turn 1 will not show anything — there is no history to patch yet. That is normal.
|
|
159
187
|
|
|
160
188
|
Turn 2+, check the console:
|
|
161
189
|
|
|
@@ -170,7 +198,7 @@ Turn 2+, check the console:
|
|
|
170
198
|
[Cache] session abcdefgh: replayed reasoning for turn 0 (1842 chars)
|
|
171
199
|
```
|
|
172
200
|
|
|
173
|
-
If nothing shows up, either it
|
|
201
|
+
If nothing shows up, either it is a non-reasoning model (correct) or the plugin did not load (check for `[ThinkingFix] Plugin loaded` at startup).
|
|
174
202
|
|
|
175
203
|
Quick proxy health check:
|
|
176
204
|
|
|
@@ -190,7 +218,7 @@ journalctl --user -u reasoning-cache-go.service -f
|
|
|
190
218
|
|
|
191
219
|
---
|
|
192
220
|
|
|
193
|
-
## Running
|
|
221
|
+
## Running tests
|
|
194
222
|
|
|
195
223
|
```bash
|
|
196
224
|
npm test
|
|
@@ -207,7 +235,7 @@ The proxy tests cover 15 cases: route resolution for all model prefixes, `patchR
|
|
|
207
235
|
|
|
208
236
|
## This bug is everywhere
|
|
209
237
|
|
|
210
|
-
OpenCode
|
|
238
|
+
OpenCode is not the only tool that drops `reasoning_content`. Here is a partial list of places this same bug shows up:
|
|
211
239
|
|
|
212
240
|
**OpenCode (anomalyco/opencode):** [#24190](https://github.com/anomalyco/opencode/issues/24190), [#24104](https://github.com/anomalyco/opencode/issues/24104), [#24722](https://github.com/anomalyco/opencode/issues/24722), [#25311](https://github.com/anomalyco/opencode/issues/25311), [#25134](https://github.com/anomalyco/opencode/issues/25134), [#25000](https://github.com/anomalyco/opencode/issues/25000), [#24124](https://github.com/anomalyco/opencode/issues/24124), [#24130](https://github.com/anomalyco/opencode/issues/24130), [#24261](https://github.com/anomalyco/opencode/issues/24261), [#24442](https://github.com/anomalyco/opencode/issues/24442), [#24569](https://github.com/anomalyco/opencode/issues/24569)
|
|
213
241
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-thinking-fix",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
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",
|
|
@@ -19,6 +19,14 @@
|
|
|
19
19
|
"type": "git",
|
|
20
20
|
"url": "https://github.com/tbosancheros39/opencode-thinking-fix.git"
|
|
21
21
|
},
|
|
22
|
+
"oc-plugin": ["server"],
|
|
23
|
+
"main": "./plugins/opencode-thinking-fix-universal.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
"./server": {
|
|
26
|
+
"import": "./plugins/opencode-thinking-fix-universal.ts",
|
|
27
|
+
"types": "./plugins/opencode-thinking-fix-universal.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
22
30
|
"files": [
|
|
23
31
|
"plugins/",
|
|
24
32
|
"proxy/",
|
|
@@ -26,9 +34,13 @@
|
|
|
26
34
|
"watchdog/",
|
|
27
35
|
"systemd/",
|
|
28
36
|
"README.md",
|
|
37
|
+
"LICENSE.md",
|
|
29
38
|
".gitignore"
|
|
30
39
|
],
|
|
31
40
|
"scripts": {
|
|
32
41
|
"test": "node tests/test-plugin.js && node tests/test-proxy.js"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@opencode-ai/plugin": "^1.3.0"
|
|
33
45
|
}
|
|
34
46
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Plugin } from '@opencode-ai/plugin'
|
|
1
|
+
import type { Plugin, PluginModule } from '@opencode-ai/plugin'
|
|
2
2
|
|
|
3
3
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
4
|
// CONFIRMED FACTS (cross-validated across DeepSeek V4 Pro, Claude MAX, Kimi
|
|
@@ -103,4 +103,4 @@ export const ThinkingFixPlugin: Plugin = async ({ client }) => {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
export default ThinkingFixPlugin
|
|
106
|
+
export default { server: ThinkingFixPlugin } satisfies PluginModule
|
package/proxy/proxy.js
CHANGED
|
@@ -37,6 +37,7 @@ const UPSTREAM_URL = process.env.UPSTREAM_URL || ''
|
|
|
37
37
|
// Each entry: { base: upstream API base URL, reasoning: does this model emit reasoning_content? }
|
|
38
38
|
// Prefix-based matching: "deepseek-v4-pro" matches the "deepseek" prefix
|
|
39
39
|
const ROUTES = {
|
|
40
|
+
'deepseek-v4-pro': { base: 'https://api.deepseek.com', reasoning: true },
|
|
40
41
|
deepseek: { base: 'https://api.deepseek.com', reasoning: true },
|
|
41
42
|
kimi: { base: 'https://api.moonshot.ai/v1', reasoning: true },
|
|
42
43
|
moonshot: { base: 'https://api.moonshot.ai/v1', reasoning: true },
|