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 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).
@@ -0,0 +1,5 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ declare const ReasoningSplit: Plugin;
3
+ export default ReasoningSplit;
4
+ export { ReasoningSplit };
5
+ //# sourceMappingURL=index.d.ts.map
@@ -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
+ }