Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/providers/minimax.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Expected payload fields:
- `model_remains[].current_interval_total_count`
- `model_remains[].current_interval_usage_count`
- optional remaining aliases (`current_interval_remaining_count`, `current_interval_remains_count`)
- optional remaining percent fields (`current_interval_remaining_percent`)
- `model_remains[].start_time`
- `model_remains[].end_time`
- `model_remains[].remains_time`
Expand All @@ -64,6 +65,7 @@ Expected payload fields:
- Treat `current_interval_usage_count` as remaining prompts (MiniMax remains API behavior).
- If only remaining aliases are provided, compute `used = total - remaining`.
- If explicit used-count fields are provided, prefer them.
- If count totals are unavailable but a remaining percentage is provided, render a percent progress line.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- Plan name is taken from explicit plan/title fields when available.
- If plan fields are missing in GLOBAL mode, infer plan tier from known limits (`100/300/1000/2000` prompts or `1500/4500/15000/30000` model-call equivalents).
- If plan fields are missing in CN mode, infer only exact known CN limits (`600/1500/4500` model-call counts).
Expand Down
98 changes: 61 additions & 37 deletions plugins/minimax/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,13 @@

if (!modelRemains || modelRemains.length === 0) return null

const displayMultiplierForSelection = endpointSelection === "CN" ? 1 / MODEL_CALLS_PER_PROMPT : 1
let chosen = modelRemains[0]
for (let i = 0; i < modelRemains.length; i += 1) {
const item = modelRemains[i]
if (!item || typeof item !== "object") continue
const total = readNumber(item.current_interval_total_count ?? item.currentIntervalTotalCount)
if (total !== null && total > 0) {
if (total !== null && total > 0 && Math.round(total * displayMultiplierForSelection) > 0) {
chosen = item
break
}
Expand All @@ -258,7 +259,51 @@
if (!chosen || typeof chosen !== "object") return null

const total = readNumber(chosen.current_interval_total_count ?? chosen.currentIntervalTotalCount)
if (total === null || total <= 0) return null
const startMs = epochToMs(chosen.start_time ?? chosen.startTime)
const endMs = epochToMs(chosen.end_time ?? chosen.endTime)
const remainsRaw = readNumber(chosen.remains_time ?? chosen.remainsTime)
const nowMs = Date.now()
const remainsMs = inferRemainsMs(remainsRaw, endMs, nowMs)

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
}

const explicitPlanName = normalizePlanName(pickFirstString([
data.current_subscribe_title,
data.plan_name,
data.plan,
data.current_plan_title,
data.combo_title,
payload.current_subscribe_title,
payload.plan_name,
payload.plan,
]))
const inferredPlanName = inferPlanNameFromLimit(total, endpointSelection)
const planName = explicitPlanName || inferredPlanName

if (total === null || total <= 0) {
const remainingPercent = readNumber(
chosen.current_interval_remaining_percent ?? chosen.currentIntervalRemainingPercent
)
if (remainingPercent !== null && remainingPercent >= 0 && remainingPercent <= 100) {
return {
planName: planName || "Coding Plan",
used: 100 - remainingPercent,
total: 100,
resetsAt,
periodDurationMs,
formatKind: "percent",
}
}
return null
}

const usageFieldCount = readNumber(chosen.current_interval_usage_count ?? chosen.currentIntervalUsageCount)
const remainingCount = readNumber(
Expand Down Expand Up @@ -292,35 +337,6 @@
if (used < 0) used = 0
if (used > total) used = total

const startMs = epochToMs(chosen.start_time ?? chosen.startTime)
const endMs = epochToMs(chosen.end_time ?? chosen.endTime)
const remainsRaw = readNumber(chosen.remains_time ?? chosen.remainsTime)
const nowMs = Date.now()
const remainsMs = inferRemainsMs(remainsRaw, endMs, nowMs)

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
}

const explicitPlanName = normalizePlanName(pickFirstString([
data.current_subscribe_title,
data.plan_name,
data.plan,
data.current_plan_title,
data.combo_title,
payload.current_subscribe_title,
payload.plan_name,
payload.plan,
]))
const inferredPlanName = inferPlanNameFromLimit(total, endpointSelection)
const planName = explicitPlanName || inferredPlanName

return {
planName,
used,
Expand Down Expand Up @@ -367,12 +383,20 @@
const isCnEndpoint = successfulEndpoint === "CN"
const displayMultiplier = isCnEndpoint ? 1 / MODEL_CALLS_PER_PROMPT : 1

const line = {
label: "Session",
used: Math.round(parsed.used * displayMultiplier),
limit: Math.round(parsed.total * displayMultiplier),
format: { kind: "count", suffix: "prompts" },
}
const line =
parsed.formatKind === "percent"
? {
label: "Session",
used: Math.round(parsed.used),
limit: 100,
format: { kind: "percent" },
}
: {
label: "Session",
used: Math.round(parsed.used * displayMultiplier),
limit: Math.round(parsed.total * displayMultiplier),
format: { kind: "count", suffix: "prompts" },
}
if (parsed.resetsAt) line.resetsAt = parsed.resetsAt
if (parsed.periodDurationMs !== null) line.periodDurationMs = parsed.periodDurationMs

Expand Down
36 changes: 36 additions & 0 deletions plugins/minimax/plugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,42 @@ describe("minimax plugin", () => {
expect(() => plugin.probe(ctx)).toThrow("Could not parse usage data")
})

it("falls back to CN remaining percent when count totals are unavailable", async () => {
const ctx = makeCtx()
setEnv(ctx, { MINIMAX_CN_API_KEY: "mini-cn-key" })
ctx.host.http.request.mockReturnValue({
status: 200,
headers: {},
bodyText: JSON.stringify({
base_resp: { status_code: 0 },
model_remains: [
{
model_name: "general",
current_interval_total_count: 0,
current_interval_usage_count: 0,
current_interval_remaining_percent: 94,
start_time: 1780279200000,
end_time: 1780297200000,
},
{
model_name: "video",
current_interval_total_count: 3,
current_interval_usage_count: 3,
current_interval_remaining_percent: 100,
},
],
}),
})

const plugin = await loadPlugin()
const result = plugin.probe(ctx)

expect(result.plan).toBe("Coding Plan (CN)")
expect(result.lines[0].used).toBe(6)
expect(result.lines[0].limit).toBe(100)
expect(result.lines[0].format).toEqual({ kind: "percent" })
})

it("throws parse error when both used and remaining counts are missing", async () => {
const ctx = makeCtx()
setEnv(ctx, { MINIMAX_API_KEY: "mini-key" })
Expand Down
Loading