opencode-minimax-easy-vision 1.2.2 → 1.3.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/README.md CHANGED
@@ -6,6 +6,29 @@ Originally built for [MiniMax](https://www.minimax.io/) models, it can be config
6
6
 
7
7
  It restores the "paste and ask" workflow by automatically saving image assets and routing them through the [MiniMax Coding Plan MCP](https://github.com/MiniMax-AI/MiniMax-Coding-Plan-MCP)
8
8
 
9
+ ## Table of Contents
10
+
11
+ * [Demo](#demo)
12
+ * [The Problem](#the-problem)
13
+ * [Prerequisites](#prerequisites)
14
+ * [Installation](#installation)
15
+ * [What This Plugin Does](#what-this-plugin-does)
16
+ * [Supported Models](#supported-models)
17
+ * [Custom Model Configuration](#custom-model-configuration)
18
+ * [Locations (Priority Order)](#locations-priority-order)
19
+ * [Config Format](#config-format)
20
+ * [Pattern Syntax](#pattern-syntax)
21
+ * [Wildcard Rules](#wildcard-rules)
22
+ * [Configuration Examples](#configuration-examples)
23
+ * [Custom Image Analysis Tool](#custom-image-analysis-tool)
24
+ * [Custom Prompt Template](#custom-prompt-template)
25
+ * [Supported Image Formats](#supported-image-formats)
26
+ * [Usage](#usage)
27
+ * [Example Interaction](#example-interaction)
28
+ * [Development](#development)
29
+ * [License](#license)
30
+ * [References](#references)
31
+
9
32
  ## Demo
10
33
 
11
34
  See how it works:
@@ -24,6 +47,36 @@ These models expect the MiniMax Coding Plan MCP's `understand_image` tool, which
24
47
  * **Manual steps**: You have to save screenshots manually, find the path, and reference it in your prompt.
25
48
  * **Broken flow**: The "paste and ask" experience available with Claude or GPT models is lost.
26
49
 
50
+ ## Prerequisites
51
+
52
+ The MiniMax Coding Plan MCP server must be configured in your `opencode.json`:
53
+
54
+ ```json
55
+ {
56
+ "mcp": {
57
+ "MiniMax": {
58
+ "type": "local",
59
+ "command": ["uvx", "minimax-coding-plan-mcp"],
60
+ "environment": {
61
+ "MINIMAX_API_KEY": "your-api-key-here",
62
+ "MINIMAX_API_HOST": "https://api.minimax.io"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## Installation
70
+
71
+ Add the plugin to the `plugin` array in your `opencode.json` file:
72
+
73
+ ```json
74
+ {
75
+ "$schema": "https://opencode.ai/config.json",
76
+ "plugin": ["opencode-minimax-easy-vision"]
77
+ }
78
+ ```
79
+
27
80
  ## What This Plugin Does
28
81
 
29
82
  This plugin automates the vision pipeline so you don't have to think about it.
@@ -39,14 +92,13 @@ This plugin automates the vision pipeline so you don't have to think about it.
39
92
 
40
93
  ## Supported Models
41
94
 
42
- By default, the plugin activates for MiniMax models:
95
+ By default, the plugin activates for MiniMax models using the pattern `["minimax/*"]`:
43
96
 
44
- * **Provider ID** containing `minimax`
45
- * **Model ID** containing `minimax` or `abab`
97
+ * `minimax/*` all models from the `minimax` provider
46
98
 
47
99
  **Examples:**
100
+ * `minimax/minimax-m2.5`
48
101
  * `minimax/minimax-m2.1`
49
- * `minimax/abab6.5s-chat`
50
102
 
51
103
  ### Custom Model Configuration
52
104
 
@@ -61,19 +113,18 @@ You can enable this for other models by creating a config file.
61
113
 
62
114
  ```json
63
115
  {
64
- "models": ["minimax/*", "opencode/*", "*/glm-4.7-free"]
116
+ "models": ["minimax/*", "z-ai/*", "*/minimax-m2.5"]
65
117
  }
66
118
  ```
67
119
 
68
120
  #### Pattern Syntax
69
121
 
70
- | Pattern | Matches |
71
- | ---------------- | --------------------------------------- |
72
- | `*` | Match ALL models |
73
- | `minimax/*` | All models from the `minimax` provider |
74
- | `*/glm-4.7-free` | Specific model from any provider |
75
- | `opencode/*` | All models from the `opencode` provider |
76
- | `*/abab*` | Any model containing `abab` |
122
+ | Pattern | Matches |
123
+ | --------------------- | ------------------------------------------ |
124
+ | `*` | Match ALL models |
125
+ | `minimax/*` | All models from the `minimax` provider |
126
+ | `*/minimax-m2.5` | Specific model from any provider |
127
+ | `z-ai/*` | All models from the `z-ai` provider |
77
128
 
78
129
  #### Wildcard Rules
79
130
 
@@ -82,7 +133,7 @@ You can enable this for other models by creating a config file.
82
133
  * `*` matches everything
83
134
  * `*text*` matches values containing `text`
84
135
 
85
- If the config is missing or empty, it defaults to MiniMax-only behavior.
136
+ If the config is missing or empty, it defaults to `["minimax/*"]`.
86
137
 
87
138
  #### Configuration Examples
88
139
 
@@ -98,7 +149,7 @@ If the config is missing or empty, it defaults to MiniMax-only behavior.
98
149
 
99
150
  ```json
100
151
  {
101
- "models": ["minimax/*", "opencode/*", "google/*"]
152
+ "models": ["minimax/*", "z-ai/*", "google/*"]
102
153
  }
103
154
  ```
104
155
 
@@ -106,84 +157,81 @@ If the config is missing or empty, it defaults to MiniMax-only behavior.
106
157
 
107
158
  ```json
108
159
  {
109
- "models": ["minimax/*", "opencode/gpt-5-nano", "*/claude-3-7-sonnet*"]
160
+ "models": ["minimax/*", "z-ai/glm-4.7", "*/minimax-m2.5"]
110
161
  }
111
162
  ```
112
163
 
113
164
  ### Custom Image Analysis Tool
114
165
 
115
- By default, the plugin uses the `mcp_minimax_understand_image` tool from the MiniMax Coding Plan MCP. You can configure a different tool for image analysis:
116
-
117
- ```json
118
- {
119
- "models": ["*"],
120
- "imageAnalysisTool": "mcp_openrouter_analyze_image"
121
- }
122
- ```
166
+ By default, the plugin uses the `mcp_minimax_understand_image` tool from the MiniMax Coding Plan MCP. You can configure a different tool for image analysis.
123
167
 
124
168
  > [!NOTE]
125
169
  > The `imageAnalysisTool` value is the **tool name**, not the MCP server name.
126
170
  >
127
171
  > MCP servers expose one or more tools—for example, the MiniMax Coding Plan MCP server exposes the
128
- > `mcp_minimax_understand_image` tool.
129
-
172
+ > `mcp_minimax_understand_image` tool. OpenCode prefixes tool names with `mcp_<server-name>_`.
130
173
 
131
- This allows you to use tools from other MCP servers that provide image analysis capabilities, such as:
174
+ #### Example: openrouter-image-mcp
132
175
 
133
- * [openrouter-image-mcp](https://github.com/JonathanJude/openrouter-image-mcp) - Uses OpenRouter with GPT-4V, Claude, Gemini
134
- * [mcp-image-recognition](https://github.com/mario-andreschak/mcp-image-recognition) - Uses Anthropic/OpenAI Vision APIs
135
- * [Peekaboo](https://github.com/steipete/Peekaboo) - macOS screenshot + AI analysis
176
+ [openrouter-image-mcp](https://github.com/JonathanJude/openrouter-image-mcp) routes image analysis through OpenRouter, giving you access to any vision-capable model including free ones.
136
177
 
137
- The plugin will instruct the model to use the configured tool. The tool should accept an image file path as input.
178
+ Add the MCP server to your `opencode.json`:
138
179
 
139
- ## Supported Image Formats
180
+ ```json
181
+ {
182
+ "mcp": {
183
+ "openrouter_image": {
184
+ "type": "local",
185
+ "command": ["npx", "openrouter-image-mcp"],
186
+ "environment": {
187
+ "OPENROUTER_API_KEY": "your-api-key-here",
188
+ "OPENROUTER_MODEL": "nvidia/nemotron-nano-12b-v2-vl:free"
189
+ }
190
+ }
191
+ }
192
+ }
193
+ ```
140
194
 
141
- * PNG
142
- * JPEG
143
- * WebP
195
+ Then configure the plugin to use it:
144
196
 
145
- *(Limited by the [MiniMax Coding Plan MCP](https://github.com/MiniMax-AI/MiniMax-Coding-Plan-MCP) `understand_image` tool.)*
197
+ ```json
198
+ {
199
+ "models": ["minimax/*"],
200
+ "imageAnalysisTool": "mcp_openrouter_image_analyze_image"
201
+ }
202
+ ```
146
203
 
147
- ## Installation
204
+ > [!TIP]
205
+ > `nvidia/nemotron-nano-12b-v2-vl:free` is a free vision model on OpenRouter that requires no credits.
148
206
 
149
- ### Via npm
207
+ ### Custom Prompt Template
150
208
 
151
- Just add the plugin to the `plugin` array in your `opencode.json` file:
209
+ By default, the plugin generates a fixed instruction prompt telling the model to use the image analysis tool. You can override this with a custom template:
152
210
 
153
211
  ```json
154
212
  {
155
- "$schema": "https://opencode.ai/config.json",
156
- "plugin": ["opencode-minimax-easy-vision"]
213
+ "promptTemplate": "I'm attaching {imageCount} image(s) for you to analyze.\n\nImages:\n{imageList}\n\nUse the `{toolName}` tool on each one.\n\nMy question: {userText}"
157
214
  }
158
215
  ```
159
216
 
160
- ### From Local Source
217
+ #### Available Variables
161
218
 
162
- 1. Clone the repository.
163
- 2. Build the plugin:
164
- ```bash
165
- npm install && npm run build
166
- ```
167
- 3. Copy the built `dist/index.js` into your OpenCode plugin directory.
219
+ | Variable | Description |
220
+ | -------------- | --------------------------------------------- |
221
+ | `{imageList}` | Newline-separated list: `- Image 1: /path/to/file` |
222
+ | `{imageCount}` | Number of images, e.g. `1`, `3` |
223
+ | `{toolName}` | The configured MCP tool name |
224
+ | `{userText}` | The user's original message text (may be empty) |
168
225
 
169
- ## Prerequisites
226
+ The template must contain at least one variable — if none are present, the plugin falls back to the default prompt.
170
227
 
171
- The MiniMax Coding Plan MCP server must be configured in your `opencode.json`:
228
+ ## Supported Image Formats
172
229
 
173
- ```json
174
- {
175
- "mcp": {
176
- "MiniMax": {
177
- "command": "uvx",
178
- "args": ["minimax-coding-plan-mcp"],
179
- "env": {
180
- "MINIMAX_API_KEY": "your-api-key-here",
181
- "MINIMAX_API_HOST": "https://api.minimax.io"
182
- }
183
- }
184
- }
185
- }
186
- ```
230
+ * PNG
231
+ * JPEG
232
+ * WebP
233
+
234
+ *(Limited by the [MiniMax Coding Plan MCP](https://github.com/MiniMax-AI/MiniMax-Coding-Plan-MCP) `understand_image` tool.)*
187
235
 
188
236
  ## Usage
189
237
 
@@ -202,12 +250,17 @@ The MiniMax Coding Plan MCP server must be configured in your `opencode.json`:
202
250
 
203
251
  ## Development
204
252
 
205
- ```bash
206
- npm install
207
- npm run build
208
- ```
209
-
210
- The built plugin will be available at `dist/index.js`
253
+ 1. Install dependencies and build:
254
+ ```bash
255
+ npm install
256
+ npm run build
257
+ ```
258
+ 2. The built plugin will be available at `dist/index.js`.
259
+ 3. Symlink it into the global plugin directory and restart OpenCode to pick up changes:
260
+ ```bash
261
+ mkdir -p ~/.config/opencode/plugin
262
+ ln -sf $(pwd)/dist/index.js ~/.config/opencode/plugin/minimax-easy-vision.js
263
+ ```
211
264
 
212
265
  ## License
213
266
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAqdlD,eAAO,MAAM,uBAAuB,EAAE,MA+DrC,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAqhBlD,eAAO,MAAM,uBAAuB,EAAE,MA+DrC,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -8,7 +8,13 @@ const PLUGIN_NAME = "minimax-easy-vision";
8
8
  const CONFIG_FILENAME = "opencode-minimax-easy-vision.json";
9
9
  const TEMP_DIR_NAME = "opencode-minimax-vision";
10
10
  const MAX_TOOL_NAME_LENGTH = 256;
11
- const DEFAULT_MODEL_PATTERNS = ["minimax/*", "*/abab*"];
11
+ const PROMPT_TEMPLATE_VARIABLES = [
12
+ "{imageList}",
13
+ "{imageCount}",
14
+ "{toolName}",
15
+ "{userText}",
16
+ ];
17
+ const DEFAULT_MODEL_PATTERNS = ["minimax/*"];
12
18
  const DEFAULT_IMAGE_ANALYSIS_TOOL = "mcp_minimax_understand_image";
13
19
  const SUPPORTED_MIME_TYPES = new Set([
14
20
  "image/png",
@@ -47,6 +53,16 @@ function parseImageAnalysisTool(value) {
47
53
  return undefined;
48
54
  return value;
49
55
  }
56
+ function parsePromptTemplate(value) {
57
+ if (typeof value !== "string")
58
+ return undefined;
59
+ const trimmed = value.trim();
60
+ if (trimmed === "")
61
+ return undefined;
62
+ if (!PROMPT_TEMPLATE_VARIABLES.some((v) => trimmed.includes(v)))
63
+ return undefined;
64
+ return trimmed;
65
+ }
50
66
  function parseConfigObject(raw) {
51
67
  if (!raw || typeof raw !== "object")
52
68
  return {};
@@ -54,6 +70,7 @@ function parseConfigObject(raw) {
54
70
  return {
55
71
  models: parseModelsArray(obj.models),
56
72
  imageAnalysisTool: parseImageAnalysisTool(obj.imageAnalysisTool),
73
+ promptTemplate: parsePromptTemplate(obj.promptTemplate),
57
74
  };
58
75
  }
59
76
  async function readConfigFile(configPath) {
@@ -84,7 +101,7 @@ async function loadPluginConfig(directory, log) {
84
101
  // Resolve models with precedence
85
102
  const modelsResult = selectWithPrecedence(projectConfig?.models, userConfig?.models, undefined);
86
103
  if (modelsResult.source !== "default") {
87
- log(`Loaded models from ${modelsResult.source} config: ${modelsResult.value.join(", ")}`);
104
+ log(`Loaded models from ${modelsResult.source} config: ${modelsResult.value?.join(", ")}`);
88
105
  }
89
106
  else {
90
107
  log(`Using default models: ${DEFAULT_MODEL_PATTERNS.join(", ")}`);
@@ -97,9 +114,18 @@ async function loadPluginConfig(directory, log) {
97
114
  else {
98
115
  log(`Using default imageAnalysisTool: ${DEFAULT_IMAGE_ANALYSIS_TOOL}`);
99
116
  }
117
+ // Resolve promptTemplate with precedence
118
+ const templateResult = selectWithPrecedence(projectConfig?.promptTemplate, userConfig?.promptTemplate, undefined);
119
+ if (templateResult.source !== "default") {
120
+ log(`Using promptTemplate from ${templateResult.source} config (${templateResult.value?.length ?? 0} chars)`);
121
+ }
122
+ else {
123
+ log("Using default (hardcoded) injection prompt template");
124
+ }
100
125
  pluginConfig = {
101
126
  models: modelsResult.value,
102
127
  imageAnalysisTool: toolResult.value,
128
+ promptTemplate: templateResult.value,
103
129
  };
104
130
  }
105
131
  // Config: Accessors
@@ -109,6 +135,9 @@ function getConfiguredModels() {
109
135
  function getImageAnalysisTool() {
110
136
  return pluginConfig.imageAnalysisTool ?? DEFAULT_IMAGE_ANALYSIS_TOOL;
111
137
  }
138
+ function getPromptTemplate() {
139
+ return pluginConfig.promptTemplate;
140
+ }
112
141
  // Pattern Matching (supports wildcards: *, prefix*, *suffix, *contains*)
113
142
  function matchesWildcardPattern(pattern, value) {
114
143
  const p = pattern.toLowerCase();
@@ -204,7 +233,7 @@ async function handleDataUrl(url, filePart, log) {
204
233
  return { path: savedPath, mime: parsed.mime, partId: filePart.id };
205
234
  }
206
235
  catch (err) {
207
- log(`Failed to save image: ${err}`);
236
+ log(`Failed to save image: ${err instanceof Error ? err.message : String(err)}`);
208
237
  return null;
209
238
  }
210
239
  }
@@ -246,7 +275,7 @@ async function processImagePart(filePart, log) {
246
275
  if (url.startsWith("http://") || url.startsWith("https://")) {
247
276
  return handleHttpUrl(url, filePart, log);
248
277
  }
249
- log(`Unsupported URL scheme for part ${filePart.id}: ${url.substring(0, 50)}...`);
278
+ log(`Unsupported URL scheme for part ${filePart.id}: ${url.slice(0, 50)}...`);
250
279
  return null;
251
280
  }
252
281
  async function extractImagesFromParts(parts, log) {
@@ -267,13 +296,29 @@ async function extractImagesFromParts(parts, log) {
267
296
  // we replace them with text instructions that tell the model to use an
268
297
  // MCP tool (e.g., understand_image) with the file path or URL.
269
298
  // The user's original text is preserved as "User's request: ...".
299
+ function applyPromptTemplate(template, vars) {
300
+ return template
301
+ .replace(/\{imageList\}/g, vars.imageList)
302
+ .replace(/\{imageCount\}/g, String(vars.imageCount))
303
+ .replace(/\{toolName\}/g, vars.toolName)
304
+ .replace(/\{userText\}/g, vars.userText);
305
+ }
270
306
  function generateInjectionPrompt(images, userText, toolName) {
271
307
  if (images.length === 0)
272
308
  return userText;
273
- const isSingle = images.length === 1;
274
309
  const imageList = images
275
310
  .map((img, idx) => `- Image ${idx + 1}: ${img.path}`)
276
311
  .join("\n");
312
+ const customTemplate = getPromptTemplate();
313
+ if (customTemplate !== undefined) {
314
+ return applyPromptTemplate(customTemplate, {
315
+ imageList,
316
+ imageCount: images.length,
317
+ toolName,
318
+ userText,
319
+ });
320
+ }
321
+ const isSingle = images.length === 1;
277
322
  const imageCountText = isSingle ? "an image" : `${images.length} images`;
278
323
  const imagePlural = isSingle ? "image is" : "images are";
279
324
  const analyzeText = isSingle ? "this image" : "each image";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-minimax-easy-vision",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "OpenCode plugin that enables vision support for Minimax models by saving pasted images and injecting MCP tool instructions",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,6 +10,8 @@
10
10
  ],
11
11
  "scripts": {
12
12
  "build": "tsc",
13
+ "format": "prettier --write src/**/*.ts",
14
+ "format:check": "prettier --check src/**/*.ts",
13
15
  "prepublishOnly": "npm run build"
14
16
  },
15
17
  "keywords": [
@@ -23,8 +25,9 @@
23
25
  "author": "devadathanmb",
24
26
  "license": "AGPL-3.0",
25
27
  "devDependencies": {
26
- "@opencode-ai/plugin": "^1.1.25",
27
- "@types/node": "^22.0.0",
28
+ "@opencode-ai/plugin": "^1.2.26",
29
+ "@types/node": "^25.0.0",
30
+ "prettier": "^3.8.1",
28
31
  "typescript": "^5.7.0"
29
32
  },
30
33
  "peerDependencies": {