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 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
- You ask DeepSeek or Kimi a question. It picks a tool, calls it, works fine. Then you ask a follow-up and it dies:
3
+ [![npm version](https://img.shields.io/npm/v/opencode-thinking-fix)](https://www.npmjs.com/package/opencode-thinking-fix)
4
+ [![npm downloads](https://img.shields.io/npm/dm/opencode-thinking-fix)](https://www.npmjs.com/package/opencode-thinking-fix)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/tbosancheros39/opencode-thinking-fix/blob/main/LICENSE.md)
6
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](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
- Here's what's happening. DeepSeek V4 (and Kimi K2.7, GLM 5.x, MiMo V2.5) requires that `reasoning_content` from every prior assistant turn gets included in subsequent API requests. Their [docs](https://api-docs.deepseek.com/guides/thinking_mode) spell this out directly:
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 strips it. Three PRs tried to fix this upstream ([#24250](https://github.com/anomalyco/opencode/pull/24250), [#24428](https://github.com/anomalyco/opencode/pull/24428), [#24895](https://github.com/anomalyco/opencode/pull/24895)) — none got merged. The field is non-standard per OpenAI, so both OpenCode and the AI SDK punt on it.
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: the plugin (stops the crashes)
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
- What it does: scans outgoing messages for any assistant turn that already has `reasoning_content`. If it finds one (meaning you're 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.
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 doesn't see its earlier reasoning. Kimi K2.7 Code rejects empty strings entirely, it needs the real text.
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: the proxy (replays real reasoning)
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. Noticeable difference on complex multi-turn coding sessions.
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 don't technically require it.
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 don't 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 doesn't break.
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 | Don't use reasoning_content |
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 won't show anything — there's no history to patch yet. That's normal.
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's a non-reasoning model (correct) or the plugin didn't load (check for `[ThinkingFix] Plugin loaded` at startup).
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 the test suite
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 isn't the only tool that drops `reasoning_content`. Here's a partial list of places this same bug shows up:
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.0.0",
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 },