Skip to content

feat(*): run the image and video roles on MiniMax through its provider - #1013

Merged
0xKT merged 21 commits into
mainfrom
feat/minimax_media_generation
Oct 21, 2026
Merged

0xKT merged 21 commits into
mainfrom
feat/minimax_media_generation

Conversation

@LivXue

@LivXue LivXue commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Summary

Runs the image and video roles on MiniMax. This carries octo-patch's #432 (MiniMax image references) and #433 (MiniMax image-to-video) and reworks both onto the provider route the review settled on #432: MiniMax is a provider a media section names, not an address or a model name the tool recognises.

  • The registry marks minimax and minimax_cn_api as image_api and, new, video_api. A section naming either borrows that provider's address, key and headers through borrow_media_credentials, and the tool dispatches on media_provider() to MiniMax's own APIs. MINIMAX_API_KEY is not read for these tools, so the tool, the capability report and doctor resolve one key, which settles the blocking note on feat(agent): support MiniMax image references #432 about the readers.
  • Images: POST /v1/image_generation. An input image is the character to keep (subject_reference), one per request as MiniMax documents. A base_resp status is the error class with MiniMax's sentence as the detail, a picture MiniMax's content check withheld is a refusal, the call is recorded as usage, a picture link goes through the guarded download, and the file takes the format its bytes declare.
  • Video: the video section gains provider (VideoToolConfig), the settings save and raven_config accept a provider marked video_api, model.options carries the flag (both generated clients regenerated), and the roles card offers the video role those providers. A video pick writes the provider it runs on, empty for OpenRouter.
  • On MiniMax, MiniMax-H3 and MiniMax-H3-Max take /v2/video_generation and the other models /v1/video_generation with its query and file handoff. A call is text-to-video unless it gives first_frame_image (a local file under the image tool's workspace rule, a URL or a data URL). A params key the model family does not take, a ratio beside a first frame, and adaptive on a text-only call are refused before anything is sent, since MiniMax would ignore the first two silently and takes adaptive only beside a frame; a concrete ratio is passed through. The finished video is downloaded through the guarded fetch, without the key.
  • Off MiniMax, and read off the class, both tools' schemas are byte-identical to main's. On MiniMax they say what MiniMax takes: the tools author to_schema, so the registry serves them per turn, and the search and index readers of description and parameters get the same per-provider answer.
  • A capability row on a named provider names providers.<name>.apiKey, with no OpenRouter variable or credit note, and doctor prints no empty "or env:" line. docs/minimax-media.md replaces the two pages the PRs added, the settings design records carry a dated amendment, and the changelog has the entry.
  • Raven-PPT draws with the host's image tool, so an unpinned deck takes MiniMax's route from the host. A deck pinned by PPT_IMAGE_MODEL, PPT_IMAGE_API_BASE or PPT_IMAGE_API_KEY used to drop the provider's name, since the deck would have resolved it against the providers its chat runs on, and with the name it lost MiniMax's request shape: the OpenAI Images path on MiniMax's address, which MiniMax answers with 404. A pinned section on a named provider now keeps the name in a file of its own beside the render (product_render.write_rendered_part), with that provider's endpoint: owner-only, named by the render's pid so the same sweep removes it, and resolved there, so the deck's chat providers are untouched. New PPT_IMAGE_PROVIDER puts a deck's pictures on a provider other than the host image role's; it needs PPT_IMAGE_MODEL, carries none of the host's endpoint across, and a name the image tool does not run on draws nothing, with the launch log saying what to set. A PPT_IMAGE_API_BASE without it is an OpenAI-compatible endpoint, as before.
  • The routing gate that decides whether a lane can draw reads the same pin. With <PREFIX>_IMAGE_PROVIDER set it answers for that provider, by the launcher's rule: no for a provider the image tool cannot run on and for a switch without a model, yes for the lane's own image key, no for a base of its own without one, and otherwise the host's live key for that provider. It never counts the host image role's key for another provider, and the key that pays for the lane's words is lent only towards OpenRouter. An unpinned lane is answered as before. The gate stays read-only. The rule is stated twice because a launcher can run on an older raven, so one table drives both sides: the gate's test, and a launcher test that renders each row and asserts the gate opens exactly where a picture request goes out.
  • A launch refused after the deck's image section was written to its own file (at the ACP home, or a failed final write) now removes that file; it is named by the launcher's pid, so no other run's is touched. On a raven from before render parts, a PPT_IMAGE_PROVIDER switch renders inline instead of raising.

The two carried commits keep octo-patch's authorship. Replaying them onto main needed one adaptation each, named in its commit body, because #866 replaced borrow_openrouter_key with borrow_media_credentials and removed borrows_openrouter_key. What the reviews filed, and where it went:

Known gaps, checked and not changed here:

  • The deck's ppt_generate_image describes references as up to six pictures to match or vary, and on MiniMax the image tool sends the first alone, as the character to keep. Its schema does not vary by provider the way image_generate's now does; that belongs with the deck engine, not here.
  • A hand-written video provider the video tool has no request path for is refused in a sentence on every call, while doctor still reports the key it borrows. The settings save and raven_config refuse such a provider.
  • The video download goes through the guarded fetch, which refuses an address a fake-IP resolver hands out, so behind one a MiniMax video arrives as "generated, but the video URL was refused or could not be fetched". The image route asks for base64 and is unaffected.
  • The gate mirrors the launcher in two places only approximately, both rare and both closing rather than opening a route: a lane pinned to the host's own image provider with a base of its own and no key, on a host whose image section holds a key of its own, is refused though the launcher would send that key; and a switch to OpenRouter on the host's chat reads the host's providers.openrouter key, where the launcher takes any host provider block that addresses OpenRouter.
  • A launcher killed outright (SIGKILL) after writing the deck's image file still leaves it until the next successful launch's sweep, as before.
  • No authenticated MiniMax call was made. Requests and answers follow MiniMax's API reference and are mocked; the routes were checked against MiniMax's live API with no key, on api.minimax.io and api.minimaxi.com: POST /v1/image_generation answers its authentication error, POST /v1/images/generations the same 404 as a made-up path.

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

The full-suite counts below were measured on origin/main ba3a3f8 with this branch applied, on Linux with Python 3.12, and were not re-measured since. The branch was rebased onto 55c145c and then onto 05cf9c3, without conflicts and with every earlier commit patch-identical; the review follow-up was verified at 05cf9c3:

  • pytest -n 0 tests/test_agents_ppt_launcher.py tests/test_subagent_routed_lane_readiness.py tests/test_subagent_vendored_agents.py tests/test_media_gen_minimax.py tests/test_media_gen_tool.py tests/test_rpc_model.py: 703 passed, 28 of them new, in a venv built by uv sync --frozen --python 3.12 --all-extras; tests/test_config_product_render.py tests/test_subagent_routing_backend.py: 213 passed, 3 skipped.
  • Each new regression was watched failing before its fix: the provider switch raised KeyError, the refused and failed launches left the image file, and five of the gate table's rows disagreed with the launcher. Five mutants of the fixes (the switch's default, the cleanup, the gate falling back to the host image key, the OpenRouter loan on the lane's own key, the chat key lent to another provider) each turned their tests red; the video-set test turned red when one side of the set was changed. The gate's table run with file writes, sockets and key output trapped stayed clean, beside a control mutant for each trap.
  • At 05cf9c3: ruff check, ruff format --check, ty check, lint-imports (10 contracts kept), scripts/check_commit_messages.py, commitlint, scripts/check_large_files.py and scripts/check_source_language.py over the 21 commits: pass, the last beside a CJK control commit it refuses, and commitlint beside a header it refuses.

Earlier rounds:

  • uv run --frozen --python 3.12 --all-extras pytest -q -p no:randomly: 33584 passed, 178 skipped, 2 xfailed, 8 failed. The same 8 fail on clean ba3a3f8 on this machine: five proxy tests in tests/test_config_update_providers.py, test_install_script.py::test_resolve_node_dir_answers_each_case_it_exists_for, test_knowledge_parser_excel.py::test_a_legacy_workbook_is_converted_and_read and test_subagent_node_runtime.py::test_what_cannot_be_read_names_nothing.

  • ruff check and ruff format --check over raven evolver agents plugins-dist tests scripts docs-site, lint-imports (10 contracts kept), deptry raven and ty check raven evolver agents plugins-dist scripts: all pass.

  • scripts/check_large_files.py and scripts/check_source_language.py over origin/main... this branch: exit 0, each beside a control commit it refuses (a 2.1 MiB file; a CJK comment in media_gen.py).

  • scripts/check_commit_messages.py and commitlint --config commitlint.config.cjs over the fifteen commits then on the branch: pass.

  • ui-web: npm run gen:check (generated.ts matches the contract, 245 methods), npm run lint (0 errors; 4 warnings, all in files this PR does not touch), npm run type-check, and npx vitest run --maxWorkers=8: 251 files, 4159 tests passed. On this 32-core machine vitest's default worker count failed a varying set of tests on clean main a8455ba too, mostly 5 s timeouts, each passing when its file ran alone.

  • ui-tui: npm run lint, lint:rpc, lint:i18n and type-check pass. npx vitest run --maxWorkers=8: 2233 passed, 13 skipped, 9 failed, the same 9 (in paintDump, intensityTransition, textInputTypingBurst and userSlabRule) that fail on clean ba3a3f8 here.

  • Off MiniMax, and read off the class, both tools' schemas were compared with main's module for a section on no provider, openrouter, openai and volcengine: byte-identical.

  • Mutation check: each behaviour this PR adds was removed one at a time (27 in the tools and settings, 17 in the deck launcher and its render parts, 2 in Roles.tsx), and every mutant turned at least one test red, beside an unmutated run that is green. The three on the text-only ratio refusal are the guard, the default it fills and the direction of the comparison.

  • Relevant tests pass locally

  • Relevant lint / type checks pass locally

  • User-facing docs or screenshots are updated when needed

Risk

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

A section naming no provider, or one on another provider, behaves as on main, and both tools show it main's schema. A config that wrote provider into the video section by hand was ignored on main and is honoured now: on a provider without a video path every call is refused. MiniMax keys travel only to the address of the provider section that holds them. A deck pinned on a named provider now reads its image section from an owner-only file beside its render, which holds the image key as the render does; an unpinned deck, and one pinned to a base without a provider, renders as before. A route into a lane that pins <PREFIX>_IMAGE_PROVIDER can now open or close differently from before: it opens on the host's key for the pinned provider, and closes where the launcher would draw nothing. Revert the squash commit to remove the routes; a config naming MiniMax for a media role then fails the settings save again.

Related Issues

Supersedes #432 and #433; both are carried here.

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking: reject adaptive ratios before submitting text-only MiniMax H3 jobs

I found one merge-blocking request-validation defect, marked inline.

Coverage: I reviewed the repository rules and domain maps, the full github/main...HEAD diff, the media/config/provider/RPC/UI callers, the 15-commit history, backward compatibility for OpenRouter and plain media sections, test changes for weakening, and the Runtime/Web/TUI/agent architecture boundaries. I also checked the new MiniMax V1/V2 wire shapes against MiniMax's current official API reference.

Verification: uv run pytest over the 11 changed Python test modules passed (1265 passed, 3 platform skips) after syncing the declared dev extra; the first attempt could not collect because that extra was not installed. The focused Roles test passed (36 tests), web type-check and generated-RPC check passed, web lint had 0 errors and 4 warnings in untouched files, and the source-language, large-file, diff, and commit-message checks passed.

Comment thread raven/agent/tools/media_gen.py Resolved
@LivXue

LivXue commented Oct 19, 2026

Copy link
Copy Markdown
Member Author

No Windows compatibility issue found in the inspected scope.

Reviewed head 2c1e525d1d95f9fcffd64bde6977eace683cb50e, base ba3a3f8c546de374b9e290a5bfca7321d7cd0b13. Inspected the complete 40-file diff across raven/agent/tools/media_gen.py, raven/agent/tools/capabilities.py, raven/config, raven/providers/registry.py, raven/rpc, raven/cli/doctor_commands.py, agents/raven-ppt/run.py, rpc-schema/openrpc.json, ui-web, ui-tui, docs, and tests. Exact-head archive blobs and reverse patch applicability were verified.

Windows compatibility notes:

  • product_render._write_owner_only and write_rendered_part guard os.fchmod with hasattr(os, "fchmod"), write UTF-8 JSON via os.open / os.fdopen, and name sidecar files .config.rendered.<pid>.<name>.json so sweep_stale_renders cleans them up by PID liveness on Windows (pid_alive).
  • ImageGenerateTool and VideoGenerateTool resolve local reference and first-frame paths with Path(ref).expanduser().resolve() and enforce restrict_to_workspace via resolved.parents before reading bytes.
  • The new render-part tests guard POSIX mode assertions (0o600) with os.name == "posix" / os.name != "nt".
  • Separately, an API parameter validation thread on raven/agent/tools/media_gen.py (params={"ratio": "adaptive"} on text-only MiniMax V2 calls) is already open in review comment #4316363017.

Verification and limitations:

  • Native Windows 11 / Python 3.13.12 (uv run pytest ... -n 0 -x): 1,496 passed, 3 skipped across the touched PPT launcher, CLI doctor, live config, product render, config update, MiniMax media generation, media tool, raven_config, RPC console/model, tool capabilities, and OpenRPC schema suites.
  • Three skips are explicit POSIX file-mode checks on Windows. No live MiniMax API request, browser UI run, or full-repository test suite was executed in this scheduled check.
Exact verification commands
# suite: 1496 passed, 0 failed, 0 errors, 3 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\139a745da2a44748ab0d7f2cd7c36ab7\pr1013\source\head
uv run pytest tests/test_agents_ppt_launcher.py 'tests/test_cli_doctor_commands.py::test_doctor_flags_a_capability_registered_without_a_key' 'tests/test_cli_doctor_commands.py::test_doctor_names_the_key_of_the_provider_a_section_runs_on' 'tests/test_cli_doctor_commands.py::test_an_unconfigured_capability_is_not_a_failure' tests/test_config_live.py tests/test_config_product_render.py tests/test_config_update_tools.py tests/test_media_gen_minimax.py tests/test_media_gen_tool.py tests/test_raven_config_tool.py 'tests/test_rpc_console.py::test_the_settings_whitelist_is_exactly_this_set' 'tests/test_rpc_console.py::test_the_image_role_may_run_on_volcengine' 'tests/test_rpc_console.py::test_the_image_role_may_run_on_minimax' 'tests/test_rpc_console.py::test_the_video_role_may_run_on_minimax' 'tests/test_rpc_console.py::test_the_video_provider_takes_only_a_provider_the_video_tool_runs_on' tests/test_rpc_model.py tests/test_tool_capabilities.py tests/test_rpc_schema_match.py -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\139a745da2a44748ab0d7f2cd7c36ab7\checks\1013-suite\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\139a745da2a44748ab0d7f2cd7c36ab7\checks\1013-suite\junit.xml'

@LivXue
LivXue force-pushed the feat/minimax_media_generation branch from 2c1e525 to 88447f5 Compare October 20, 2026 02:29
@LivXue
LivXue requested a review from gloryfromca October 20, 2026 02:32

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No blockers; this can merge as far as I am concerned.

The new revision fixes the outstanding MiniMax V2 mode conflict before submission and preserves the valid default, concrete-ratio, and first-frame paths. I resolved my earlier thread.

I covered the repository rules in AGENTS.md/CLAUDE.md and the applicable runtime/UI context constraints; the new diff and surrounding request caller; history via range-diff (all 15 previously reviewed commits are content-equivalent after the rebase, with only this fix added); backward compatibility through the control cases; test integrity (coverage was added, not weakened); and the provider/API architecture boundary. No new domain vocabulary or architectural crossing was introduced.

Verification: uv run pytest tests/test_media_gen_minimax.py tests/test_media_gen_tool.py -q (188 passed), plus git diff --check github/main...HEAD (passed). The visible GitHub checks are passing, with several unit/Windows jobs still running at review time.

@LivXue

LivXue commented Oct 20, 2026

Copy link
Copy Markdown
Member Author

No Windows compatibility issue found in the inspected scope.

Reviewed head 88447f5c419754bd90aa26dd8894a311e2c0472e, base 55c145ceb87a11e06226add05da5372a9cf6f991. Rechecked the complete 40-file comparison against the previous review at 2c1e525d1d95f9fcffd64bde6977eace683cb50e: 35 patches are identical, and the 5 updated patches (raven/agent/tools/media_gen.py, tests/test_media_gen_minimax.py, docs/minimax-media.md, CHANGELOG.md, and rpc-schema/openrpc.json after rebase) were inspected in full. Exact-head archive blobs and reverse patch applicability were verified.

Windows compatibility notes:

  • The new text-only MiniMax V2 ratio: "adaptive" guard in VideoGenerateTool._execute_minimax is pure in-memory parameter validation before any HTTP client or file I/O is created, resolving review comment #4316363017.
  • All six new regression cases in tests/test_media_gen_minimax.py pass on native Windows alongside the existing product_render.write_rendered_part and raven-ppt launcher suites.

Verification and limitations:

  • Native Windows 11 / Python 3.13.12 (uv run pytest ... -n 0 -x): 1,504 passed, 3 skipped across the touched PPT launcher, CLI doctor, live config, product render, config update, MiniMax media generation, media tool, raven_config, RPC console/model, tool capabilities, and OpenRPC schema suites.
  • Three skips are explicit POSIX file-mode checks on Windows. No live MiniMax API request, browser UI run, or full-repository test suite was executed in this scheduled check.
Exact verification commands
# suite: 1504 passed, 0 failed, 0 errors, 3 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\35ab19773c0343e792e4aa67d59bc3ba\pr1013\source\head
uv run pytest tests/test_agents_ppt_launcher.py 'tests/test_cli_doctor_commands.py::test_doctor_flags_a_capability_registered_without_a_key' 'tests/test_cli_doctor_commands.py::test_doctor_names_the_key_of_the_provider_a_section_runs_on' 'tests/test_cli_doctor_commands.py::test_an_unconfigured_capability_is_not_a_failure' tests/test_config_live.py tests/test_config_product_render.py tests/test_config_update_tools.py tests/test_media_gen_minimax.py tests/test_media_gen_tool.py tests/test_raven_config_tool.py 'tests/test_rpc_console.py::test_the_settings_whitelist_is_exactly_this_set' 'tests/test_rpc_console.py::test_the_image_role_may_run_on_volcengine' 'tests/test_rpc_console.py::test_the_image_role_may_run_on_minimax' 'tests/test_rpc_console.py::test_the_video_role_may_run_on_minimax' 'tests/test_rpc_console.py::test_the_video_provider_takes_only_a_provider_the_video_tool_runs_on' tests/test_rpc_model.py tests/test_tool_capabilities.py tests/test_rpc_schema_match.py -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\35ab19773c0343e792e4aa67d59bc3ba\checks\1013-suite\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\35ab19773c0343e792e4aa67d59bc3ba\checks\1013-suite\junit.xml'

@0xKT

0xKT commented Oct 20, 2026

Copy link
Copy Markdown
Member

Not a blocker (review of 88447f5c, merged onto 05cf9c35): three places where the new PPT_IMAGE_PROVIDER pin does not reach everything that depends on it, and one nit. I reproduced the first three with the launcher's own render_config in a scratch home, with the deck's draw recorded on a mock transport. Line numbers are in this head merged onto 05cf9c35.

Severity of this one finding, not a verdict on the pull request. Any blocking findings from this pass are review threads on the changed files; GitHub renders those collapsed, as a file name with no text.

  1. On a raven without render parts, a provider switch crashes the whole deck launch.

    • run.py:270 says this file "can outlive its raven -- a stamped home tree outranks a checkout's own agents/", and run.py:286 says a raven from before render parts writes a pinned section inline. Render parts are new here, so main's raven (05cf9c35) is such a raven: it has media_provider and no rendered_part.
    • On a switch, the loop at run.py:315-317 pops provider from the section. With no render parts own_file is False, so the branch at run.py:344 runs and del image["provider"] at run.py:350 raises.
    • Reproduced with this PR's run.py on main's raven/ package: host image role on OpenRouter, PPT_IMAGE_PROVIDER=openai and PPT_IMAGE_MODEL=gpt-image-2-mini, with or without PPT_IMAGE_API_KEY. render_config raises KeyError: 'provider' at run.py:350, so the deck does not start at all. PPT_IMAGE_MODEL alone renders.
    • image.pop("provider", None) at that line fixes it. I re-ran the same cases with it: they render inline on https://api.openai.com/v1 with the host's providers.openai key, as the comment at run.py:286 describes. Every path that passes today reaches that line with the key present, so nothing else changes. test_a_raven_without_render_parts_keeps_a_pinned_section_inline pins PPT_IMAGE_MODEL only; a switch case there would cover this.
  2. The route gate was not taught PPT_IMAGE_PROVIDER, so it and the launcher now disagree about whether the deck can draw.

    • product_image_key says it is "the folder-side half of what configure_image_generation renders ... in that function's own precedence" (raven/agent/subagent/vendored_agents.py:295-310), and _lane_generates_images adds the host's image section (raven/agent/loop/wiring.py:333-351). raven-design's route to Raven-PPT declares "needs": ["image_generation", "image_search"], so this answer opens or closes that route. This PR changes configure_image_generation but not the mirror.
    • The setup .env.example:68 documents as "the deck pays with the host's key for that provider": host chat on MiniMax with providers.minimax keyed and no image role, deck PPT_IMAGE_PROVIDER=minimax and PPT_IMAGE_MODEL=image-01. The deck draws (POST https://api.minimax.io/v1/image_generation with the host's key), but the gate answers False, so the route from raven-design stays closed.
    • The other direction: a deck on its own OpenRouter PPT_API_KEY with PPT_IMAGE_PROVIDER=minimax and a model, on a host with no MiniMax key. The gate answers True (it lends PPT_API_KEY towards OpenRouter), but the deck has no key for MiniMax and draws nothing. The launcher's two new refusals (a provider with no image route, and a switch without PPT_IMAGE_MODEL) also leave the gate open.
    • The own-key example in docs/minimax-media.md (provider, key and model together) agrees on both sides. Teaching product_image_key the pin would bring the mirror back to its docstring: a switched deck lends nothing towards OpenRouter, and with no deck key the host's key for the named provider pays.
  3. A launch refused after the image step leaves the deck's key in a file.

    • serve() says "no file holding merged secrets should exist for a run that cannot start" (run.py:756-758). The new part file is written inside render_config at run.py:391, before steps that can still refuse, such as product_acp_home at run.py:721 (raven/config/product_render.py:617-626).
    • Reproduced: host image role on MiniMax with the deck's own PPT_IMAGE_API_KEY (the "own key on a host already on MiniMax" setup in docs/minimax-media.md), and a placement that product_acp_home refuses as "cannot be created". The launch exits and .config.rendered.<pid>.image.json (mode 0600, holding the deck key) stays in the state root. With nothing pinned, the same refusal leaves nothing.
    • The sweep at run.py:741 comes after that refusal, so while the refusal persists, each attempt adds one more file. Three refused attempts left three files, and the first launch that gets past the refusal sweeps them. The files are owner-only, so this is hygiene. Writing the part just before write_rendered (run.py:750), or removing it on the way out, would keep the promise.
  4. Nit. Two places write down which providers the video tool runs on. One is the registry's video_api, read by the save, the options row and the raven_config choices. The other is media_gen.py's own dispatch: _MINIMAX_PROVIDERS at :484 plus the "openrouter" literal at :1485. They agree today, and tests/test_rpc_model.py:213 pins the registry side as a literal list. Nothing ties the two together, so a provider given video_api=True alone would be offered and saved, and every call would then answer "no request path for this provider". A test asserting that the two sets are equal would leave one rule.

octo-patch and others added 18 commits October 21, 2026 03:47
Replayed onto main by a maintainer. One test now calls
borrow_media_credentials, the helper #866 put in place of
borrow_openrouter_key, with the author's assertions unchanged.
Replayed onto main by a maintainer. Its guard in borrows_openrouter_key,
which #866 removed, moved to borrow_media_credentials: a MiniMax video
model still borrows no OpenRouter key.
MiniMax becomes a provider the image tool runs on, chosen the way any
other is: tools.media.image.provider names minimax or minimax_cn_api, the
section borrows that provider's address, key and headers, and the tool
answers it with MiniMax's own /v1/image_generation. The registry marks
both sections image_api, so the roles card offers them and the save
takes them.

This replaces the reference route's own detection. There is no address
test and no key chain of its own, and MINIMAX_API_KEY is not read, so the
tool, the capability report and doctor resolve one key.

An input image is the character to keep (subject_reference), one per
request as MiniMax documents. A status MiniMax answers inside HTTP 200 is
the error class, with its sentence as the detail. A picture its content
check withheld is a refusal, not an empty answer. The call is recorded as
usage, a picture link goes through the guarded download, and the file
takes the format its bytes declare.
…er it

The video section gets the image section's provider field, and the
registry a video_api flag: OpenRouter, and MiniMax's two API-key
sections. The settings save and raven_config take a provider marked
video_api, model.options carries the flag, and the roles card offers the
video role those providers. A video pick writes the provider it runs on,
empty for OpenRouter, so a pick back to OpenRouter moves the role off
MiniMax.

A capability row on a named provider names that provider's key, and
neither OpenRouter's variable nor its credit note; doctor prints no empty
"or env:" line. The settings design records carry a dated amendment.
A video section on minimax or minimax_cn_api now takes MiniMax's video
API instead of the model-name route: MiniMax-H3 and MiniMax-H3-Max on
/v2/video_generation, the other models on /v1/video_generation with its
query and file handoff. The address, key and headers are the provider's,
borrowed, so the tool and every reader of the section agree on the key.

A call is text-to-video unless it gives first_frame_image, which may be a
local file (read under the workspace rule the image tool keeps), a URL or
a data URL. A params key the model family does not take is refused before
anything is sent, as is a ratio beside a first frame, which MiniMax would
ignore. The client-side value checks are gone: they were per-model
tables, one already stricter than the API. The finished video is
downloaded through the guarded fetch, without the key. A video section
naming a provider with no video path, and a first frame off MiniMax, are
refused in so many words.
docs/minimax-media.md replaces the two pages the carried commits added,
which described the address-and-environment setup this branch removed.
The V2 content item spelled the image part by hand, which the
image_block guard refuses outside raven/utils/images.py.
MiniMax-H3-Max documents extra.prompt_expansion_mode, and the params
check refused it as unknown.
…n MiniMax

The image and video tools author their schema, so the registry serves it
per turn, and the provider the section runs on picks between two: off
MiniMax each is byte-identical to main's, and on MiniMax it says what
MiniMax takes (a character reference; a first frame and MiniMax's
params). A model on OpenRouter is no longer offered a first frame it
would be refused.
…ts class

The per-provider schema made description and parameters properties, so
ImageGenerateTool.parameters read off the class was the property object,
and the subagent routing test that takes the reference argument's
spelling off the host tool failed. A small descriptor answers MiniMax's
schema on a tool that runs on MiniMax, and the default one elsewhere and
on the class, which has no section to ask.
… name one

A deck pinned by PPT_IMAGE_MODEL, PPT_IMAGE_API_BASE or
PPT_IMAGE_API_KEY read its image section as a snapshot in its own
render, with the provider's name dropped, since the deck would have
resolved the name against the providers its chat runs on. The name took
MiniMax's request shape with it: a MiniMax host's pinned deck called the
OpenAI Images path on MiniMax's address, which MiniMax answers with 404.

A pinned section on a named provider now keeps the name and is written
to a file of its own beside the render: product_render's new
write_rendered_part names it by the render's pid and writes it
owner-only as the render is, so the sweep that removes a dead server's
render removes it too, and the render's naming stays in that one module.
The file carries that provider's endpoint and the deck resolves the
section there, so the providers its chat runs on stay as they were. The
selectionConfig field now says it is a file the section is read from
against that file's providers, the host's or a pinned deck's own. A
raven from before render parts renders the section inline, as it did.

PPT_IMAGE_PROVIDER names a provider other than the host image role's.
Neither the host's model nor its endpoint crosses to it, so
PPT_IMAGE_MODEL is required with it; a name the image tool does not run
on draws nothing, and the launch log says what to set. A
PPT_IMAGE_API_BASE without it is called as an OpenAI-compatible
endpoint, as before.
The test holds the rule that MiniMax's request shape follows the
provider a section names, but its mock answered the OpenAI Images path
on MiniMax's address with a picture. MiniMax's API answers that path
with its router's plain 404, so the mock now does too, and the test
holds the schema to the same rule.
The text-only branch set ratio to 16:9 before spreading the caller's params
over it, so an explicit adaptive replaced that default and reached an API
that takes adaptive only beside a first frame. The call is now refused
before an HTTP client exists, naming the two ways to make it valid. The
first-frame branch and a concrete ratio are unchanged.

Covered for both providers and both V2 models, with the default, an explicit
9:16 and a first frame's adaptive as controls. Schema text and
docs/minimax-media.md say the same.
… parts

A switch to another image provider already takes the host's provider name
off the section. On a raven from before render parts the pinned section is
written inline, and that branch then deleted the name a second time and
raised KeyError, so the whole deck launch failed. The name is now dropped
only if it is still there.

Covered for a switch from OpenRouter to OpenAI with and without the deck's
own image key: the section is rendered inline on OpenAI's address with the
host's OpenAI key or the deck's, and carries no provider name and no
selectionConfig.
A pinned image section is written to a file of its own, holding the deck's
image key, before the launcher places the ACP home, builds the mode
catalogue and writes the render. A launch refused at any of those steps
never reached the sweep, so the owner-only file stayed behind until a later
launch succeeded, one more per refused pid.

render_config now removes that file on any exit that is not a finished
render, SystemExit included, and re-raises. The file is named by this pid,
so removing it cannot touch another run's.

Covered by a launch refused at the ACP home through a real placement
refusal, and by a render whose final write fails; the owner-only mode, the
live read and the dead-pid sweep keep their tests.
…auncher does

The gate deciding whether a routed lane can draw ignored the lane's
<PREFIX>_IMAGE_PROVIDER, so it answered for the host's image role while the
launcher configured another provider. Measured both ways: a deck pinned to
MiniMax on the host's MiniMax key, the configuration .env.example documents,
was refused, and the route from Raven-Design to Raven-PPT closed; a deck
pinned to a provider it cannot draw on, or with no model for a switch, or
with no key for the target, was admitted on the host's image key for
another provider, or on the deck's OpenRouter chat key.

product_image_pin reads the folder's provider and model the way the
launcher does, and product_image_key no longer lends the chat key to a
provider other than OpenRouter. With a pin, the gate refuses a provider the
image tool cannot run on and a switch without a model, accepts the lane's
own image key, refuses a base of its own with no key, and otherwise asks
the host's live providers for the target's key. It never falls back to the
host image role's key for another provider. Unpinned lanes keep the
previous answer. The gate stays read-only.

The rule is stated twice because a launcher can run on an older raven, so
one table drives both sides: the gate's test, and a launcher test that
renders each row, sends the picture request through a mock transport and
asserts the gate opens exactly where a request goes out. Provider aliases,
process environment over the folder .env, and a host key revoked live are
covered too.
…paths

The registry's video_api decides which providers the roles card offers for
the video role, and the tool's dispatch decides which ones run. Nothing tied
the two together: the RPC test pins the registry side to a literal list. The
new test asserts the offered set equals the providers the video tool has a
request path for, so drift on either side fails it.
… docstrings

"product" is the retired category word for a discovered agent; the two
docstrings the routing gate fix added used it in prose.
@LivXue
LivXue force-pushed the feat/minimax_media_generation branch from 88447f5 to 59a6eb5 Compare October 21, 2026 03:54
@LivXue

LivXue commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

@0xKT thanks, all four reproduced here before fixing, and all four are fixed in this push (head 59a6eb57, rebased onto 05cf9c35; the earlier commits are patch-identical). Each new test was watched failing on 88447f5c first.

  1. Provider switch on a raven without render parts -- 04ccbf8a. image.pop("provider", None) at that line, as you suggested. New test_a_raven_without_render_parts_renders_a_provider_switch_inline, beside the model-only one, switches from OpenRouter to openai with and without PPT_IMAGE_API_KEY: before the fix both raised KeyError: 'provider'; now both render inline on https://api.openai.com/v1 with the host's providers.openai key or the deck's, with no provider and no selectionConfig.

  2. Route gate and PPT_IMAGE_PROVIDER -- f0f4f02f. One deviation from the shape you named: teaching product_image_key the pin was necessary but not enough. With it returning nothing for a switched deck, _lane_generates_images still fell back to the host image role's key, so a deck switched to MiniMax with no MiniMax key anywhere stayed open on the host's OpenRouter image key. So both halves changed. product_image_pin reads the folder's provider and model, product_image_key lends the chat key only towards OpenRouter, and with a pin the gate follows the launcher's rule: no for a provider without an image route and for a switch without a model, yes for the deck's own image key, no for a base of its own without one, otherwise the host's live key for the pinned provider. It never counts the host image key for another provider. One table of nine rows drives both sides: the gate's test, and a launcher test that renders each row and asserts the gate is True exactly where a picture request reaches the mock transport. On 88447f5c five of the six rows that mirror your cases disagreed, your .env.example:68 case among them (gate False, deck draws); now all nine agree. Aliases (MiniMax, MINIMAX), process env over the folder .env, and a host key removed while running are covered as well. The gate stays read-only: the table, run with file writes, sockets and key output trapped, stayed clean, and a control mutant for each trap was caught.

  3. Image file left by a refused launch -- 9bdee0aa. I took your second option. render_config wraps its body and, on any exit that is not a finished render (SystemExit included), removes this pid's part and re-raises. Deferring the write would also have needed a cleanup for a failed final write, and the pid-named part can only be this run's. Tests: your product_acp_home refusal (a file where subagent_sessions should be) and a failing write_rendered each leave no .config.rendered.* behind; before the fix both left the 0600 part.

  4. Nit -- 6db99167. test_the_providers_offered_for_video_are_the_ones_the_video_tool_has_a_path_for asserts {s.name for s in PROVIDERS if s.video_api} == media_gen._MINIMAX_PROVIDERS | {"openrouter"}; green today, red when _MINIMAX_PROVIDERS was narrowed by hand.

Mutants: removing the pop default, the cleanup, the gate's no-fallback ending, the OpenRouter-only loan, and the own-key check on an OpenRouter switch each turned their tests red. The six files you would run (test_agents_ppt_launcher, test_subagent_routed_lane_readiness, test_subagent_vendored_agents, test_media_gen_minimax, test_media_gen_tool, test_rpc_model): 703 passed, 28 new. Two places where the gate stays an approximation of the launcher, both rare and both closing rather than opening a route, are listed under Known gaps in the description. Linux only this round: not re-run on Windows or macOS, and every request was mocked with synthetic keys.

Could you take another look?

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No blockers; this can merge as far as I am concerned.

Reviewed the five follow-up commits and separated them from the rebase: range-diff shows the 16 previously reviewed commits are patch-identical. The provider-switch compatibility fix, failed-render cleanup, pinned-provider routing gate, video-provider contract test, and vocabulary correction all hold under their callers and controls; I found no concrete regression after trying to refute them.

Coverage this round: repository rules and canonical vocabulary; the actual diff plus surrounding launcher, gate, and media-tool callers; history via range-diff; old-raven and unpinned-path backward compatibility; test integrity (new regressions and shared launcher/gate cases, with no weakened assertions); and the provider, secret, routing, and rendered-part architecture boundaries. My prior thread remains resolved.

Verification: uv run pytest tests/test_agents_ppt_launcher.py tests/test_subagent_routed_lane_readiness.py tests/test_subagent_vendored_agents.py tests/test_media_gen_minimax.py tests/test_media_gen_tool.py tests/test_rpc_model.py -q (703 passed), and git diff --check github/main...HEAD passed. Visible GitHub checks were passing, with four unit shards still running at review time.

@0xKT
0xKT merged commit d9c0a1d into main Oct 21, 2026
24 of 27 checks passed
@0xKT
0xKT deleted the feat/minimax_media_generation branch October 21, 2026 04:03
@0xKT 0xKT mentioned this pull request Oct 27, 2026
4 of 12 tasks
0xKT added a commit that referenced this pull request Oct 27, 2026
## Summary

Bump the package version from 0.2.5 to 0.2.6 for the next release. Only
pyproject.toml and uv.lock change.

Since v0.2.5, main merged 70 PRs: 15 features, 48 fixes, three test, two
docs,
one perf and one chore. The larger features are portable Agent Plugins
1.0
packages with their MCP servers (#1110), the compacting history strategy
with
source refs and checkpoints (#1031), sub-agent runs as their own
trajectory
spans and tabs (#1091, #1094, #1096, #1100), download mirror fallback
(#1053),
Keenable as a keyless web_search and web_fetch vendor (#1088), Opper as
an
OpenAI-compatible gateway (#869), and the MiniMax image and video roles
(#1013).
None carries a BREAKING CHANGE footer. The maintainer chose a patch
bump, the
way v0.2.1 through v0.2.5 were for their features.

A v0.2.6-rc1 tag goes first to dry-run the release pipeline, as v0.2.5
did. The
stable tag and release notes follow once this lands.

uv.lock is regenerated with uv 0.12.12, which leaves only the raven
version
line changed (uv 0.9.x rewrites unrelated dependency markers and uv
0.13.x
raises the lock revision).

## Type

- [ ] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [x] Other

## Verification

- `uvx --from uv==0.12.12 uv lock` on a clean worktree cut from
origin/main:
  Updated raven v0.2.5 -> v0.2.6
- `git diff --numstat origin/main..HEAD`: one line each in
pyproject.toml and
  uv.lock
- `uvx --from uv==0.13.3 uv lock --check`: exit 0, so the
  `uv export --locked` step in release.yml accepts the lock
- `PYTHONPATH=. python3 scripts/check_commit_messages.py
origin/main..HEAD`:
  exit 0
- `npx commitlint --from origin/main --to HEAD --config
commitlint.config.cjs`:
  exit 0
- No test run: the change is version metadata only.

- [ ] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed

## Risk

No code change. Rollback is reverting this commit before any v0.2.6 tag
is
pushed.

- [ ] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

## Related Issues

N/A

Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
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.

4 participants