minimax-reasoning 0.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 +21 -0
- package/README.md +167 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 openopencode
|
|
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
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# minimax-reasoning
|
|
2
|
+
|
|
3
|
+
OpenCode plugin that fixes the `<think>` tag leakage in the TUI for **MiniMax M3** and other OpenAI-compatible reasoning models.
|
|
4
|
+
|
|
5
|
+
> Without this plugin, the model's chain-of-thought shows up as raw text in your TUI:
|
|
6
|
+
>
|
|
7
|
+
> ```
|
|
8
|
+
> <think>The user is asking me to calculate 17 * 23. Let me compute...391.</think>
|
|
9
|
+
> 391
|
|
10
|
+
> ```
|
|
11
|
+
>
|
|
12
|
+
> With this plugin, the thinking is split into a separate part and rendered as a **collapsible thinking block**:
|
|
13
|
+
>
|
|
14
|
+
> ```
|
|
15
|
+
> ▼ [Thinking]
|
|
16
|
+
> The user is asking me to calculate 17 * 23...
|
|
17
|
+
> 391
|
|
18
|
+
> ```
|
|
19
|
+
|
|
20
|
+
## Why this exists
|
|
21
|
+
|
|
22
|
+
OpenAI's Chat Completions spec predates reasoning models — it has no field for chain-of-thought. When MiniMax exposes M3's reasoning through that spec, it embeds the thinking inside the normal `content` field wrapped in `<think>...</think>` tags.
|
|
23
|
+
|
|
24
|
+
OpenCode's TUI **does not parse `<think>` tags** inside text. It only renders a collapsible thinking block when the reasoning arrives on a dedicated `reasoning_content` field (which is the shape Anthropic, OpenAI Responses, and DeepSeek use natively).
|
|
25
|
+
|
|
26
|
+
This plugin bridges the gap by injecting the official MiniMax `reasoning_split: true` flag into the LLM request. The server then returns the same thinking in a separate `message.reasoning_content` field, which OpenCode renders correctly.
|
|
27
|
+
|
|
28
|
+
> **Even better:** MiniMax also offers an Anthropic-compatible API at `/anthropic/v1`. If you can switch to that, you don't need this plugin at all — see [When to use this plugin](#when-to-use-this-plugin) below.
|
|
29
|
+
|
|
30
|
+
## Quick install
|
|
31
|
+
|
|
32
|
+
Edit `~/.config/opencode/opencode.json` and add the plugin:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"plugin": [
|
|
37
|
+
"oh-my-openagent@latest",
|
|
38
|
+
"minimax-reasoning@latest"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
OpenCode will install the plugin on next startup. Restart any active TUI session.
|
|
44
|
+
|
|
45
|
+
### Local install (development / offline)
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"plugin": [
|
|
50
|
+
"./local-plugins/minimax-reasoning"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## When to use this plugin
|
|
56
|
+
|
|
57
|
+
Use this plugin **only when** you need to keep using MiniMax's OpenAI-compatible Chat Completions endpoint (`baseURL: https://api.minimaxi.com/v1`, `npm: @ai-sdk/openai-compatible`).
|
|
58
|
+
|
|
59
|
+
If you can switch endpoints, the cleaner fix is to use the Anthropic-compatible API natively:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"provider": {
|
|
64
|
+
"minimax": {
|
|
65
|
+
"npm": "@ai-sdk/anthropic",
|
|
66
|
+
"options": {
|
|
67
|
+
"apiKey": "...",
|
|
68
|
+
"baseURL": "https://api.minimaxi.com/anthropic/v1"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The Anthropic endpoint returns reasoning as native `thinking` content blocks, so OpenCode's TUI handles them out of the box — no plugin needed.
|
|
76
|
+
|
|
77
|
+
## How it works
|
|
78
|
+
|
|
79
|
+
OpenCode exposes a `chat.params` plugin hook that fires just before the LLM call. The hook receives a mutable `output.options` object; whatever you set there gets routed through OpenCode's `Oo.providerOptions()` translation layer into the AI SDK's `providerOptions[<sdk-key>]`, which the openai-compatible provider's `getArgs()` spreads into the HTTP request body.
|
|
80
|
+
|
|
81
|
+
This plugin sets `output.options.reasoning_split = true`, which the MiniMax server reads and uses to move thinking out of `content` and into `reasoning_content` in the response.
|
|
82
|
+
|
|
83
|
+
The AI SDK's openai-compatible provider already parses `delta.reasoning_content` natively, emitting `reasoning-start` / `reasoning-delta` / `reasoning-end` events. OpenCode's event adapter then creates a separate `reasoning` part in the database, which the TUI renders as a collapsible block.
|
|
84
|
+
|
|
85
|
+
**Zero custom rendering code in this plugin.** All the reasoning-block UI is OpenCode's native behavior, just unlocked by the right request flag.
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
| Env var | Values | Default | Effect |
|
|
90
|
+
|---|---|---|---|
|
|
91
|
+
| `OPENCODE_REASONING_SPLIT_MODE` | `split` / `disable` / `off` | `split` | Operating mode |
|
|
92
|
+
|
|
93
|
+
- **`split`** (default): inject `reasoning_split: true` → thinking stays on, just split into a separate field
|
|
94
|
+
- **`disable`**: inject `thinking: { type: "disabled" }` → model skips emitting thinking tags (faster, cheaper, no thinking shown)
|
|
95
|
+
- **`off`**: plugin does nothing (effectively disabled)
|
|
96
|
+
|
|
97
|
+
## Adding more providers
|
|
98
|
+
|
|
99
|
+
Edit `PROVIDERS` in `src/index.ts`:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const PROVIDERS: Record<string, Partial<Record<Mode, Record<string, unknown>>>> = {
|
|
103
|
+
minimax: {
|
|
104
|
+
split: { reasoning_split: true },
|
|
105
|
+
disable: { thinking: { type: "disabled" } },
|
|
106
|
+
},
|
|
107
|
+
myProvider: {
|
|
108
|
+
split: { some_flag: true },
|
|
109
|
+
disable: { another_flag: "off" },
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Then rebuild: `npm run build`.
|
|
115
|
+
|
|
116
|
+
## Verify it works
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# 1. Run a simple prompt
|
|
120
|
+
opencode run "Reply: 17 * 23 = ? Just the answer." --model minimax/MiniMax-M3
|
|
121
|
+
# Expected: no `<think>` block in visible output
|
|
122
|
+
|
|
123
|
+
# 2. Inspect DB to confirm reasoning is a separate part
|
|
124
|
+
SID=$(sqlite3 ~/.local/share/opencode/opencode.db \
|
|
125
|
+
"SELECT id FROM session ORDER BY time_created DESC LIMIT 1")
|
|
126
|
+
sqlite3 ~/.local/share/opencode/opencode.db \
|
|
127
|
+
"SELECT json_extract(p.data, '\$.type'), length(json_extract(p.data, '\$.text'))
|
|
128
|
+
FROM part p JOIN message m ON p.message_id = m.id
|
|
129
|
+
WHERE m.session_id = '$SID'
|
|
130
|
+
AND json_extract(p.data, '\$.type') IN ('text', 'reasoning')"
|
|
131
|
+
# Expected output: two rows — one 'reasoning' and one 'text'
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Caveats
|
|
135
|
+
|
|
136
|
+
- **Multi-turn thinking continuity**: MiniMax's docs require the **entire `response_message`** (including `reasoning_details`) to be preserved in message history for the model's chain-of-thought to stay coherent across turns. This plugin preserves the thinking text in a separate DB part, but OpenCode's OpenAI-compatible path may not replay the full `reasoning_details` array on subsequent turns. If you see thinking quality degrade over long agentic sessions, switch to the Anthropic endpoint or set `OPENCODE_REASONING_SPLIT_MODE=disable`.
|
|
137
|
+
- **M2.x models**: According to MiniMax's docs, `thinking: { type: "disabled" }` is a soft hint — M2.x models continue to reason even with this flag set, but the response is just cleaner.
|
|
138
|
+
|
|
139
|
+
## Related ecosystem
|
|
140
|
+
|
|
141
|
+
- **[OpenCode PR #31426](https://github.com/anomalyco/opencode/pull/31426)** — merged: enables MiniMax M3 thinking on the Anthropic interface
|
|
142
|
+
- **[OpenCode PR #26233](https://github.com/anomalyco/opencode/pull/26233)** — open: would add `provider.options.extraBody` as a config-only solution (no plugin needed) once merged
|
|
143
|
+
- **[Pi Agent's MiniMax provider](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/minimax.ts)** — uses the Anthropic-compatible endpoint natively
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Clone
|
|
149
|
+
git clone https://github.com/openopencode/minimax-reasoning.git
|
|
150
|
+
cd minimax-reasoning
|
|
151
|
+
|
|
152
|
+
# Install deps
|
|
153
|
+
npm install
|
|
154
|
+
|
|
155
|
+
# Build
|
|
156
|
+
npm run build
|
|
157
|
+
|
|
158
|
+
# Test locally — link into OpenCode's plugin dir
|
|
159
|
+
ln -s "$PWD" ~/.config/opencode/plugins/minimax-reasoning
|
|
160
|
+
|
|
161
|
+
# Or reference as file path in opencode.json:
|
|
162
|
+
# "plugin": ["/absolute/path/to/minimax-reasoning"]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT — see [LICENSE](./LICENSE).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA0CjD,QAAA,MAAM,cAAc,EAAE,MAoBrB,CAAA;AAED,eAAe,cAAc,CAAA;AAG7B,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const PROVIDERS = {
|
|
2
|
+
// MiniMax (minimaxi.com / MiniMax.io)
|
|
3
|
+
// The default Chat Completions endpoint embeds chain-of-thought inside
|
|
4
|
+
// `content` as `<think>...</think>` tags. Setting `reasoning_split: true`
|
|
5
|
+
// makes the server return the same thinking under a separate
|
|
6
|
+
// `message.reasoning_content` field, which OpenCode's TUI already knows
|
|
7
|
+
// how to render as a collapsible thinking block.
|
|
8
|
+
minimax: {
|
|
9
|
+
split: { reasoning_split: true },
|
|
10
|
+
// `thinking: { type: "disabled" }` is documented as a "soft hint" for M3
|
|
11
|
+
// (the model still reasons internally but skips emitting <think> tags).
|
|
12
|
+
// Useful when you don't want to see the thinking trace at all.
|
|
13
|
+
disable: { thinking: { type: "disabled" } },
|
|
14
|
+
},
|
|
15
|
+
// Add more providers here as needed. Example:
|
|
16
|
+
// "<providerID>": {
|
|
17
|
+
// split: { some_flag: true },
|
|
18
|
+
// disable: { another_flag: "off" },
|
|
19
|
+
// },
|
|
20
|
+
};
|
|
21
|
+
const MODES = new Set(["split", "disable"]);
|
|
22
|
+
function getMode() {
|
|
23
|
+
const v = process.env.OPENCODE_REASONING_SPLIT_MODE;
|
|
24
|
+
if (v === "off")
|
|
25
|
+
return null;
|
|
26
|
+
if (v === "disable" || v === "split")
|
|
27
|
+
return v;
|
|
28
|
+
// Default: split (keeps reasoning visible, just split into the right field)
|
|
29
|
+
return "split";
|
|
30
|
+
}
|
|
31
|
+
const ReasoningSplit = async () => {
|
|
32
|
+
return {
|
|
33
|
+
"chat.params": async (input, output) => {
|
|
34
|
+
const mode = getMode();
|
|
35
|
+
if (mode === null)
|
|
36
|
+
return;
|
|
37
|
+
const providerID = input.model.providerID;
|
|
38
|
+
const extra = PROVIDERS[providerID]?.[mode];
|
|
39
|
+
if (!extra)
|
|
40
|
+
return;
|
|
41
|
+
// OpenCode merges `output.options` into providerOptions via its
|
|
42
|
+
// Oo.providerOptions() translation layer. For openai-compatible and
|
|
43
|
+
// anthropic AI SDK providers, those options are then spread into the
|
|
44
|
+
// request body by the SDK's getArgs().
|
|
45
|
+
output.options ??= {};
|
|
46
|
+
for (const [k, v] of Object.entries(extra)) {
|
|
47
|
+
output.options[k] = v;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export default ReasoningSplit;
|
|
53
|
+
// Named export for users who prefer `import { ReasoningSplit } from ...`
|
|
54
|
+
export { ReasoningSplit };
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,MAAM,SAAS,GAAmE;IAChF,sCAAsC;IACtC,uEAAuE;IACvE,0EAA0E;IAC1E,6DAA6D;IAC7D,wEAAwE;IACxE,iDAAiD;IACjD,OAAO,EAAE;QACP,KAAK,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;QAChC,yEAAyE;QACzE,wEAAwE;QACxE,+DAA+D;QAC/D,OAAO,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE;KAC5C;IACD,8CAA8C;IAC9C,sBAAsB;IACtB,qCAAqC;IACrC,wCAAwC;IACxC,OAAO;CACR,CAAA;AAED,MAAM,KAAK,GAAsB,IAAI,GAAG,CAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAA;AAEpE,SAAS,OAAO;IACd,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAA;IACnD,IAAI,CAAC,KAAK,KAAK;QAAE,OAAO,IAAI,CAAA;IAC5B,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,OAAO;QAAE,OAAO,CAAC,CAAA;IAC9C,4EAA4E;IAC5E,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,cAAc,GAAW,KAAK,IAAI,EAAE;IACxC,OAAO;QACL,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;YACtB,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAM;YAEzB,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,UAAU,CAAA;YACzC,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC,CAAA;YAC3C,IAAI,CAAC,KAAK;gBAAE,OAAM;YAElB,gEAAgE;YAChE,oEAAoE;YACpE,qEAAqE;YACrE,uCAAuC;YACvC,MAAM,CAAC,OAAO,KAAK,EAAE,CAAA;YACrB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3C,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;YACvB,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC,CAAA;AAED,eAAe,cAAc,CAAA;AAE7B,yEAAyE;AACzE,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
+
"name": "minimax-reasoning",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "OpenCode plugin that fixes MiniMax M3 <think> tag leakage in the TUI by injecting reasoning_split into the chat params. Also works for any OpenAI-compatible provider that uses the same pattern.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"opencode",
|
|
10
|
+
"opencode-plugin",
|
|
11
|
+
"minimax",
|
|
12
|
+
"minimax-m3",
|
|
13
|
+
"reasoning",
|
|
14
|
+
"thinking",
|
|
15
|
+
"mcp",
|
|
16
|
+
"ai",
|
|
17
|
+
"llm"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://github.com/openopencode/minimax-reasoning",
|
|
20
|
+
"bugs": "https://github.com/openopencode/minimax-reasoning/issues",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/openopencode/minimax-reasoning.git"
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"typecheck": "tsc --noEmit",
|
|
42
|
+
"clean": "rm -rf dist",
|
|
43
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@opencode-ai/plugin": ">=1.0.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@opencode-ai/plugin": "^1.17.0",
|
|
50
|
+
"@types/node": "^20.0.0",
|
|
51
|
+
"typescript": "^5.6.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=20"
|
|
55
|
+
},
|
|
56
|
+
"sideEffects": false
|
|
57
|
+
}
|