Skip to content

minimax 0.2.7: native Magpie image, video, and web search - #1008

Open
klarkxy wants to merge 2 commits into
magpie-community:mainfrom
klarkxy:minimax-native-media
Open

klarkxy wants to merge 2 commits into
magpie-community:mainfrom
klarkxy:minimax-native-media

Conversation

@klarkxy

@klarkxy klarkxy commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

@magpie-community/opencode-minimax-auth 0.2.7 registers MiniMax Code as a backend for Magpie's built-in image generation, video generation, and web search. Agents can use Magpie's native /mcp/magpie/image, videos API, and /mcp/magpie/web-search, and drop the separate MiniMax named tools if they only need those capabilities. The named tools stay; this is additive.

This does not change #1005 (opt-in compact/progressive MiniMax tools), which still awaits maintainers.

How it hooks in

Magpie core already has plugin extension points for this; no Magpie core change is required.

  • Web search: the plugin search hook ({search: {<id>: {name, auth, execute}}}), listed beside Tavily and the rest, signed in as minimax-code or minimax-code-global. Calls MiniMax Code's existing /mavis/api/v1/mcp/web_search API (same as the web_search tool). No separate search API key. A refusal from that API carries a numeric status (401/402/403/429) so Magpie can rest the account for search.
  • Image: Magpie lists a plugin model that gives images alone and is spoken with @ai-sdk/openai-compatible. The plugin answers plugin://<id>/v1/images/generations and /images/edits in its fetch by calling the Matrix generate_image tool, then get_asset_url. The live generate_image catalog has no model enum, so the listed model is MiniMax Image (MiniMax-Image). Magpie's non-Codex/SenseNova edit path sends multipart/form-data with the image bytes; those bytes (and any data: URL) are uploaded through the account's live upload_temp_url signed-form flow, then passed to generate_image as a temp_url. HTTPS URLs are passed through. A size such as 1024x1024 is mapped to MiniMax's area (1K/2K/4K) so it does not fall through to the vendor's 4K default. Right after a generation, get_asset_url is asked again for a few seconds while the URL still has /visible/ (the watermarked rendition reported in minimax: get_asset_url answers a watermarked /visible/ URL right after a generation #974); if the wait runs out, the last URL is returned as-is.
  • Video: Magpie lists plugin models whose capabilities.output includes video. The plugin answers the OpenAI-shaped /videos API in its fetch with submit_video_generation, query_video_generation, and get_asset_url, using the real model ids MiniMax-H3, MiniMax-H3-Max, and MiniMax-Hailuo-2.3. A first or last frame given as a data: URL is uploaded the same way; an HTTPS URL is passed through.

Resource library

MiniMax Code as a Library agent target (the icon row used to give skills to the MiniMax Code app) already lives in Magpie core, not in this plugin. Magpie shows it when it detects the MiniMax Code app (its data folder). This plugin cannot add that column.

After updating the plugin

Sign in to MiniMax Code (China) or MiniMax Code (Global), then:

  1. Settings → Models → Images → Image generation: choose MiniMax Image.
  2. Settings → Models → Images → Video generation: choose MiniMax H3, MiniMax H3 Max, or MiniMax Hailuo 2.3.
  3. Settings → Models → Web search → Search APIs: enable MiniMax Code or MiniMax Code (Global) (no key needed).

The image and video models also appear on the MiniMax Code provider's model list; leave chat on M3 / M2.7.

Tests

bun test packages/minimax: 182 pass, including native.test.mjs. bun scripts/check.mjs passes. Rebased onto current main.

@yetone yetone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. The search hook and the video routes match magpie's plugin contract on main (e6fe481b3), but two things don't work the way magpie actually calls the plugin, so I'm requesting changes. Reviewed head: 4cc171a.

What I checked against magpie main:

  • Search: host.js searchesOf/runSearch takes {search: {<id>: {name, auth, execute}}}. auth has to name a provider this plugin's auth hook signs in to (searchAuthOf), and ctx.getAuth()/ctx.abort are what the hook gets. This PR matches that.
  • Image: draw.go:396 lists a plugin model as a drawer when its output is images only and its npm is @ai-sdk/openai-compatible (PluginOpenAICompatible). Generations go as JSON to plugin://<id>/v1/images/generations (draw.go:1652). Your mediaPath catches that after pluginFetch rewrites the URL onto the chat base. This matches too.
  • Video: video.go customVideomakersOf treats a plugin model whose capabilities.output has video as a video model, sends the OpenAI-shaped body (remoteVideoBody) to videoBase(p)+"/videos", and polls /videos/{id} and /videos/{id}/content. This matches.

Blocking

1. /images/edits never works through magpie (native.mjs:472-484, native.mjs:374-381)

The edit request for a plugin drawer that isn't Codex or SenseNova goes down the multipart branch in draw.go:1686-1720. magpie sends multipart/form-data with the image bytes, and it downloads an https reference into bytes first (fetchPicture). jsonBody only parses JSON, so every edit and every reference-image drawing comes back 400. Probe: I fed handleMediaFetch the body magpie builds (model, prompt, n, and an image file part):

PROBE edits multipart -> 400 {"error":{"message":"request body must be a JSON object","type":"invalid_request_error"}}

native.test.mjs:121 passes only because it posts a JSON {prompt} that magpie never sends. Meanwhile MiniMax-Image declares input: ["text","image"], so magpie offers it for edits and references.

Video has the same gap. magpie keeps an http(s) input_reference as a URL, but a local file or an uploaded image reaches the plugin as a data: URL (video.go:967, picture.dataURL), which mediaUrl refuses.

The README's "reference images must already be HTTPS URLs" is also not shown to work. The live Matrix schema describes reference_images[].url / input_image.url as "temp_url from a completed upload_temp_url upload ... or an existing upload URL from this environment", and submit_video_generation says "upload first and pass the URL". Nothing here tries an arbitrary external https URL.

Either of these would be fine:

  • (a) Declare the image model input: ["text"] and drop the edits route, so magpie doesn't offer what can't work. Text-to-image and text-to-video stay.
  • (b) Parse the multipart body and upload the bytes through MiniMax's upload flow before calling generate_image, with a test that posts the multipart body magpie actually builds.

2. A 401 on the media path is never renewed (index.mjs:1123-1127, index.mjs:1279)

postTool throws a plain Error("... HTTP 401") with no status, and handleMediaFetch turns any throw without a status into 502. So res?.status === 401 is never true and the renewal branch can't run. Probe through auth.loader().fetch with the connector list answering 401:

PROBE 401 -> 502 kept {"error":{"message":"MiniMax Code tool API: HTTP 401","type":"api_error"}} list asks: 1

The chat path and the named tools (createToolRequest) both renew on 401. Please carry the status on the error, or reuse createToolRequest's send and renew, and add a test where the first ask is 401 and the second is 200.

Non-blocking (fix while you're here)

  1. postTool (index.mjs:1270-1302) is a copy of createToolRequest's envelope checks without its secret redaction. One function for both would keep them from drifting.
  2. #974: the native image path calls get_asset_url right after generate_image, which is exactly when #974 says MiniMax answers with the watermarked /visible/ rendition. magpie then saves whatever that URL serves. So unless #974 is wrong, every native image can come back watermarked. At least say so on #974. Better, add a short bounded wait while the URL has /visible/.
  3. The search hook's errors carry no status. host.js rests an account for search only when the hook throws with a numeric status (401/402/403/429, see the comment over searchesOf). As it is, a 429 keeps being retried on the same account.
  4. With no quality, area is left out, and the vendor's default is 4K. magpie's usual 1024x1024 then comes out as a 4K image. Consider mapping size to area.
  5. mediaUrl accepts any scheme (http:, ftp: …), though the docs say HTTPS.

Related issues

  • #955: not affected. The native path validates against the live catalog through callMatrixTool.
  • #985: not affected. The chat models' input modalities are unchanged.
  • #974: see item 4.

What ran (mica, Bun 1.4.2, temp HOME)

  • bun test packages/minimax: 176 pass, 0 fail.
  • bun scripts/check.mjs: ok.
  • Broken by hand, and a test went red each time: dropping the search hook, skipping the media answer in the loader fetch, leaving the media models out of the live list, and allowing data: URIs.
  • The two probes above.
  • Root bun test with origin/main (1e5b1c8) merged in, under lockf ~/suite.lock: 8387 pass, 20 skip, 0 fail.
  • No real MiniMax calls were made.

Register MiniMax Code as a backend for Magpie's built-in image
generation, video generation, and web search, so agents can use
/mcp/magpie/image, the videos API, and /mcp/magpie/web-search and drop
the separate MiniMax named tools if they only need those capabilities.
The named tools stay; this is additive.

Search uses the plugin search hook, signed in as minimax-code or
minimax-code-global, and MiniMax Code's web_search API. Image and video
use Magpie's plugin fetch contract: MiniMax-Image is listed as an
images-only @ai-sdk/openai-compatible model, and H3 / H3 Max / Hailuo
2.3 as models that give video. The fetch answers the OpenAI-shaped
images and videos routes by calling the same Matrix connector tools
(generate_image, submit_video_generation, query_video_generation,
get_asset_url). No new MiniMax endpoints.
Parse the multipart /images/edits body Magpie actually sends, and
upload those bytes (and video data: references) through the live
catalog's upload_temp_url signed-form flow before generate_image /
submit_video_generation. HTTPS URLs stay as they are.

postTool and createToolRequest now share one envelope reader; HTTP
errors carry their status, so a 401 on the media path can be renewed
the way chat already is, and the search hook can rest an account on
401/402/403/429.

Also: a short wait while get_asset_url still answers a /visible/ URL
(magpie-community#974), map size to area so 1024x1024 is 1K rather than the 4K
default, and accept only https and data: media URLs.

Tests: the multipart body Magpie builds, a video data: reference, a
media 401 then 200, a /visible/ retry, search status, and size→area.
@klarkxy
klarkxy force-pushed the minimax-native-media branch from 4cc171a to 2a7303b Compare November 11, 2026 15:18
@klarkxy

klarkxy commented Nov 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Kept edits and reference images (option b), and addressed the rest in 2a7303b. Rebased onto current main. Version stays 0.2.7 (still unreleased).

1. /images/edits multipart and video data: references (blocking)
The plugin now parses the multipart body Magpie actually sends (model, prompt, n, size, quality, image / image[] file parts, optional mask). Those bytes go through the live catalog's upload_temp_url signed-form flow (filename + mime_type → upload_host / fields / temp_url, then POST the file), and generate_image is called with the resulting temp_url. Video data: input_reference / last_frame is uploaded the same way; HTTPS URLs are passed through. Other schemes (http:, ftp:, paths) are refused. The README claim is updated.

Tests: an images/edits multipart body is uploaded through upload_temp_url, then generate_image (posts the multipart body Magpie builds); a video data: input_reference is uploaded; an https URL is passed through; mediaRef accepts https and data: only.

2–3. Media 401 renewal, and one envelope for postTool / createToolRequest (blocking + non-blocking)
HTTP errors from the tool API now carry a numeric status, so handleMediaFetch can return 401 and the loader fetch renews, same as chat. postTool and createToolRequest share toolRequestInit + parseToolEnvelope and the same secret redaction.

Test: a 401 on the media path is renewed and the second ask succeeds (first list ask 401, second 200).

4. #974 watermarked /visible/ URL (non-blocking)
get_asset_url is asked again for up to 8 seconds, in 1 second steps, while the URL still contains /visible/. If the wait runs out, the last URL is returned as-is. Saying so here rather than on #974: this is a bounded retry, not a measurement of how long the watermarked rendition lasts or whether every asset kind does it.

Test: get_asset_url is asked again while the URL still has /visible/.

5. Search hook status (non-blocking)
A refused search now throws with a numeric status, so Magpie can rest the account on 401/402/403/429.

Test: the search hook carries a numeric status when the API refuses the account.

6. Map size to area (non-blocking)
With no explicit area or quality, 1024x1024 (and other WxH sizes) map to 1K / 2K / 4K by the longer side, so Magpie's usual square is not left to the vendor's 4K default. An explicit quality still wins.

Test: covered in duration and resolution follow each video model's limits and in the multipart edits test (size: 1024x1024 → area: "1K").

7. mediaUrl schemes (non-blocking)
Only https: (kept as-is) and data: (uploaded) are accepted.

bun test packages/minimax: 182 pass. bun scripts/check.mjs passes. No live MiniMax calls in this pass.

@yetone yetone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the second round. I checked each item from the last review against the code at 2a7303b. Most of them are done. Two problems are still blocking, and both come from the new code. One quietly sends the wrong image to MiniMax, and the other can charge the user for a generation twice.

The items from the last review

# Claim At 2a7303b
1 Multipart edits are parsed and uploaded through upload_temp_url Done. One gap: the mask (blocking A below)
1 Video data: refs are uploaded, and https refs pass through Done
2 A media 401 is renewed It renews, but it runs the whole request again (blocking B)
3 postTool and createToolRequest share one envelope and keep the redaction Done
4 #974 /visible/ gets a bounded retry Done (8s, in 1s steps)
5 The search status is a number Done for HTTP refusals. A refusal in base_resp still throws with no status (see below)
6 size maps to area Done
7 mediaRef takes https and data: only Done

Blocking

A. The mask is uploaded as a reference image (packages/minimax/native.mjs:403)

referenceImages uploads every part whose field is image, image[], mask or markup, and puts all of them in reference_images. magpie's multipart branch (internal/gateway/draw.go, the else after drawsCodex) adds d.Mask as a part named mask. generate_image takes no mask, so MiniMax gets the black-and-white mask as one more picture to draw from. The edit comes back wrong, and the user is still charged. Probe: the multipart body magpie builds, with one image part and one mask part:

PROBE mask -> 200 [{"url":"https://temp.example/image0.png",...},{"url":"https://temp.example/image1.png",...}]

Please refuse a mask part with a 400, as magpie does for OpenRouter ("does not support mask edits"), and add a test for it. Drop markup too, unless something sends it.

B. A 401 after generate_image runs the paid generation again (packages/minimax/index.mjs:1124-1129, packages/minimax/native.mjs:270-277)

The renewal wraps the whole of handleMediaFetch. In one image request, generate_image runs first, for up to GEN_MS (10 minutes), and get_asset_url runs after it. If the token is turned away on get_asset_url, handleMediaFetch answers 401. The loader then renews and calls media() again from the start, so generate_image runs a second time and is charged twice. The first image is lost. A long generation makes this more likely, because the token can expire or be rotated while it runs. Probe through auth.loader().fetch: generate_image succeeds on tok, get_asset_url answers 401 on tok, and the renewal gives tok-2:

PROBE 401-after-generate -> 200 generate_image calls: 2

createToolRequest doesn't have this problem, because it renews per HTTP call and repeats only the call that got the 401. Please do the same on the media path: renew inside the transport that makeTokenRequest returns (or reuse createToolRequest's send-and-renew), and drop the retry around media(). Then add a test where get_asset_url gets the 401 and generate_image runs exactly once.

Non-blocking

  • allowedUploadHost (packages/minimax/native.mjs:576-581) accepts http://127.0.0.1, localhost and ::1, so that the tests' Bun.serve upload host works. In production, it means an upload_host from the vendor's answer makes the plugin POST the user's image to a service on this computer, and magpie's own gateway is on loopback too. Please accept only https in the code, and point the tests at a fake fetch instead.
  • parseToolEnvelope: a refusal inside base_resp (code !== 0) throws with no status. If MiniMax reports quota or rate limits there, rather than as an HTTP status, magpie keeps sending searches to that account. If you know which codes those are, map them to 402 or 429.
  • videoContent (native.mjs:349-353) copies all of the download's headers. If the CDN ever sets content-encoding, Bun has already decoded the body, and the client decodes it a second time. Dropping content-encoding and content-length there would avoid that.

Version and rebase

#1014 has merged as 2b87fad and published @magpie-community/opencode-minimax-auth 0.2.7. This PR conflicts with it in packages/minimax/package.json and connector-tools.mjs. Please rebase onto main, bump to 0.2.8, and keep both native.mjs and the new magpie.options in package.json. As it stands, publish.yml would find 0.2.7 already on npm, and this change would never ship.

What ran (mote, Bun, temp HOME, fake fetch only)

  • bun test packages/minimax: 182 pass, 0 fail.
  • bun scripts/check.mjs: exit 0.
  • I broke the code by hand four times and restored it after each, and a test went red every time:
    • Multipart not parsed: the edits multipart test went red.
    • toolHttpError with no status: the media-401 and search-status tests went red.
    • The media 401 retry removed: the media-401 test went red.
    • mediaRef allowing http:: the scheme tests went red.
  • The two probes above.
  • git merge-tree against main dba2406: clean. Against #1014's head: conflict.
  • No real MiniMax calls were made.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants