feat: proxy MiniMax H3 video generation - #1427
Conversation
📝 WalkthroughWalkthrough变更概览本次变更新增 OpenAI 视频生成 v2 端点支持。系统新增 Changes视频生成 v2 代理
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| accountingTier: "none", | ||
| modelRequired: false, | ||
| rawPassthrough: false, | ||
| match: (pathname) => hasPrefix(pathname, "/v2/query/video_generation"), |
There was a problem hiding this comment.
Query path duplicates base segments
If a provider uses the supported base URL https://api.minimax.io/v2/video_generation, a task query falls through to standard URL concatenation and produces /v2/video_generation/v2/query/video_generation/{task}, causing the upstream request to fail on a nonexistent path.
Knowledge Base Used: Proxy request pipeline
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/endpoint-family-catalog.ts
Line: 151
Comment:
**Query path duplicates base segments**
If a provider uses the supported base URL `https://api.minimax.io/v2/video_generation`, a task query falls through to standard URL concatenation and produces `/v2/video_generation/v2/query/video_generation/{task}`, causing the upstream request to fail on a nonexistent path.
**Knowledge Base Used:** [Proxy request pipeline](https://app.greptile.com/ygxz/-/custom-context/knowledge-base/ding113/claude-code-hub/-/docs/proxy-pipeline.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/v1/_lib/proxy/video-generation-v2.ts`:
- Around line 53-89: Update the validation returns in video-generation-v2.ts
around the request checks to use stable validation error codes instead of
hardcoded English messages, while generating localized messages at the response
boundary based on the request language. Update
tests/unit/proxy/video-generation-v2.test.ts lines 25-39 to assert the relevant
error codes rather than English message fragments.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: cd72d19f-1d7f-4e10-a8a4-ff28a0ff7183
📒 Files selected for processing (11)
src/app/v1/_lib/proxy/endpoint-family-catalog.tssrc/app/v1/_lib/proxy/forwarder.tssrc/app/v1/_lib/proxy/video-generation-v2.tssrc/app/v2/[...route]/route.tssrc/proxy.matcher.tssrc/proxy.tstests/unit/app/v1/url.test.tstests/unit/proxy-matcher.test.tstests/unit/proxy/endpoint-family-catalog.test.tstests/unit/proxy/endpoint-family-provider-routing.test.tstests/unit/proxy/video-generation-v2.test.ts
| if (body.model !== TEXT_TO_VIDEO_MODEL) { | ||
| return fail(`Invalid request: model must be ${TEXT_TO_VIDEO_MODEL}.`); | ||
| } | ||
|
|
||
| if (!Array.isArray(body.content) || body.content.length !== 1) { | ||
| return fail("Invalid request: text-to-video content must contain exactly one text item."); | ||
| } | ||
|
|
||
| const content = body.content[0]; | ||
| if (typeof content !== "object" || content === null || Array.isArray(content)) { | ||
| return fail("Invalid request: content must be an object."); | ||
| } | ||
| const textContent = content as Record<string, unknown>; | ||
| if (textContent.type !== "text") { | ||
| return fail("Invalid request: text-to-video content type must be text."); | ||
| } | ||
| if (typeof textContent.text !== "string" || textContent.text.trim().length === 0) { | ||
| return fail("Invalid request: text-to-video requires a non-empty text prompt."); | ||
| } | ||
| if (textContent.text.length > 7000) { | ||
| return fail("Invalid request: text prompt must not exceed 7000 characters."); | ||
| } | ||
|
|
||
| if (body.resolution !== TEXT_TO_VIDEO_RESOLUTION) { | ||
| return fail(`Invalid request: resolution must be ${TEXT_TO_VIDEO_RESOLUTION}.`); | ||
| } | ||
| if (!Number.isInteger(body.duration) || Number(body.duration) < 4 || Number(body.duration) > 15) { | ||
| return fail("Invalid request: duration must be an integer from 4 to 15 seconds."); | ||
| } | ||
| if (typeof body.ratio !== "string" || !TEXT_TO_VIDEO_RATIOS.has(body.ratio)) { | ||
| return fail("Invalid request: text-to-video requires a supported non-adaptive ratio."); | ||
| } | ||
| if ( | ||
| body.callback_url !== undefined && | ||
| (typeof body.callback_url !== "string" || !isHttpUrl(body.callback_url)) | ||
| ) { | ||
| return fail("Invalid request: callback_url must be an HTTP or HTTPS URL."); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- target implementation ---'
sed -n '1,180p' src/app/v1/_lib/proxy/video-generation-v2.ts
printf '%s\n' '--- related tests ---'
sed -n '1,180p' tests/unit/proxy/video-generation-v2.test.ts
printf '%s\n' '--- error and localization references ---'
rg -n --glob '*.{ts,tsx,json}' 'ProxyError|next-intl|Invalid request|error code|errorCode|validate\(' src tests | head -240
printf '%s\n' '--- v1 proxy structure ---'
fd -t f . src/app/v1 | sort | head -160Repository: ding113/claude-code-hub
Length of output: 35867
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- validator call sites ---'
rg -n -C 8 'validateVideoGenerationV2TextRequest|VIDEO_GENERATION_V2_CREATE_PATH|video_generation' src/app/v1 tests | head -320
printf '%s\n' '--- proxy error definitions and handling ---'
sed -n '1,260p' src/app/v1/_lib/proxy/errors.ts
sed -n '1,280p' src/app/v1/_lib/proxy/error-handler.ts
sed -n '150,230p' src/app/v1/_lib/proxy-handler.ts
printf '%s\n' '--- route and response construction ---'
sed -n '1,260p' 'src/app/v1/[...route]/route.ts'
printf '%s\n' '--- API error localization flow ---'
rg -n -C 10 'api-error-i18n|errorCode.*detail|errorParams|request.validation_failed|messages.*error' src tests | head -320Repository: ding113/claude-code-hub
Length of output: 50379
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- validation throw and response path ---'
sed -n '3308,3355p' src/app/v1/_lib/proxy/forwarder.ts
rg -n -C 6 'buildError|clientErrorMessage|resolveFinalClientErrorMessage|getLocale' src/app/v1/_lib/proxy/error-handler.ts src/app/v1/_lib/proxy/responses.ts src/app/v1/_lib/proxy-handler.ts
printf '%s\n' '--- proxy response implementation ---'
rg -n 'class ProxyResponses|buildError|errorCode|detail|message' src/app/v1/_lib/proxy/responses.ts src/app/v1/_lib/proxy/errors.ts | head -180
sed -n '1,180p' src/app/v1/_lib/proxy/responses.ts
printf '%s\n' '--- locale use in v1 code ---'
rg -n -C 4 'getLocale|Accept-Language|accept-language|locale' src/app/v1 src/i18n | head -220
printf '%s\n' '--- translation message structure ---'
fd -t f . messages src | rg '(^|/)(en|zh-CN|zh-TW|ja|ru)(/|\\.|$)|messages|error-messages'
sed -n '1,240p' src/lib/utils/error-messages.tsRepository: ding113/claude-code-hub
Length of output: 50381
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- proxy API error contract tests ---'
rg -n -C 8 'invalid_request|invalid_request_error|error\.message|ProxyResponses\.buildError|ProxyErrorHandler' tests/api tests/integration tests/unit/proxy | head -260
printf '%s\n' '--- existing proxy validation error patterns ---'
rg -n -C 3 'new ProxyError\(|return fail\(|Invalid request:' src/app/v1/_lib/proxy src/app/v1/_lib | head -280
printf '%s\n' '--- frontend API error translation boundary ---'
rg -n -C 8 'function getApiErrorMessageKey|class ApiError|getApiErrorMessageKey|errorCode' src/lib src/hooks src/components | head -220
printf '%s\n' '--- read-only call-graph invariant check ---'
python3 - <<'PY'
from pathlib import Path
validator = Path("src/app/v1/_lib/proxy/video-generation-v2.ts").read_text()
forwarder = Path("src/app/v1/_lib/proxy/forwarder.ts").read_text()
responses = Path("src/app/v1/_lib/proxy/responses.ts").read_text()
checks = {
"validator returns message field": "ok: false; message: string" in validator,
"forwarder throws ProxyError from validation message":
"throw new ProxyError(videoValidation.message, 400);" in forwarder,
"ProxyResponses serializes message":
'"message": string' in responses and "message," in responses,
"ProxyResponses derives code from status/type":
"getErrorCode(status, finalType)" in responses,
"ProxyResponses has no validation error-code argument":
"buildError(\n status: number,\n message: string,\n errorType?: string" in responses,
}
for name, result in checks.items():
print(f"{name}: {result}")
PYRepository: ding113/claude-code-hub
Length of output: 50379
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- existing localized proxy error precedent ---'
sed -n '1180,1220p' src/app/v1/_lib/proxy/session.ts
rg -n -C 5 'export (const|type).*ERROR_CODES|function getErrorMessageServer|getErrorMessageServer' src/lib/utils/error-messages.ts
printf '%s\n' '--- exact error response schema and tests ---'
python3 - <<'PY'
from pathlib import Path
forwarder = Path("src/app/v1/_lib/proxy/forwarder.ts").read_text()
handler = Path("src/app/v1/_lib/proxy/error-handler.ts").read_text()
responses = Path("src/app/v1/_lib/proxy/responses.ts").read_text()
validator = Path("src/app/v1/_lib/proxy/video-generation-v2.ts").read_text()
assert "throw new ProxyError(videoValidation.message, 400);" in forwarder
assert "clientErrorMessage = error.getClientSafeMessage();" in handler
assert "ProxyResponses.buildError(" in handler
assert "message," in responses
assert "getErrorCode(status, finalType)" in responses
assert "{ ok: false; message: string }" in validator
print("video validation -> ProxyError(message) -> client error message -> error.message: PASS")
print("response code is derived from HTTP status/type, not a validation-specific code: PASS")
print("validator currently exposes presentation text rather than a machine-readable validation code: PASS")
PYRepository: ding113/claude-code-hub
Length of output: 2932
为视频生成验证错误使用 i18n 和稳定错误码。
验证错误会通过 ProxyError 写入 API 的 error.message。当前消息为硬编码英文,且 error.code 只能表示 HTTP 状态,不能标识具体验证失败。
src/app/v1/_lib/proxy/video-generation-v2.ts:53-89:返回稳定错误码,并在响应边界根据请求语言生成消息。tests/unit/proxy/video-generation-v2.test.ts:25-39:断言错误码,不要断言英文消息片段。
📍 Affects 2 files
src/app/v1/_lib/proxy/video-generation-v2.ts#L53-L89(this comment)tests/unit/proxy/video-generation-v2.test.ts#L25-L39
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/v1/_lib/proxy/video-generation-v2.ts` around lines 53 - 89, Update
the validation returns in video-generation-v2.ts around the request checks to
use stable validation error codes instead of hardcoded English messages, while
generating localized messages at the response boundary based on the request
language. Update tests/unit/proxy/video-generation-v2.test.ts lines 25-39 to
assert the relevant error codes rather than English message fragments.
Source: Coding guidelines
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7573e79e1d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (!Array.isArray(body.content) || body.content.length !== 1) { | ||
| return fail("Invalid request: text-to-video content must contain exactly one text item."); |
There was a problem hiding this comment.
Check H3 content text before forwarding
For /v2/video_generation the user prompt is accepted in top-level content[].text, but the sensitive-word guard runs earlier in CHAT_PIPELINE and extractTextFromMessages only reads prompt, system, messages, and input. Any blocked term inside an H3 prompt therefore produces texts.length === 0 and is forwarded upstream; add extraction/normalization for this content shape before the guard decision.
Useful? React with 👍 / 👎.
| id: "video-generation-v2-create", | ||
| surface: "openai", | ||
| accountingTier: "none", | ||
| modelRequired: true, | ||
| rawPassthrough: false, | ||
| match: (pathname) => pathname === "/v2/video_generation", |
There was a problem hiding this comment.
Teach URL building about video_generation
Adding this /v2/video_generation family without updating buildProxyUrl's endpoint list means providers configured with the version-root base URL pattern that the helper supports elsewhere, e.g. https://api.minimax.io/v2, are called as https://api.minimax.io/v2/v2/video_generation because /video_generation does not match any endpoint regex and falls through to plain concatenation. Add the new video paths to targetEndpoints so version-root and endpoint-root provider URLs route correctly.
Useful? React with 👍 / 👎.
| if (body.model !== TEXT_TO_VIDEO_MODEL) { | ||
| return fail(`Invalid request: model must be ${TEXT_TO_VIDEO_MODEL}.`); |
There was a problem hiding this comment.
Localize video validation failures
These new validation failures are returned to clients through ProxyError, but the messages are hardcoded English literals. The repository requires user-facing strings to use i18n across the supported languages, so invalid /v2/video_generation requests bypass the localized error catalog; route these messages through the existing i18n/error helper instead.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Code Review Summary
The new video-generation routing, schema validation, and endpoint-family registration are well-structured and follow established patterns in the codebase (the v2 route mirrors v1beta, the endpoint-family overlap between create/resources matches the response-* families, and the validator is correctly placed after request transformations and before serialization). No code-level defects met the reporting threshold across all six review perspectives.
PR Size: M
- Lines changed: 261 (258 additions / 3 deletions)
- Files changed: 11
Issues Found
| Category | Critical | High | Medium | Low |
|---|---|---|---|---|
| Logic/Bugs | 0 | 0 | 0 | 0 |
| Security | 0 | 0 | 0 | 0 |
| Error Handling | 0 | 0 | 0 | 0 |
| Types | 0 | 0 | 0 | 0 |
| Comments/Docs | 0 | 0 | 0 | 0 |
| Tests | 0 | 0 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Standards Note (Action Recommended)
Base branch targets main instead of dev. CLAUDE.md states under Repository Info:
PR Target Branch:
dev(all pull requests must target the dev branch)
This PR should be re-targeted to dev before merge to comply with the repository's contribution policy.
Review Coverage
- Logic and correctness - validator ordering, path matching via
normalizeEndpointPath, endpoint-family overlap resolution - Security (OWASP Top 10) -
callback_urlvalidated as http(s) and forwarded only (not fetched server-side); no injection surface - Error handling -
isHttpUrlcatch surfaces as a 400 validation error; no silent/swallowed failures - Type safety - casts guarded by runtime validation; no
anyusage - Documentation accuracy - matcher comments in
proxy.matcher.ts/proxy.tscorrectly updated to includev2 - Test coverage - happy path, 8 rejection cases, and non-create bypass covered
- Code clarity - clean, readable, consistent with existing proxy families
Automated review by Claude AI
Reason: MiniMax-H3 video generation requests need native
/v2routing and request validation.This change exposes the
/v2proxy route, adds video-generation endpoint families, preserves regional upstream paths, and validates the supported text-to-video request schema before forwarding. Focused routing and schema tests cover the new endpoint alongside existing proxy behavior.Checks:
./node_modules/.bin/biome check src/app/v2/[...route]/route.ts src/app/v1/_lib/proxy/video-generation-v2.ts src/app/v1/_lib/proxy/endpoint-family-catalog.ts src/app/v1/_lib/proxy/forwarder.ts src/proxy.matcher.ts src/proxy.ts tests/unit/proxy-matcher.test.ts tests/unit/proxy/endpoint-family-catalog.test.ts tests/unit/proxy/endpoint-family-provider-routing.test.ts tests/unit/proxy/video-generation-v2.test.ts tests/unit/app/v1/url.test.ts./node_modules/.bin/vitest run --configLoader bundle tests/unit/proxy/video-generation-v2.test.ts tests/unit/proxy/endpoint-family-catalog.test.ts tests/unit/proxy/endpoint-family-provider-routing.test.ts tests/unit/proxy-matcher.test.ts tests/unit/app/v1/url.test.ts(221 passed)bun run typecheck