Support MiniMax image generation schema - #8322
Conversation
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Thanks for the PR, @octo-patch! Before we can review the code, the PR body needs to follow the pull request template.
Right now the description uses free-form sections (Reason, Changes, Checks) and is missing the required headings:
## What this PR does## Why it's needed— the motivation / user-facing benefit## Reviewer Test Plan(with### How to verify,### Evidence (Before & After),### Tested on)## Risk & Scope## Linked Issues- The bilingual
<details>Chinese translation
Could you edit the PR body to fill in the template? In particular, "Why it's needed" should explain the motivation (who wants MiniMax image generation and why), and the Reviewer Test Plan should describe how a maintainer can confirm the behavior. Once the body follows the template, re-run with @qwen-code /triage and we'll continue the review.
中文说明
感谢贡献,@octo-patch!在进入代码审查之前,PR 正文需要遵循 PR 模板。
目前描述使用的是自由格式的小节(Reason、Changes、Checks),缺少必需的标题:
## What this PR does## Why it's needed—— 动机 / 面向用户的收益## Reviewer Test Plan(包含### How to verify、### Evidence (Before & After)、### Tested on)## Risk & Scope## Linked Issues- 双语的
<details>中文翻译
请编辑 PR 正文以填写模板。其中 "Why it's needed" 应说明动机(谁需要 MiniMax 图像生成、为什么需要),Reviewer Test Plan 应描述维护者如何确认该行为。正文遵循模板后,使用 @qwen-code /triage 重新运行,我们会继续审查。
— Qwen Code · qwen3.8-max-preview
ReviewOverviewAdds MiniMax image generation support to What looks good
Issues
Suggestions (minor)
None of these block the mainline flow — item 1 is the one I'd fix before merging, since it directly affects how users experience every MiniMax-side failure. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
— qwen3.8-max-preview via Qwen Code /review
| throw new Error(formatImageGenerationError(response.status, payload)); | ||
| } | ||
| const payload = await readJsonResponse(response, MAX_API_RESPONSE_BYTES); | ||
| const image = findMiniMaxGeneratedImage(payload); |
There was a problem hiding this comment.
[Suggestion] This !response.ok error branch in generateMiniMaxImage (~10 lines: the JSON-body read with fallback plus formatImageGenerationError) has no test coverage, while the equivalent DashScope branch is covered by two tests (HTTP 429 throttling and non-JSON error body). — Failure scenario: MiniMax returns 429/401 and this path runs in production unexercised; if a later refactor drops the try/catch around readJsonResponse, a malformed error body throws an unhandled ResponseSizeLimitError instead of the user-facing message and no test catches the regression. Suggested fix: add a test that sends a non-200 response (e.g. 429 with a JSON error body) to a MiniMax base URL and asserts the rejection message matches /rate limit/i and fetchFn is called exactly once (no download attempted).
— qwen3.8-max-preview via Qwen Code /review
| (parsed.hostname === 'api.minimax.io' || | ||
| parsed.hostname === 'api.minimaxi.com') && | ||
| (normalizedPath === '/v1' || | ||
| normalizedPath === MINIMAX_IMAGE_GENERATION_PATH) |
There was a problem hiding this comment.
[Suggestion] This hostname allowlist is implicitly coupled to the preset's baseUrl array in packages/core/src/providers/presets/minimax.ts, with no shared constant, comment, or test keeping them in sync. — Failure scenario: a maintainer adds a new MiniMax regional endpoint (e.g. https://api.minimax.eu/v1) to the preset's baseUrl array; with no signal that this routing function also needs updating, image-01 requests on that endpoint silently fall through to the DashScope request schema and fail with a confusing 404 / "did not contain an image URL" error that gives no hint the root cause is a missing hostname here. Suggested fix: export a shared MINIMAX_IMAGE_HOSTNAMES set used by both the preset and this function, or add a test asserting every hostname in the preset's baseUrl is recognized here.
— qwen3.8-max-preview via Qwen Code /review
| if (/^https:\/\//i.test(value)) { | ||
| return { kind: 'url', value }; | ||
| } | ||
| return { kind: 'base64', value }; | ||
| } | ||
| return undefined; |
There was a problem hiding this comment.
[Suggestion] This for loop never iterates past the first non-empty string: once a non-empty candidate is reached, the unconditional return { kind: 'base64', value } exits, so the for/continue structure implies multi-candidate scanning that the control flow defeats. — Failure scenario: if MiniMax ever returns a non-image string before a valid entry (e.g. image_urls: ["content_filter_notice", "https://cdn.example.com/img.png"]), the first string is classified as base64, decodePngBase64Image throws "did not contain a valid PNG image", and the valid HTTPS URL at index 1 is never examined (n: 1 is pinned today, so this is unlikely in practice, but the structure misleads anyone extending to n > 1). Suggested fix: continue past unrecognized entries instead of returning unconditionally, falling through to return undefined only when no usable entry is found.
— qwen3.8-max-preview via Qwen Code /review
| function decodePngBase64Image(value: string): Buffer { | ||
| const match = value.match(/^data:image\/png;base64,(.+)$/i); | ||
| const base64 = (match?.[1] ?? value).trim(); |
There was a problem hiding this comment.
[Suggestion] The PNG-signature validation in decodePngBase64Image has no negative test — removing the check leaves every existing test green (the only base64 test feeds valid PNG bytes). The DashScope download path has two equivalent negative tests ("rejects a download that is not a PNG image" and "rejects a download with only a partial PNG signature"); this base64 path has none. — Failure scenario: if the signature check is accidentally removed or inverted, non-PNG base64 data (a JPEG or truncated payload from a misbehaving endpoint) is returned to the caller as mimeType: 'image/png' with no error, producing a corrupt image downstream. Suggested fix: add a test that puts base64-encoded non-PNG bytes (e.g. Buffer.from('not a png').toString('base64')) in data.image_urls and asserts the rejection message matches /valid PNG/i.
— qwen3.8-max-preview via Qwen Code /review
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
— qwen3.7-max via Qwen Code /review
| ): Promise<GeneratedImage> { | ||
| const generationUrl = request.baseUrl.endsWith(MINIMAX_IMAGE_GENERATION_PATH) | ||
| ? request.baseUrl | ||
| : `${request.baseUrl}${MINIMAX_IMAGE_GENERATION_PATH.slice(3)}`; |
There was a problem hiding this comment.
[Suggestion] .slice(3) is a magic-number extraction that silently depends on the internal structure of MINIMAX_IMAGE_GENERATION_PATH ('/v1/image_generation'). If the version prefix changes (e.g. '/v2/image_generation'), .slice(3) produces '2/image_generation' instead of '/image_generation', yielding a malformed URL like https://api.minimax.io/v12/image_generation.
Consider extracting the subpath semantically:
| : `${request.baseUrl}${MINIMAX_IMAGE_GENERATION_PATH.slice(3)}`; | |
| : `${request.baseUrl}${MINIMAX_IMAGE_GENERATION_SUBPATH}`; |
Where MINIMAX_IMAGE_GENERATION_SUBPATH = '/image_generation' is a separate constant, or use MINIMAX_IMAGE_GENERATION_PATH.replace(/^\/v\d+/, '') to derive it.
— qwen3.7-max via Qwen Code /review
| } catch { | ||
| // non-JSON error body — formatImageGenerationError handles missing fields | ||
| } | ||
| throw new Error(formatImageGenerationError(response.status, payload)); |
There was a problem hiding this comment.
[Suggestion] MiniMax error responses nest diagnostic information under base_resp: { status_code, status_msg }, but formatImageGenerationError reads only top-level code and message fields. MiniMax-specific error details are silently discarded.
Additionally, MiniMax can return HTTP 200 with application-level errors (base_resp.status_code !== 0 and empty image_urls), which produces the misleading "Image generation response did not contain an image URL." error instead of the real cause (authentication failure, rate limit, content moderation).
Consider extracting base_resp error fields before calling formatImageGenerationError, and checking base_resp.status_code after parsing the success response.
— qwen3.7-max via Qwen Code /review
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 113 passed · 0 failed · 113 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:113 通过 · 0 失败 · 113 总计 Verification reportPR 8322 — Support MiniMax image generation schema — deep verification (follow-up round)Verdict: 中文 — 判定:✅ 通过(merge-ready)· 113/113 脚本断言全部通过
Previous-finding status at the new head
On F1's fix shape: the author's implementation differs from the round-1 suggested patch and is a superset of it — Central claim + A/BCentral claim: Oracle: a real TLS peer on 127.0.0.1:8443 impersonating the vendor hostnames (self-signed CA via Table 1 — routing, wire schema, response parsing
Table 2 — error surfacing (the delta since round 1)
Test pinning: vacuity + mutation matrix at the new headVacuity (witness Mutation matrix (witness
Positive control: MA/MB/ME are each killed by exactly the test the commit added for that behavior; restores verified byte-identical (sha256) after every mutant. FindingsNo new findings this round. Carried over, both non-blocking:
Not covered
MethodologyEnvironment: CI Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
Triage re-ran on Aug 12 at the same head The request-changes review from Aug 1 still gates the PR on this, so no duplicate review was submitted — and per the gate rules this run stops here: no code review pass, no approval. The split from last time still holds:
@octo-patch same one thing left from you: rewrite the PR body following the template. Your existing verification story (unit tests + the maintainer's real-stack runs) slots straight into the Reviewer Test Plan section. After that, trigger Maintainer notes:
中文说明Triage 于 8 月 12 日在同一个 head 8 月 1 日的 request-changes 评审 至今仍因此卡住该 PR,所以没有重复提交评审 —— 按照门禁规则,本次运行到此为止:不做代码审查,也不做批准。 上次的结论依然成立:
@octo-patch 仍然只差一件事:按模板重写 PR 正文。你已有的验证材料(单测 + 维护者的真实栈验证)可以直接填入 Reviewer Test Plan 小节。完成后再触发 维护者注意:
— Qwen Code · qwen3.8-max Reviewed at |
|
Thanks for the review. I now surface non-zero MiniMax base_resp status codes and messages for HTTP 200 and non-2xx responses, and added a regression test. I ran git diff --check; the targeted Vitest test could not run in this worker because dependencies are not installed. |
|
Thanks for the review. I replaced the MiniMax endpoint magic offset with an explicit image-generation suffix and pushed a new commit. I ran npm ci, the two targeted core test files (25 tests), core typecheck, ESLint on the changed service, and git diff --check. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not explored to full depth (tool budget reached): Reverse audit round 2 of the PR 8322 review (MiniMax imag...: none — all checks above completed within budget.; This PR adds MiniMax image generation support to qwen-cod...: none — all checks above completed within budget.; This PR adds MiniMax image generation support to qwen-cod...: none — all checks completed (tests executed, not just read).; This PR adds MiniMax image generation support to qwen-cod...: none — completed all checks I had planned. Actually, one thing I can note: I did not run the test suite. Unnecessary for the performance lens.; This PR adds MiniMax image generation support to qwen-cod...: none — all planned checks completed within budget., and 1 more.
— qwen3.8-max via Qwen Code /review (v0.21.10)
| (parsed.hostname === 'api.minimax.io' || | ||
| parsed.hostname === 'api.minimaxi.com') && |
There was a problem hiding this comment.
[Suggestion] This hostname allowlist exact-matches only the two official hosts, while the chat provider (MINIMAX_HOST_SUFFIXES in core/openaiContentGenerator/provider/minimax.ts) deliberately also matches subdomains of minimax.io / minimaxi.com for proxies (with a documented rationale), and the same two hosts are already declared in two more places (MINIMAX_KNOWN_HOSTS in that provider module and telemetry/gen-ai-provider.ts). — Failure scenario: a user configures an imageOnly MiniMax model against a subdomain proxy or corporate gateway (e.g. https://gateway.minimax.io/v1) — a configuration resolveImageGenerationModel supports — chat works via suffix matching, but image generation silently POSTs the DashScope schema to …/services/aigc/multimodal-generation/generation and fails with a 404/schema error that does not mention the real cause (verified with a live probe against this code). When MiniMax ships a new regional host, all three copies must also be updated in tandem or image generation silently breaks.
| (parsed.hostname === 'api.minimax.io' || | |
| parsed.hostname === 'api.minimaxi.com') && | |
| (parsed.hostname === 'api.minimax.io' || | |
| parsed.hostname === 'api.minimaxi.com' || | |
| parsed.hostname.endsWith('.minimax.io') || | |
| parsed.hostname.endsWith('.minimaxi.com')) && |
Longer-term, hoist one shared known-hosts/suffixes constant, or carry the wire schema from the resolved provider config instead of sniffing hostnames.
— qwen3.8-max via Qwen Code /review (v0.21.10)
| prompt_optimizer: true, | ||
| response_format: 'url', | ||
| }; | ||
| const dimensions = parseImageSize(request.size); |
There was a problem hiding this comment.
[Suggestion] The size-absent branch (request body omits width/height) has no test assertion: the schema test pins the body only with size, and 'accepts a full MiniMax image generation endpoint' omits size but asserts only the request URL. — Failure scenario: a mutation that always appends dimensions (e.g. Number(undefined) serialized as null, or a hardcoded default) passes the suite green and sends MiniMax a malformed or wrong-dimension request for users who run image generation without a configured size. Current behavior was probe-confirmed correct (body carries no width/height keys when size is absent) — this only pins it.
In the full-endpoint test, also assert the body:
expect(JSON.parse(String(fetchFn.mock.calls[0]?.[1]?.body))).toEqual({
model: 'image-01',
prompt: expect.any(String),
n: 1,
prompt_optimizer: true,
response_format: 'url',
});— qwen3.8-max via Qwen Code /review (v0.21.10)
| const match = value.match(/^data:image\/png;base64,(.+)$/i); | ||
| const base64 = (match?.[1] ?? value).trim(); |
There was a problem hiding this comment.
[Suggestion] The ?? value fallback — raw base64 without the data:image/png;base64, prefix — is untested: the diff's test-efficacy probe (harness validated) deleted it and every affected test stayed green (mutant survived). findMiniMaxGeneratedImage routes any non-https: candidate to this branch, so bare base64 is a live shape. — Failure scenario: if a later change drops or breaks this fallback (e.g. a "simplification" to prefixed data URIs only), bare-base64 responses would throw a TypeError on (match?.[1]).trim() in production while the suite stays green.
Add a case to image-generation-service.test.ts where image_urls contains raw base64 (no prefix) and assert the decoded bytes:
// image_urls: [Buffer.from(PNG_BYTES).toString('base64')]
// expect the decoded result.bytes to equal PNG_BYTES and fetchFn not to be called— qwen3.8-max via Qwen Code /review (v0.21.10)
| const code = | ||
| readStringOrNumber(payload, 'code') ?? | ||
| readStringOrNumber(baseResponse, 'status_code'); |
There was a problem hiding this comment.
[Suggestion] MiniMax application errors delivered with HTTP 200 — the exact shape this PR adds the statusCode && statusCode !== '0' gate for — can never reach the access-denied or content-moderation branches below, because those match HTTP status (401/403) or regex-test the code string only, while MiniMax codes are numeric base_resp.status_code strings like '1004'. The rate-limit branch already tests `${code} ${message}`, so only the access and moderation branches are asymmetric. — Failure scenario: an invalid/expired MINIMAX_API_KEY returned as { base_resp: { status_code: 1004, status_msg: 'API key not valid' } } yields Image generation failed (1004: API key not valid). instead of the access-denied message with its remediation hint — verified with a live probe against this code, including a flip check; even a status_msg literally containing 'access denied' misses the branch. status_msg still surfaces in the suffix, so this is message quality, not lost information.
Mirror the rate-limit branch and test code+message (and/or map known MiniMax auth/moderation codes):
if (
status === 401 ||
status === 403 ||
/access|permission/i.test(`${code} ${message}`)
) {Note this alone only flips payloads whose status_msg carries access/permission wording — a complete fix would also map known MiniMax auth codes (e.g. 1004).
— qwen3.8-max via Qwen Code /review (v0.21.10)
Local real-stack verification report (maintainer)Verdict: works as intended. Verified at head MethodReal stack, no unit-test shortcuts: both head and merge-base were bundled ( MiniMax models were configured via Results — 19/19 scenario assertions passed
Request captured by the fake origin in H1 — matches the official API reference (endpoint, field names, and types): POST https://api.minimax.io/v1/image_generation
Authorization: Bearer $MINIMAX_API_KEY
{
"model": "image-01",
"prompt": "a lighthouse at dawn",
"n": 1,
"prompt_optimizer": true,
"response_format": "url",
"width": 1024,
"height": 768
}Unit tests: ScreenshotsURL flow success in the real TUI (image saved into Business error (HTTP 200 +
Non-blocking observations
中文版本(Chinese version)本地真实环境验证报告(维护者)结论:符合预期,可作为合并参考。 验证基于 head 方法真实栈验证,不走单测捷径:head 与 merge-base 分别 MiniMax 模型按 preset 安装后的形态写入 结果 — 19/19 场景断言全部通过
H1 假源站抓到的请求体与官方 API 文档一致(见上方英文版 JSON)。单测:两个相关测试文件在 head 上 25/25 通过。 非阻塞观察项
|
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not explored to full depth (tool budget reached): This PR adds MiniMax image generation support: it routes ...: none — all planned checks completed within budget.; This PR adds MiniMax image generation support to qwen-cod...: none — all checks above completed within budget.; This PR adds MiniMax image generation support: it routes ...: none — all planned checks completed within budget..
— qwen3.8-max via Qwen Code /review (v0.21.10)
| if (!image) { | ||
| throw new Error('Image generation response did not contain an image URL.'); | ||
| } |
There was a problem hiding this comment.
[Suggestion] This if (!image) guard on the MiniMax path has no test coverage: none of the four new MiniMax tests sends a 200 response with missing data, a non-array or empty image_urls, or non-string candidates. A mutation probe confirmed the gap — deleting this guard leaves all 21 tests green, while a payload like {"base_resp":{"status_code":0},"data":{"image_urls":[]}} then crashes with TypeError: Cannot read properties of undefined (reading 'kind') instead of the clean error below. — Failure scenario: a future edit deletes or reorders this guard → a MiniMax 200 response with empty image_urls crashes with an unhandled TypeError instead of throwing 'Image generation response did not contain an image URL.', and no test fails. Suggested fix: add a test asserting rejects.toThrow('Image generation response did not contain an image URL.') for a payload with data: { image_urls: [] } (optionally with a [null, '', 'https://…'] candidate list to pin the skip logic).
— qwen3.8-max via Qwen Code /review (v0.21.10)
Re-verified on a real stack at the current head
|
| File on the code path | Touched by the merge? |
|---|---|
services/image-generation-service.ts |
no |
providers/presets/minimax.ts |
no |
tools/image-gen.ts |
no |
extension/network-policy.ts (resolveNetworkTarget) |
no |
imageModel resolution in config/config.ts |
no |
/model --image dialog |
no |
So this re-run is a regression check on the surrounding tree rather than a review of new code — and the tree is clean.
Method
Unchanged from the previous round, re-run from scratch. Both head and merge-base were built and bundled (npm ci && npm run build && npm run bundle) and executed as the real CLI inside a Linux container where api.minimax.io, api.minimaxi.com, and dashscope.aliyuncs.com resolve to a local TLS fake origin (self-signed CA via NODE_EXTRA_CA_CERTS) that implements the official MiniMax response schema and ledgers every request. A scripted OpenAI-compatible provider drives the real image_gen tool.
The hosts are mapped to a public-looking IP on loopback (11.99.0.7/32 on lo — inside 11/8, which is not in the loopback/RFC1918/TEST-NET/CGNAT block lists), so the image-URL download really passes the resolveNetworkTarget('public') DNS guard instead of bypassing it. Bundle identity was printed inside the container to rule out stale builds:
/repo-head: sha256 b900ed33602e5cd0… (42c0ea44)
/repo-base: sha256 958b70973ba28080… (5e97fc8f)
MiniMax models were configured exactly as the preset installs them (baseUrl: https://api.minimax.io/v1, envKey: MINIMAX_API_KEY, imageOnly: true), with imageModel: "image-01".
Results — 19/19 assertions passed
| # | Scenario | Result |
|---|---|---|
| H1 | URL flow, size 1024*768 (intl host) |
✅ POST /v1/image_generation, PNG downloaded from the returned URL, saved bytes sha256-identical to the fixture served (c39093d10fb3bb17…) |
| H2 | base64 in data.image_urls (raw) |
✅ decoded + PNG signature validated, no download request made |
| H3 | base64 as data:image/png;base64, URI |
✅ decoded correctly |
| H4 | HTTP 200 + base_resp.status_code: 1008 |
✅ Image generation failed (1008: insufficient balance). — no misleading "HTTP 200" |
| H5 | HTTP 429 + base_resp 1002 |
✅ Image generation rate limit reached (1002: rate limit triggered). |
| H6 | China host api.minimaxi.com/v1, model image-01-live |
✅ routed to /v1/image_generation, image saved |
| H7 | DashScope regression (dashscope.aliyuncs.com/api/v1) |
✅ still posts the legacy endpoint with the legacy input.messages/parameters schema, image saved |
| B1 | merge-base bundle, same MiniMax config | ✅ (expected failure) posts /v1/services/aigc/multimodal-generation/generation on api.minimax.io → 404 → Image generation failed with HTTP 404. |
The A/B is what makes the change load-bearing — same config, only the bundle differs:
head (42c0ea44): TOOL_OUTCOME: Generated image saved to …/generated-images/…/….png
base (5e97fc8f): TOOL_OUTCOME: Image generation failed with HTTP 404.
Request captured by the fake origin in H1 — endpoint, field names, and types match the official API reference:
POST https://api.minimax.io/v1/image_generation
Authorization: Bearer $MINIMAX_API_KEY
{
"model": "image-01",
"prompt": "a lighthouse at dawn",
"n": 1,
"prompt_optimizer": true,
"response_format": "url",
"width": 1024,
"height": 768
}Full fake-origin ledger for the run (note line 12 — the pre-PR bundle hitting the wrong endpoint):
2 200 minimax-generation api.minimax.io POST /v1/image_generation
3 200 png-download api.minimax.io GET /files/gen-2.png
4 200 minimax-generation api.minimax.io POST /v1/image_generation (raw base64, no download follows)
5 200 minimax-generation api.minimax.io POST /v1/image_generation (data-URI base64)
6 200 minimax-generation api.minimax.io POST /v1/image_generation (base_resp 1008)
7 429 minimax-generation api.minimax.io POST /v1/image_generation (base_resp 1002)
8 200 minimax-generation api.minimaxi.com POST /v1/image_generation (image-01-live)
9 200 png-download api.minimaxi.com GET /files/gen-8.png
10 200 dashscope-generation dashscope.aliyuncs.com POST /api/v1/services/aigc/multimodal-generation/generation
11 200 png-download dashscope.aliyuncs.com GET /files/gen-10.png
12 404 not-found api.minimax.io POST /v1/services/aigc/multimodal-generation/generation <- base bundle
Unit tests at head: image-generation-service.test.ts (21) + presets/minimax.test.ts (4) → 25/25 passed.
Screenshots (re-captured at 42c0ea44)
URL flow success in the real TUI — image saved under .qwen/generated-images/…:
Business error (HTTP 200 + base_resp 1008) surfaced with code and message, no bogus HTTP status:
/model --image picker showing the two new preset entries with the MiniMax base URL and env key:
(Glyphs are transliterated to ASCII in these captures — the renderer used for the screenshots hangs on box-drawing characters. The .ans captures are byte-exact.)
Non-blocking observations (unchanged, none block merge)
- Official base64 responses live in
data.image_base64, notdata.image_urls. The PR pinsresponse_format: "url", so this never triggers against the real API, and the base64 handling insideimage_urlsis purely defensive. If base64 mode is ever requested,findMiniMaxGeneratedImagewould need to readdata.image_base64— and note the 1 MB API-response cap would likely be exceeded by real base64 payloads. https://api.minimax.iowithout/v1silently falls through to the DashScope-style path (host matches, path is''), producing a confusing 404. The preset always writes/v1, so this only affects hand-edited configs; widening the matcher or raising an explicit error would be friendlier.- The official t2i reference lists only
image-01for this endpoint; worth confirmingimage-01-liveis accepted upstream. If it is rejected, the error surfaces cleanly (H4/H5 verify that shape). prompt_optimizeris hardcodedtruewhile the API default isfalse— consistent with the DashScope path'sprompt_extend: true, so presumably intentional.- Cosmetic, pre-existing: the image-model picker shows
Modality: text-only / Context Window: 200,000 tokensdefaults for image-only entries (visible in the third screenshot).
中文版本(Chinese version)
在当前 head 42c0ea44 上重新做了真实环境验证
接上一轮在 head 434d911 的报告:该分支之后合并了 main,因此我重建两侧产物,针对当前 head 重跑了全部验证。
结论:依然符合预期,可以合并。 在 42c0ea44(对照新 merge-base 5e97fc8f)上 19/19 条端到端场景断言全部通过,相关单测 25/25 通过。上一轮的非阻塞观察项没有变化,文末重列一遍备查。
这次 merge 改变了什么
merge commit 42c0ea44 带入了 301 个 main 的提交,但 PR 的四个文件与 434d911 逐字节一致(git diff 434d911 42c0ea44 -- <四个文件> 为空)。并且 main 的这些提交没有触碰该代码路径上的任何文件:
| 代码路径上的文件 | 本次 merge 是否改动 |
|---|---|
services/image-generation-service.ts |
否 |
providers/presets/minimax.ts |
否 |
tools/image-gen.ts |
否 |
extension/network-policy.ts(resolveNetworkTarget) |
否 |
config/config.ts 中的 imageModel 解析 |
否 |
/model --image 选择器 |
否 |
所以这一轮是对周边代码树的回归检查,而不是对新代码的评审 —— 检查结果是干净的。
方法
与上一轮相同,完整重跑。head 与 merge-base 分别 npm ci && npm run build && npm run bundle,以真实 CLI 运行在 Linux 容器中;容器内 api.minimax.io、api.minimaxi.com、dashscope.aliyuncs.com 解析到本地 TLS 假源站(自签 CA + NODE_EXTRA_CA_CERTS),假源站按 MiniMax 官方响应 schema 应答并把每个请求记入台账;脚本化的 OpenAI 兼容模型驱动真实 image_gen 工具。
这些域名被映射到 loopback 上的公网观感 IP(11.99.0.7/32,属于 11/8,不在 loopback/RFC1918/TEST-NET/CGNAT 的封锁表里),因此图片 URL 下载是真实通过 resolveNetworkTarget('public') 的 DNS 守卫,而不是绕过它。容器内打印了产物指纹,排除用到旧构建的可能:
/repo-head: sha256 b900ed33602e5cd0… (42c0ea44)
/repo-base: sha256 958b70973ba28080… (5e97fc8f)
MiniMax 模型按 preset 安装后的形态配置(baseUrl: https://api.minimax.io/v1、envKey: MINIMAX_API_KEY、imageOnly: true),并设置 imageModel: "image-01"。
结果 — 19/19 断言通过
| # | 场景 | 结果 |
|---|---|---|
| H1 | URL 流,size 1024*768(国际站) |
✅ POST /v1/image_generation,从返回 URL 下载 PNG,落盘字节与假源站所服务的 fixture sha256 一致(c39093d10fb3bb17…) |
| H2 | data.image_urls 内裸 base64 |
✅ 正确解码并校验 PNG 签名,未发起下载请求 |
| H3 | data:image/png;base64, 前缀 |
✅ 正确解码 |
| H4 | HTTP 200 + base_resp.status_code: 1008 |
✅ 报 Image generation failed (1008: insufficient balance).,不再出现误导性的 "HTTP 200" |
| H5 | HTTP 429 + base_resp 1002 |
✅ Image generation rate limit reached (1002: rate limit triggered). |
| H6 | 国内站 api.minimaxi.com/v1 + image-01-live |
✅ 正确路由到 /v1/image_generation,图片落盘 |
| H7 | DashScope 回归(dashscope.aliyuncs.com/api/v1) |
✅ 仍走旧端点与旧 input.messages/parameters schema,图片落盘 |
| B1 | merge-base 产物,同样的 MiniMax 配置 | ✅(预期失败)向 api.minimax.io 发 /v1/services/aigc/multimodal-generation/generation → 404 → Image generation failed with HTTP 404. |
A/B 对照说明这个改动是真正起作用的 —— 配置完全相同,只换产物:
head (42c0ea44): TOOL_OUTCOME: Generated image saved to …/generated-images/…/….png
base (5e97fc8f): TOOL_OUTCOME: Image generation failed with HTTP 404.
H1 被假源站抓到的请求(端点、字段名与类型均与官方 API 文档一致)以及完整台账见上方英文版;台账第 12 行就是 merge-base 产物打错端点的证据。单测在 head 上 25/25 通过。
截图(在 42c0ea44 重新采集)
三张图分别是:真实 TUI 中 URL 流成功并落盘、HTTP 200 + base_resp 1008 业务错误按 code + message 透出、/model --image 选择器中新增的两个 MiniMax 条目(含 base URL 与环境变量名)。图片见上方英文版。
(截图中的字形已转写为 ASCII —— 用于渲染截图的工具在遇到制表符号时会挂死;.ans 原始抓取是逐字节准确的。)
非阻塞观察项(与上一轮相同,均不阻塞合并)
- 官方 base64 响应位于
data.image_base64而非data.image_urls。 本 PR 固定请求response_format: "url",真实 API 下不会触发,image_urls里的 base64 处理属于防御性代码。若将来要用 base64 模式,findMiniMaxGeneratedImage需改读data.image_base64,且真实 base64 载荷大概率会超过 1 MB 的响应上限。 https://api.minimax.io不带/v1时会静默落入 DashScope 风格路径(host 匹配但 path 为空),产生难懂的 404。preset 总是写/v1,只影响手改配置;放宽匹配或显式报错会更友好。- 官方 t2i 文档仅列出
image-01,建议向上游确认image-01-live是否被该端点接受;若被拒,错误也能干净地透出(H4/H5 已验证该形态)。 prompt_optimizer硬编码为true(API 默认为false),与 DashScope 路径的prompt_extend: true一致,应属有意为之。- 外观问题(先前已存在):图像模型选择器对 image-only 条目显示
Modality: text-only / Context Window: 200,000 tokens默认值(见第三张截图)。
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 505 passed · 0 failed · 505 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:505 通过 · 0 失败 · 505 总计 Verification reportPR 8322 Deep Verification (follow-up round) — Support MiniMax image generation schemaVerdict: 中文 — 判定:✅ 通过 · 可合入(agent 判定)这是跟进轮:PR head 与上一轮完全相同(
Previous-finding status (follow-up round)The previous round verified the same head OID (
Central claim and A/B proof (re-run at the new head)Central claim: base URLs on The harness (
Score: head 36/36; base 26/26 — 9 cells flip broken→fixed, 5 parity cells + the marker control identical on both arms. The parity cells are the control: the flip is attributable to this PR alone, and the generic dashscope path is behaviorally byte-identical on both arms (wire-level, including over real TLS). Sibling probe S3 additionally re-confirmed on both arms that a non-MiniMax host with a CorrectionsOne correction to the previous verification round's own methodology note (not to any claim about the PR): the prior round recorded a base-side FindingsNone blocking, none new. The three carried findings all stand unchanged at the new head (status table above); they are re-measured, not assumed:
Not covered
MethodologyOne container (node v22.23.2, bookworm), working tree at merge commit Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
Triage re-ran again on Aug 12 at the same head The request-changes review from Aug 1 still gates the PR on this, so no duplicate review was submitted — and per the gate rules this run stops here: no code review pass, no approval. What changed since the last run — only CI, which has now fully settled on this head: The split remains: the gate is about the PR description, not the code — and only the author can clear it (or a maintainer can edit the body on the author's behalf). @octo-patch same one thing left from you: rewrite the PR body following the template. Your existing verification story (unit tests + the maintainer's real-stack runs) slots straight into the Reviewer Test Plan section. After that, trigger Maintainer note: if you'd rather waive the template gate given the approvals and verification already standing, dismissing the bot's stale request-changes review unblocks the PR; otherwise the body fix above is the unblock path. 中文说明Triage 于 8 月 12 日再次在同一个 head 8 月 1 日的 request-changes 评审 至今仍因此卡住该 PR,所以没有重复提交评审 —— 按照门禁规则,本次运行到此为止:不做代码审查,也不做批准。 上次运行以来唯一的变化是 CI —— 现已在该 head 上全部落定: 结论依然不变:门禁针对的是 PR 描述而非代码 —— 且只有作者能解除(维护者也可以代作者修改正文)。 @octo-patch 仍然只差一件事:按模板重写 PR 正文。你已有的验证材料(单测 + 维护者的真实栈验证)可以直接填入 Reviewer Test Plan 小节。完成后再触发 维护者注意:如果考虑到已有的批准与验证、希望豁免模板门禁,dismiss 机器人的过期 request-changes 评审即可解除阻塞;否则请按上述作者操作处理。 — Qwen Code · qwen3.8-max Reviewed at |
|
@qwen-code /triage |
|
Thanks for the review. I rewrote the PR body to follow the pull request template: What this PR does, Why it's needed, Reviewer Test Plan (How to verify / Evidence / Tested on), Risk & Scope, Linked Issues, and the Chinese translation. This update changes the description only — no code, test or commit changes, so CI on the current head is unaffected. @qwen-code /triage |
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 634 passed · 0 failed · 634 total Flakiness gate: ✅ 2 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:634 通过 · 0 失败 · 634 总计 抖动门:✅ 2 changed test file(s) x 5 identical rounds, no divergence Verification reportPR 8322 Deep Verification (follow-up round 2) — Support MiniMax image generation schemaVerdict: 中文 — 判定:✅ 通过 · 可合入(agent 判定)这是第二次跟进轮:PR head 与前两轮完全相同(
Previous-finding status (follow-up round 2)Head is byte-identical to the rounds that verified
Central claim and A/B proof (re-run at the new head/base)Central claim: base URLs on The harness (
Score: head 83/83 + 7/7 TLS; base 71/71 + 7/7 TLS — 12 cells differ broken→fixed (or message-shaped), 6 parity cells identical on both arms, plus 5/5 cross-arm parity comparisons ( CorrectionsOne refinement to the previous verification round's analysis (not to any claim about the PR's code): the prior rounds classified all four mutation survivors as "coverage gaps, not dead code". This round's added combined row (MUTE-8, hostname + path restrictions reverted together → 11 tests red, including the entire redirect/SSRF block of the generic suite) shows the two allowlist restrictions are redundant defence of each other within the suite — reverting either alone is unobservable because the sibling still protects every tested input — while the base64-signature and safe-integer guards remain genuine coverage gaps (no sibling hunk closes those hazards, and the scratch-dist proofs P4/P5 show each decides real behavior). The prior classification was not wrong ("not dead code" holds for all four) but under-specified; the author should read the allowlist pair as defence in depth and the other two as tests to write. FindingsNone blocking, none new. The three carried findings all stand unchanged at the new head (status table above); they were re-measured, not assumed:
Not covered
MethodologyOne container (node v22.23.2, bookworm), working tree at merge commit Flakiness gate logEvidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
The Stage 1a template gate from Aug 1 is cleared — thanks @octo-patch for the rewrite, the body now follows the template with all required sections and the Chinese translation ✓
Moving on to code review. 🔍 中文说明8 月 1 日的 Stage 1a 模板门禁已解除 —— 感谢 @octo-patch 重写正文,现已遵循模板,所有必填小节及中文翻译齐全 ✓
进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
|
Code review. Before reading the diff I wrote down my own proposal for "make
Testing evidence — this is an unattended CI run, so nothing was built or executed here; the evidence below is the PR's own CI at
No red checks; the skipped lanes are the standard fork-PR set, matching prior runs on this head. The added unit tests pin the MiniMax request URL and exact body, the base64 branch, and the Beyond the unit suite, the behavioural claim is already substantiated at this exact head by two prior sandboxed 中文说明代码审查。 在读 diff 之前我先写下了自己的方案:在现有服务内按主机路由,为 MiniMax 单独开一个请求/响应分支并复用有界读取、错误格式化与 PNG 下载流程,再在 MiniMax 预设中加入
测试证据 —— 本次为无人值守 CI 运行,未在此构建或执行任何代码;以上证据为通过 API 获取的该 PR 在 单测之外,行为层面的声明在该 head 上已有佐证:此前两次沙箱 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 5/5 — real problem, minimal focused diff, green CI, tests that pin the new paths, and wire-level verification at this exact head. Stepping back: this one holds together. The problem was verified against the code on One hygiene action completes the loop: the bot's Aug 1 request-changes review existed only for the template gate, which the author has since cleared — it is dismissed alongside this approval so it no longer blocks the PR. 中文说明信心:5/5 —— 问题真实存在,diff 最小且聚焦,CI 全绿,测试固定了新路径,且该 head 已有网络层验证。 退一步看:这个 PR 是立得住的。问题既在 一项收尾操作:机器人 8 月 1 日的 request-changes 评审仅为模板门禁而存在,作者现已补齐模板 —— 它将随本次批准一并被 dismiss,不再阻塞该 PR。 — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Stage 1a template gate resolved: the PR body was rewritten on Aug 17 and now follows the pull request template. Full triage completed at 42c0ea4 — see the stage 1-3 comments and the approval.
|
Released in v0.22.2. |

























What this PR does
The image generation service now recognises MiniMax image base URLs and talks to MiniMax on its own terms instead of reusing the DashScope request shape. When the configured image base URL points at the MiniMax global or China host, the request is sent to the MiniMax image generation endpoint as a
POSTwith aBearerAPI key and the MiniMax field names (model,prompt,n,prompt_optimizer,response_format, pluswidth/heightwhen an explicit size is requested). Responses are read from the MiniMax payload: the returned image list is accepted both as an HTTPS link, which is downloaded through the existing PNG download pipeline, and as inline base64 data, which is decoded directly and checked for a real PNG signature. Application-level failures that MiniMax reports inside its response envelope, including the ones delivered with an HTTP 200 status, are surfaced with the provider's own status code and message instead of a generic "no image URL" message. Finally, the two MiniMax image-only models are registered in the MiniMax provider preset so they can be picked as the image model.The DashScope path is untouched: routing happens only for the two official MiniMax hosts on the image generation path, so every existing provider keeps its current behaviour, and the URL download branch continues to go through the same host validation, redirect handling, size cap and PNG signature checks as before.
Why it's needed
Users who run this CLI against MiniMax could configure a MiniMax provider for chat, but the
image_gentool was hardcoded to the DashScope generation path and request schema. Pointing the image model at MiniMax therefore produced a request MiniMax cannot answer, and even a successful MiniMax response could not be parsed, because the response shape differs from the DashScope one. The result was that image generation was simply unavailable for MiniMax users, on both the global and the China endpoint, with a confusing error rather than a clear one. This change makes the existingimage_gentool work for those users without asking them to switch providers for images only, and it covers both regional endpoints so China-based and international accounts are equally supported.Reviewer Test Plan
How to verify
Unit level, no credentials needed:
Expected: the suite passes, including the added cases that pin the MiniMax request URL for a regional base URL and for a fully qualified endpoint URL, the exact MiniMax request body, the base64 response branch, and the error message produced when MiniMax reports a non-zero status code inside its response envelope.
End to end, with a MiniMax API key: configure the MiniMax provider with the global base URL (or the China one), select
image-01as the image model, then ask the agent to generate an image so theimage_gentool runs. Expected: onePOSTto the MiniMax image generation endpoint carrying the MiniMax field names, and the returned PNG saved locally. Before this change the same configuration sent a DashScope-shaped request to a DashScope path and no image was produced. A failure injected on the MiniMax side (for example an invalid key) should now report MiniMax's own status code and message.Regression check: the DashScope image path should behave exactly as before, since routing only triggers on the two MiniMax hosts.
Evidence (Before & After)
This is a service and provider-preset change with no TUI surface, so there are no screenshots. The observable before/after is on the wire and in the tool result: before, a MiniMax image base URL yielded a DashScope-shaped request and no image; after, it yields a MiniMax image generation request and a saved PNG. Both response shapes MiniMax can return (link and inline base64) are exercised by the added unit tests, and end-to-end runs against a local stand-in MiniMax origin are recorded in this thread.
Tested on
Environment (optional)
Linux, Node 22, unit tests only for the local run; CI covers the cross-platform matrix and is green on this head.
Risk & Scope
Linked Issues
None.
中文说明
这个 PR 做了什么
图像生成服务现在能够识别 MiniMax 的图像 base URL,并按 MiniMax 自己的规范发起请求,而不再复用 DashScope 的请求结构。当配置的图像 base URL 指向 MiniMax 的全球或中国站主机时,请求会以
POST方式发送到 MiniMax 的图像生成端点,使用Bearer方式携带 API key,并采用 MiniMax 的字段名(model、prompt、n、prompt_optimizer、response_format,在显式指定尺寸时再加上width/height)。响应从 MiniMax 的返回体中解析:返回的图像列表既接受 HTTPS 链接(通过既有的 PNG 下载流程下载),也接受内联 base64 数据(直接解码,并校验真实的 PNG 签名)。MiniMax 在响应信封中报告的应用级错误,包括以 HTTP 200 状态返回的那些,现在会带上该服务自身的状态码和错误信息抛出,而不是一条笼统的"未包含图像 URL"提示。最后,两个 MiniMax 纯图像模型被注册到 MiniMax provider 预设中,从而可以被选为图像模型。DashScope 的路径未被改动:只有那两个 MiniMax 官方主机上的图像生成路径才会触发路由,因此所有既有 provider 的行为保持不变,URL 下载分支也继续沿用与之前相同的主机校验、重定向处理、大小上限和 PNG 签名检查。
为什么需要
在 MiniMax 上使用本 CLI 的用户可以为对话配置 MiniMax provider,但
image_gen工具的生成路径和请求结构是写死为 DashScope 的。因此把图像模型指向 MiniMax 只会产生一个 MiniMax 无法处理的请求;即使 MiniMax 成功返回,也无法被解析,因为其响应结构与 DashScope 不同。结果就是 MiniMax 用户在全球站和中国站上都无法使用图像生成,而且只会看到一个含义不清的报错。这次改动让既有的image_gen工具对这些用户可用,无需他们仅为了生成图像而切换 provider;同时覆盖两个区域端点,使中国境内和国际账号获得同等支持。审查者测试计划
如何验证
单元测试层面,无需凭证:
预期:测试套件通过,其中包含新增用例——分别针对区域 base URL 和完整端点 URL 固定 MiniMax 请求 URL、校验准确的 MiniMax 请求体、覆盖 base64 响应分支,以及当 MiniMax 在响应信封中报告非零状态码时所产生的错误信息。
端到端,需要 MiniMax API key:用全球站(或中国站)base URL 配置 MiniMax provider,选择
image-01作为图像模型,然后让 agent 生成一张图片以触发image_gen工具。预期:向 MiniMax 图像生成端点发出一次携带 MiniMax 字段名的POST,并把返回的 PNG 保存到本地。在此改动之前,同样的配置会把 DashScope 结构的请求发往 DashScope 路径,且不会产生任何图像。若在 MiniMax 侧注入一个失败(例如无效的 key),现在应当报出 MiniMax 自身的状态码和错误信息。回归检查:DashScope 图像路径的行为应与之前完全一致,因为路由只在那两个 MiniMax 主机上触发。
证据(前后对比)
这是一处服务层与 provider 预设的改动,没有 TUI 界面,因此没有截图。可观察的前后差异体现在网络请求和工具结果上:改动前,MiniMax 图像 base URL 会产生 DashScope 结构的请求且没有图像;改动后,它会产生 MiniMax 图像生成请求并保存一个 PNG。MiniMax 可能返回的两种响应形态(链接与内联 base64)都由新增单元测试覆盖,针对本地替身 MiniMax 源站的端到端运行记录也保留在本讨论串中。
测试环境
运行环境(可选)
Linux、Node 22,本地只运行了单元测试;CI 覆盖跨平台矩阵,且在当前 head 上为绿色。
风险与范围
关联 Issue
无。