fix(minimax): rebuild for credit-based Token Plan (dual-window percent schema)#536
fix(minimax): rebuild for credit-based Token Plan (dual-window percent schema)#536FrankieeW wants to merge 3 commits into
Conversation
The Token Plan moved from model-call-count tiers to credit/token-based plans (Plus/Max/Ultra). The remains API now returns current_interval_total_count as 0 and instead exposes current_interval_remaining_percent plus a second weekly window (current_weekly_*) per model_name bucket (general, video). - Query the officially documented token_plan/remains endpoint; keep coding_plan/remains as a legacy fallback (identical payload) - Drive every line from *_remaining_percent (used = 100 - remaining_percent) - Render both the 5h interval and weekly windows for each bucket - Map general -> Session/Weekly, video -> Video/Video (Weekly), title-case any other bucket; all lines are percent format - Show Session + Weekly on the overview (matching claude/codex); Video on detail - Plan name priority: explicit API field -> count->tier fallback -> MINIMAX_PLAN override -> generic 'Token Plan' baseline (never blank). The credit-based remains API exposes no tier, so MINIMAX_PLAN lets users pin Plus/Max/Ultra. - Drop the dead companion-resource classifiers (speech HD/Turbo, image-01) and quota-hint disambiguation; those buckets no longer exist in the response
Replace the model-call-count/companion-bucket fixtures with the live remains shape (general + video buckets, interval + weekly remaining percents). Cover the token_plan/remains endpoints with legacy fallback, percent-driven Session/Weekly/Video lines, count-based tier-inference fallback, the MINIMAX_PLAN override and generic baseline, reset/period derivation, and the existing endpoint/auth/error paths.
Describe the token_plan/remains endpoint (with legacy fallback), the 5h interval + weekly windows, the *_remaining_percent signals, general/video bucket-to-line mapping, the Session+Weekly overview layout, the plan-name priority chain with MINIMAX_PLAN override, and a Tier detection section explaining why the tier cannot be derived from the API.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughMiniMax plugin updated to support Token Plan remains API with dual-window (interval and weekly) progress reporting. Endpoints switched from coding_plan to token_plan with legacy fallbacks; payload parsing refactored to extract window data from per-model buckets; progress output changed to percent-based lines; configuration and documentation updated throughout; comprehensive test coverage validates endpoint retry logic, payload variations, and output formatting. ChangesToken Plan Integration
🎯 3 (Moderate) | ⏱️ ~25 minutes ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
Closing as a duplicate — this overlaps with existing open work:
both tracked by #533 (Update MiniMax API endpoint). Consolidating effort there to avoid duplicate review. Apologies for the noise. |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR updates the MiniMax provider to use the newer Token Plan usage API and to render usage as percent-based lines for both the 5-hour interval and weekly windows.
Changes:
- Switch usage endpoints to
token_plan/remainswith expanded fallback URL lists for GLOBAL and CN. - Parse
model_remains[]as windowed buckets (interval + weekly) and emit multiple percent-based progress lines (Session/Weekly/Video). - Update tests, plugin manifest line definitions, and provider documentation to match the new payload shape and display model.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| plugins/minimax/plugin.js | Reworks endpoint selection and payload parsing to percent-based Token Plan windows and adds plan-name normalization/override logic. |
| plugins/minimax/plugin.test.js | Updates/expands tests to cover Token Plan bucket parsing, multi-line output, fallbacks, and plan naming behavior. |
| plugins/minimax/plugin.json | Declares additional progress lines (Weekly/Video) to align with the new multi-window output. |
| docs/providers/minimax.md | Updates provider docs for Token Plan endpoints, window model, tier detection limitations, and new output mapping. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function computeWindowUsedPercent(item, win) { | ||
| const remainingPercent = readNumber(item[win.percentField]) | ||
| if (remainingPercent !== null) { | ||
| return clampPercent(Math.round(100 - remainingPercent)) | ||
| } | ||
|
|
||
| const total = readNumber(item[win.totalField]) | ||
| if (total === null || total <= 0) return null | ||
| const remaining = readNumber(item[win.usageField]) | ||
| if (remaining === null) return null | ||
| return clampPercent(Math.round(((total - remaining) / total) * 100)) | ||
| } | ||
|
|
||
| function computeWindowTiming(ctx, item, win, nowMs) { | ||
| const startMs = epochToMs(item[win.startField]) | ||
| const endMs = epochToMs(item[win.endField]) | ||
| const remainsRaw = readNumber(item[win.remainsField]) | ||
| const remainsMs = inferRemainsMs(remainsRaw, endMs, nowMs, win.expectedWindowMs) | ||
|
|
||
| let resetsAt = endMs !== null ? ctx.util.toIso(endMs) : null | ||
| if (!resetsAt && remainsMs !== null) resetsAt = ctx.util.toIso(nowMs + remainsMs) | ||
|
|
||
| let periodDurationMs = null | ||
| if (startMs !== null && endMs !== null && endMs > startMs) periodDurationMs = endMs - startMs | ||
|
|
||
| return { resetsAt, periodDurationMs } | ||
| } |
| const WINDOWS = [ | ||
| { | ||
| key: "interval", | ||
| percentField: "current_interval_remaining_percent", | ||
| totalField: "current_interval_total_count", | ||
| usageField: "current_interval_usage_count", | ||
| startField: "start_time", | ||
| endField: "end_time", | ||
| remainsField: "remains_time", | ||
| expectedWindowMs: INTERVAL_WINDOW_MS, | ||
| }, | ||
| { | ||
| key: "weekly", | ||
| percentField: "current_weekly_remaining_percent", | ||
| totalField: "current_weekly_total_count", | ||
| usageField: "current_weekly_usage_count", | ||
| startField: "weekly_start_time", | ||
| endField: "weekly_end_time", | ||
| remainsField: "weekly_remains_time", | ||
| expectedWindowMs: WEEKLY_WINDOW_MS, | ||
| }, | ||
| ] |
|
|
||
| - **Protocol:** HTTPS (JSON) | ||
| - **Endpoint:** `GET https://api.minimax.io/v1/api/openplatform/coding_plan/remains` | ||
| - **Endpoint:** `GET https://api.minimax.io/v1/token_plan/remains` — the officially documented Token Plan usage endpoint ([FAQ](https://platform.minimax.io/docs/token-plan/faq)). The older `coding_plan/remains` path returns an identical payload and is kept only as a legacy fallback. |
|
|
||
| The subscription tier (`Plus` / `Max` / `Ultra`) **cannot be derived from the API**, by design: | ||
|
|
||
| - The only usage endpoint is `token_plan/remains` (every subscription/plan/quota endpoint variant returns `404`). Its `coding_plan/remains` alias is byte-for-byte identical. |
| it("falls back to GLOBAL when MINIMAX_CN_API_KEY lookup throws in AUTO mode", async () => { | ||
| const ctx = makeCtx() | ||
| setEnv(ctx, { MINIMAX_API_KEY: "mini-key" }) | ||
| ctx.host.http.request.mockReturnValue({ | ||
| status: 200, | ||
| headers: {}, | ||
| bodyText: JSON.stringify({ | ||
| base_resp: { status_code: 0 }, | ||
| model_remains: [ | ||
| { | ||
| current_interval_total_count: 100, | ||
| current_interval_usage_count: 25, | ||
| start_time: 1700000000, | ||
| end_time: 1700018000, | ||
| }, | ||
| ], | ||
| }), | ||
| ctx.host.env.get.mockImplementation((name) => { | ||
| if (name === "MINIMAX_CN_API_KEY") throw new Error("cn env unavailable") | ||
| if (name === "MINIMAX_API_KEY") return "global-key" | ||
| return null | ||
| }) | ||
|
|
||
| const plugin = await loadPlugin() | ||
| const result = plugin.probe(ctx) | ||
| const line = result.lines[0] | ||
| expect(line.periodDurationMs).toBe(18000000) | ||
| expect(line.resetsAt).toBe(new Date(1700018000 * 1000).toISOString()) | ||
| }) | ||
|
|
||
| it("infers remains_time as milliseconds when value is plausible", async () => { | ||
| const ctx = makeCtx() | ||
| setEnv(ctx, { MINIMAX_API_KEY: "mini-key" }) | ||
| vi.spyOn(Date, "now").mockReturnValue(1700000000000) | ||
| ctx.host.http.request.mockReturnValue({ | ||
| status: 200, | ||
| headers: {}, | ||
| bodyText: JSON.stringify({ | ||
| base_resp: { status_code: 0 }, | ||
| model_remains: [ | ||
| { | ||
| current_interval_total_count: 100, | ||
| current_interval_usage_count: 40, | ||
| remains_time: 300000, | ||
| }, | ||
| ], | ||
| }), | ||
| bodyText: JSON.stringify(successPayload()), | ||
| }) | ||
|
|
||
| const plugin = await loadPlugin() | ||
| const result = plugin.probe(ctx) | ||
| expect(result.lines[0].resetsAt).toBe(new Date(1700000000000 + 300000).toISOString()) | ||
| }) | ||
|
|
||
| it("throws parse error when model_remains entries are unusable", async () => { | ||
| const ctx = makeCtx() | ||
| setEnv(ctx, { MINIMAX_API_KEY: "mini-key" }) | ||
| ctx.host.http.request.mockReturnValue({ | ||
| status: 200, | ||
| headers: {}, | ||
| bodyText: JSON.stringify({ | ||
| base_resp: { status_code: 0 }, | ||
| model_remains: [null, { current_interval_total_count: 0, current_interval_usage_count: 1 }], | ||
| }), | ||
| }) | ||
|
|
||
| const plugin = await loadPlugin() | ||
| expect(() => plugin.probe(ctx)).toThrow("Could not parse usage data") | ||
| }) | ||
|
|
||
| it("throws parse error when both used and remaining counts are missing", async () => { | ||
| const ctx = makeCtx() | ||
| setEnv(ctx, { MINIMAX_API_KEY: "mini-key" }) | ||
| ctx.host.http.request.mockReturnValue({ | ||
| status: 200, | ||
| headers: {}, | ||
| bodyText: JSON.stringify({ | ||
| base_resp: { status_code: 0 }, | ||
| model_remains: [{ current_interval_total_count: 100 }], | ||
| }), | ||
| }) | ||
|
|
||
| const plugin = await loadPlugin() | ||
| expect(() => plugin.probe(ctx)).toThrow("Could not parse usage data") | ||
| expect(ctx.host.http.request.mock.calls[0][0].url).toBe(PRIMARY_USAGE_URL) | ||
| expect(result.plan).toBe("Plus (GLOBAL)") | ||
| }) |
Why
MiniMax overhauled its Coding Plan into a credit/token-based Token Plan, which changed the
remainsAPI shape substantially. The current plugin is built on the old model (model-call-count tiers + per-capability companion buckets) and no longer reflects reality:*-High-Speed/Ultra-High-Speed).current_interval_total_count: 0and instead exposescurrent_interval_remaining_percent, plus a second weekly window (current_weekly_*), permodel_namebucket (general,video).image-01) and the count→tier quota tables no longer appear in the response.Verified against the live CN API (
api.minimaxi.com). Note: GLOBAL could not be tested (no global key available), but it shares the same endpoint/shape.What changed
plugins/minimax/plugin.jstoken_plan/remainsendpoint (per the Token Plan FAQ); keepcoding_plan/remainsas a legacy fallback (identical payload).*_remaining_percent(used = 100 − remaining_percent); render both the 5h interval and weekly windows per bucket.general → Session/Weekly,video → Video/Video (Weekly), title-case any other bucket. All lines are percent format.MINIMAX_PLANoverride → genericToken Planbaseline (never blank).plugins/minimax/plugin.json— overview linesSession+Weekly; detail linesVideo+Video (Weekly).plugins/minimax/plugin.test.js— rewritten for the live dual-window percent schema (general + video buckets), thetoken_plan/remainsendpoints with legacy fallback, theMINIMAX_PLANoverride/baseline, and the existing endpoint/auth/error paths.docs/providers/minimax.md— documents the new endpoint, dual windows, percent signals, bucket→line mapping, plan-name priority chain, and a Tier detection section explaining (with doc citations) why the tier cannot be derived from the API.Tier detection note
The credit-based Token Plan exposes no tier/plan field and reports
total_count: 0— every subscription/plan/quota endpoint variant returns 404. The console usage bar (*_remaining_percent) is the only signal MiniMax provides. Tier therefore must be pinned manually viaMINIMAX_PLAN=Plus|Max|Ultra; if MiniMax later adds a plan field, the priority chain consumes it automatically.Test plan
npx vitest run plugins/minimax/plugin.test.js— 34/34 passapi.minimaxi.com/v1/token_plan/remains: renders Session/Weekly/Video lines as percent;MINIMAX_PLAN=Plus→Plus (CN), unset →Token Plan (CN)Summary by cubic
Rebuilt the MiniMax plugin for the credit-based Token Plan using the
token_plan/remainsAPI and percent-based dual windows. Shows Session/Weekly on overview and Video on detail, with CN/Global fallbacks and a resilient plan-name strategy.New Features
token_plan/remains(official); keepcoding_plan/remainsas a legacy fallback.*_remaining_percent(used = 100 − remaining), limit = 100.general→ Session + Weekly (overview);video→ Video + Video (Weekly) (detail); title-case others.end_time/weekly_end_time, orremains_timewhen missing.MINIMAX_PLANoverride →Token Plan; append region suffix.Migration
MINIMAX_PLAN=Plus|Max|Ultraif you want your tier shown (the Token Plan API does not expose it).Written for commit f86a547. Summary will update on new commits.
Summary by CodeRabbit
New Features
Documentation