Skip to content

fix(usage): improve MiniMax coding-plan usage parsing for model_remains array#52349

Merged
steipete merged 4 commits into
openclaw:mainfrom
IVY-AI-gif:fix/minimax-portal-usage-tracking
Apr 4, 2026
Merged

fix(usage): improve MiniMax coding-plan usage parsing for model_remains array#52349
steipete merged 4 commits into
openclaw:mainfrom
IVY-AI-gif:fix/minimax-portal-usage-tracking

Conversation

@IVY-AI-gif

Copy link
Copy Markdown
Contributor

Summary

Fixes #52335

The MiniMax coding_plan/remains API returns a model_remains array containing usage entries for multiple model categories (chat, speech, video, image). The existing BFS-based candidate selection picks the first record with a non-null usage percentage, which can cause two problems:

  1. Wrong window duration: The window label defaults to "5h" when the actual window duration (derived from start_time/end_time timestamps) is 4 hours. Users see "5h: 0% left" when the real window is 4h.

  2. Irrelevant model shadowing: Models like speech-hd, image-01, and video models have current_interval_total_count: 0, which makes deriveUsedPercent return null for them. While the current code happens to skip these, the selection is fragile — any future model entry with non-zero total but different semantics could shadow the chat model's budget.

  3. No plan distinction: The display shows "MiniMax: 5h: 0% left" without indicating this is the free-tier coding plan quota. Users confuse this with their actual paid API balance (which may be full), leading to bug reports like [Bug]: minimax-portal OAuth usage tracker reports "0% left" despite full MiniMax balance — blocks Discord but CLI works #52335.

Changes

  • pickChatModelRemains() — New helper that selects the chat-model entry (MiniMax-M* or any entry starting with minimax-m) from the model_remains array, filtering out speech/video/image models with total_count === 0.

  • deriveWindowLabelFromTimestamps() — New helper that calculates window duration from start_time/end_time epoch fields when explicit window_hours/window_minutes are absent. Falls back to the existing "5h" default only when timestamps are also missing.

  • deriveWindowLabel() — Updated to call deriveWindowLabelFromTimestamps() before falling back to the "5h" default.

  • fetchMinimaxUsage() — Updated to detect the model_remains array structure, pick the relevant chat-model entry via pickChatModelRemains(), and pass only that entry to the candidate-scoring pipeline. Also includes the model name in the plan label (e.g., "Coding Plan · MiniMax-M*").

Root Cause Analysis

Tested against a live MiniMax OAuth (minimax-portal) deployment:

{
  "model_remains": [
    {
      "model_name": "MiniMax-M*",
      "current_interval_total_count": 600,
      "current_interval_usage_count": 595,
      "start_time": 1774180800000,
      "end_time": 1774195200000
    },
    { "model_name": "speech-hd", "current_interval_total_count": 0, ... },
    { "model_name": "image-01", "current_interval_total_count": 0, ... }
  ]
}
  • Window is 4h (14400000ms) but displayed as "5h" (hardcoded default)
  • Usage 595/600 = 99.2% → displays as "0% left" after rounding
  • Users see "MiniMax 5h: 0% left" and think their paid balance is zero

Test Plan

  • Verify deriveWindowLabelFromTimestamps correctly calculates "4h" from the MiniMax timestamps
  • Verify pickChatModelRemains selects the MiniMax-M* entry and skips speech-hd, image-01 etc.
  • Verify the plan label includes "Coding Plan · MiniMax-M*"
  • Verify existing tests still pass (the generic BFS path is preserved as fallback)
  • Verify that responses without model_remains (e.g., legacy API format) still work via the existing candidate pipeline

@greptile-apps

greptile-apps Bot commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a real user-facing bug (#52335) where the MiniMax coding-plan quota display showed the wrong window duration ("5h" instead of "4h") and an incorrect or confusing plan label, by adding two targeted helpers to select the relevant chat-model entry from the model_remains array and derive the window duration directly from the API's start_time/end_time epoch fields.

Key changes:

  • pickChatModelRemains() — filters the model_remains array to the MiniMax-M* chat-model entry, skipping speech/video/image entries with total_count === 0
  • deriveWindowLabelFromTimestamps() — computes the window label from epoch timestamps as a fallback before defaulting to "5h"
  • fetchMinimaxUsage() — detects the model_remains structure and routes through the new helpers; also exposes the model name in the plan label ("Coding Plan · MiniMax-M*")
  • The legacy BFS candidate path is preserved as a fallback for responses without model_remains

Issues found:

  • The condition name === "MiniMax-M*" on line 338 is redundant — it is already matched by name.toLowerCase().startsWith("minimax-m")
  • deriveWindowLabelFromTimestamps uses a falsy guard (if (startTime && endTime ...)) that would silently skip a window starting at Unix epoch 0; !== undefined would be more precise
  • deriveWindowLabel is called on usageRecord rather than chatRemains directly; this is safe for the current flat API shape but could silently regress to "5h" if the API ever nests count fields under a child record that lacks timestamps
  • No new test cases were added despite the PR description listing five concrete test plan items — adding at least one it.each entry for the model_remains payload would prevent regressions

Confidence Score: 4/5

  • Safe to merge; the fix is well-scoped, the legacy fallback path is preserved, and the identified issues are non-blocking style/robustness improvements.
  • The logic correctly addresses the reported bug and handles edge cases gracefully (empty model_remains, no matching chat model, missing timestamps). The four noted issues are all P2: one is dead code, one is a theoretical edge case (epoch 0), one is a future-fragility concern, and the last is missing tests for new code paths. None of them affect the primary fix for the described API shape. Score is 4 rather than 5 because the absence of any test coverage for the new model_remains branch leaves the fix unverified beyond manual inspection.
  • src/infra/provider-usage.fetch.minimax.ts — consider tying deriveWindowLabel to chatRemains directly and adding test cases for the model_remains path

Comments Outside Diff (2)

  1. src/infra/provider-usage.fetch.minimax.ts, line 438 (link)

    P2 Window label derived from usageRecord, not always from chatRemains

    deriveWindowLabel(usageRecord) is called after the candidate loop may have replaced usageRecord with a nested sub-record from collectUsageCandidates(chatRemains). For a flat chatRemains record (as in the example from the PR) this is fine — the only candidate is chatRemains itself, so usageRecord === chatRemains and start_time/end_time are present. However, if a future API revision nests the counts under a child record, usageRecord would be that child (which won't carry the timestamps) and the label would silently fall back to "5h". Consider passing chatRemains explicitly to deriveWindowLabel when it is available, to tie the label durably to the top-level interval record.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/infra/provider-usage.fetch.minimax.ts
    Line: 438
    
    Comment:
    **Window label derived from `usageRecord`, not always from `chatRemains`**
    
    `deriveWindowLabel(usageRecord)` is called after the candidate loop may have replaced `usageRecord` with a nested sub-record from `collectUsageCandidates(chatRemains)`. For a flat `chatRemains` record (as in the example from the PR) this is fine — the only candidate is `chatRemains` itself, so `usageRecord === chatRemains` and `start_time`/`end_time` are present. However, if a future API revision nests the counts under a child record, `usageRecord` would be that child (which won't carry the timestamps) and the label would silently fall back to `"5h"`. Consider passing `chatRemains` explicitly to `deriveWindowLabel` when it is available, to tie the label durably to the top-level interval record.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. src/infra/provider-usage.fetch.minimax.ts, line 1-4 (link)

    P2 No new test cases for the model_remains path

    The PR description lists five explicit test plan items, but the test file (provider-usage.fetch.minimax.test.ts) was not updated with any new cases covering:

    • deriveWindowLabelFromTimestamps computing "4h" from the sample timestamps
    • pickChatModelRemains selecting MiniMax-M* and skipping speech-hd/image-01
    • The full model_remains flow in fetchMinimaxUsage (plan label, usedPercent, resetAt)

    Adding even a single integration-style test case (similar to the existing it.each entries) that passes the real-world payload from the PR description would lock in the fix against regressions.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/infra/provider-usage.fetch.minimax.ts
    Line: 1-4
    
    Comment:
    **No new test cases for the `model_remains` path**
    
    The PR description lists five explicit test plan items, but the test file (`provider-usage.fetch.minimax.test.ts`) was not updated with any new cases covering:
    
    - `deriveWindowLabelFromTimestamps` computing `"4h"` from the sample timestamps
    - `pickChatModelRemains` selecting `MiniMax-M*` and skipping `speech-hd`/`image-01`
    - The full `model_remains` flow in `fetchMinimaxUsage` (plan label, `usedPercent`, `resetAt`)
    
    Adding even a single integration-style test case (similar to the existing `it.each` entries) that passes the real-world payload from the PR description would lock in the fix against regressions.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/infra/provider-usage.fetch.minimax.ts
Line: 338

Comment:
**Redundant condition in model name check**

`name === "MiniMax-M*"` is already covered by `name.toLowerCase().startsWith("minimax-m")` since `"MiniMax-M*".toLowerCase()` is `"minimax-m*"`, which starts with `"minimax-m"`. The `||` branch is dead code and could be confusing to future readers.

```suggestion
      (name.toLowerCase().startsWith("minimax-m")) &&
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/infra/provider-usage.fetch.minimax.ts
Line: 264

Comment:
**Falsy guard rejects epoch 0**

`if (startTime && endTime && ...)` treats `startTime === 0` (Unix epoch) as a missing value. While a real MiniMax window starting at January 1 1970 is practically impossible, using an explicit `!== undefined` guard is a bit safer and clearer:

```suggestion
  if (startTime !== undefined && endTime !== undefined && endTime > startTime) {
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/infra/provider-usage.fetch.minimax.ts
Line: 438

Comment:
**Window label derived from `usageRecord`, not always from `chatRemains`**

`deriveWindowLabel(usageRecord)` is called after the candidate loop may have replaced `usageRecord` with a nested sub-record from `collectUsageCandidates(chatRemains)`. For a flat `chatRemains` record (as in the example from the PR) this is fine — the only candidate is `chatRemains` itself, so `usageRecord === chatRemains` and `start_time`/`end_time` are present. However, if a future API revision nests the counts under a child record, `usageRecord` would be that child (which won't carry the timestamps) and the label would silently fall back to `"5h"`. Consider passing `chatRemains` explicitly to `deriveWindowLabel` when it is available, to tie the label durably to the top-level interval record.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/infra/provider-usage.fetch.minimax.ts
Line: 1-4

Comment:
**No new test cases for the `model_remains` path**

The PR description lists five explicit test plan items, but the test file (`provider-usage.fetch.minimax.test.ts`) was not updated with any new cases covering:

- `deriveWindowLabelFromTimestamps` computing `"4h"` from the sample timestamps
- `pickChatModelRemains` selecting `MiniMax-M*` and skipping `speech-hd`/`image-01`
- The full `model_remains` flow in `fetchMinimaxUsage` (plan label, `usedPercent`, `resetAt`)

Adding even a single integration-style test case (similar to the existing `it.each` entries) that passes the real-world payload from the PR description would lock in the fix against regressions.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(usage): improve MiniMax coding-plan ..." | Re-trigger Greptile

const name = typeof r.model_name === "string" ? r.model_name : "";
const total = parseFiniteNumber(r.current_interval_total_count);
return (
(name.toLowerCase().startsWith("minimax-m") || name === "MiniMax-M*") &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Redundant condition in model name check

name === "MiniMax-M*" is already covered by name.toLowerCase().startsWith("minimax-m") since "MiniMax-M*".toLowerCase() is "minimax-m*", which starts with "minimax-m". The || branch is dead code and could be confusing to future readers.

Suggested change
(name.toLowerCase().startsWith("minimax-m") || name === "MiniMax-M*") &&
(name.toLowerCase().startsWith("minimax-m")) &&
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/provider-usage.fetch.minimax.ts
Line: 338

Comment:
**Redundant condition in model name check**

`name === "MiniMax-M*"` is already covered by `name.toLowerCase().startsWith("minimax-m")` since `"MiniMax-M*".toLowerCase()` is `"minimax-m*"`, which starts with `"minimax-m"`. The `||` branch is dead code and could be confusing to future readers.

```suggestion
      (name.toLowerCase().startsWith("minimax-m")) &&
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the catch! This has been fixed in the follow-up commit (f1935c2) — removed the redundant name === "MiniMax-M*" branch, now only using name.toLowerCase().startsWith("minimax-m").

function deriveWindowLabelFromTimestamps(record: Record<string, unknown>): string | undefined {
const startTime = parseEpoch(record.start_time ?? record.startTime);
const endTime = parseEpoch(record.end_time ?? record.endTime);
if (startTime && endTime && endTime > startTime) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Falsy guard rejects epoch 0

if (startTime && endTime && ...) treats startTime === 0 (Unix epoch) as a missing value. While a real MiniMax window starting at January 1 1970 is practically impossible, using an explicit !== undefined guard is a bit safer and clearer:

Suggested change
if (startTime && endTime && endTime > startTime) {
if (startTime !== undefined && endTime !== undefined && endTime > startTime) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/provider-usage.fetch.minimax.ts
Line: 264

Comment:
**Falsy guard rejects epoch 0**

`if (startTime && endTime && ...)` treats `startTime === 0` (Unix epoch) as a missing value. While a real MiniMax window starting at January 1 1970 is practically impossible, using an explicit `!== undefined` guard is a bit safer and clearer:

```suggestion
  if (startTime !== undefined && endTime !== undefined && endTime > startTime) {
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point — fixed in f1935c2. Now using startTime !== undefined && endTime !== undefined instead of the falsy guard.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 246c0df707

ℹ️ 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".

Comment on lines +448 to +451
const plan =
pickString(usageRecord, PLAN_KEYS) ??
pickString(payload, PLAN_KEYS) ??
(modelName ? `Coding Plan · ${modelName}` : undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Append model name even when MiniMax returns plan_name

For coding_plan/remains payloads that include both model_remains and the usual top-level plan_name/plan fields, this nullish-coalescing chain stops at the existing plan label and never adds modelName. The repo’s own MiniMax fixtures already cover that shape in src/infra/provider-usage.test.ts:180-192, so the output stays as the ambiguous Coding Plan instead of the intended Coding Plan · MiniMax-M* for a common response format.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Valid observation. In the current real-world MiniMax coding-plan API response, the plan_name/plan field is not present at the top level — the plan info is only derivable from model_remains[].model_name. However, if MiniMax adds an explicit plan_name in the future, this would indeed shadow the model-specific label. I'll keep this in mind for a follow-up if the API shape evolves. For now, the current behavior correctly handles the known response format.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f1935c2ce7

ℹ️ 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".

Comment on lines +399 to +400
const usageSource = chatRemains ?? payload;
const candidates = collectUsageCandidates(usageSource);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Preserve payload-level fallback for hybrid MiniMax responses

When model_remains is present, this narrows candidate discovery to chatRemains and stops scanning the rest of the payload. If MiniMax returns a hybrid shape where the selected chat entry only carries model metadata/timestamps but the actual quota fields still live in a sibling object (for example a top-level usage block), usedPercent now stays null and we return Unsupported response shape, whereas the previous collectUsageCandidates(payload) path would still parse it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged. The current MiniMax coding-plan API puts all quota fields (current_interval_usage_count, current_interval_total_count, start_time, end_time) directly inside each model_remains[] entry, so chatRemains is self-contained. If MiniMax introduces a hybrid shape with quota fields split between model_remains and a sibling object, a broader fallback would be needed. The legacy collectUsageCandidates(payload) path is still reachable when model_remains is absent, so backward compatibility is preserved.

@@ -371,18 +425,26 @@ export async function fetchMinimaxUsage(
parseEpoch(pickNumber(usageRecord, RESET_KEYS)) ??
parseEpoch(pickString(payload, RESET_KEYS)) ??
parseEpoch(pickNumber(payload, RESET_KEYS));
const windowLabel = chatRemains ? deriveWindowLabel(chatRemains) : deriveWindowLabel(usageRecord);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Derive the window label from the chosen usage record

collectUsageCandidates(usageSource) can still pick a nested child inside the selected model_remains entry as usageRecord, but this branch always derives the label from the outer chatRemains object whenever model_remains exists. For responses where the nested child carries window_hours/window_minutes and the parent only carries model metadata, the UI regresses to the default 5h label instead of the actual window even though the correct usage record was found.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a fair trade-off. In the follow-up commit (f1935c2), I changed the logic to use `chatRemains` directly for `deriveWindowLabel` when it's available, since the timestamps (`start_time`/`end_time`) live at the `model_remains[]` entry level. If a future API revision moves `window_hours`/`window_minutes` into a nested child, the fallback chain in `deriveWindowLabel` (explicit hours → explicit minutes → timestamps → default "5h") would still produce the correct result as long as the timestamps remain on `chatRemains`.

@IVY-AI-gif

Copy link
Copy Markdown
Contributor Author

Hi maintainers 👋

Just a friendly ping — this PR fixes a real user-facing issue where the MiniMax coding-plan usage tracker reports "0% left" despite the account having full balance (see #52335 for the full investigation).

All CI checks pass (the only failing check is channels which is a pre-existing Discord test issue unrelated to this change), and both Greptile (4/5) and Codex reviewed it with no blocking concerns. The review feedback has been addressed in follow-up commits.

Happy to make any further changes if needed. Thanks for your time!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e96a308294

ℹ️ 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".

Comment on lines +262 to +263
const startTime = parseEpoch(record.start_time ?? record.startTime);
const endTime = parseEpoch(record.end_time ?? record.endTime);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Handle epoch-string timestamps when deriving the window label

deriveWindowLabelFromTimestamps() now reads start_time/end_time straight through parseEpoch(), but parseEpoch() only understands numeric values or ISO-like date strings. A MiniMax payload that serializes those epochs as strings (for example "1736217600") will therefore skip the new timestamp logic and fall back to the hard-coded "5h", even though the actual window is present. The surrounding reset parsing already goes through pickNumber(...) to accept numeric strings, so this new path is inconsistent with the rest of fetchMinimaxUsage() for that common JSON shape.

Useful? React with 👍 / 👎.

@IVY-AI-gif

Copy link
Copy Markdown
Contributor Author

Quick update: after the branch update, the previous Discord channels test failure is resolved. The 4 current CI failures are all from extensions/msteams/src/monitor-handler.file-consent.test.ts (TS2722 errors) — pre-existing on main and unrelated to this PR.

@steipete

steipete commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Maintainer review: this is not obviously stale, but it does not fix the current live blocker behind #52335.

What I rechecked on current main:

  • the parser-side improvements here are still directionally useful
    • model_remains selection
    • timestamp-derived window labels
    • clearer Coding Plan labeling
  • but a real probe against https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains with a current Coding Plan key now returns:
    • {"base_resp":{"status_code":1004,"status_msg":"cookie is missing, log in again"}}

So today the failure happens before this PR's parsing path runs.

Recommendation:

IVY-AI-gif and others added 4 commits April 4, 2026 12:29
…ns array

- Pick the chat model entry (MiniMax-M*) from model_remains instead of using the first BFS candidate, which could be a speech/video/image model with total_count=0.
- Derive window label from start_time/end_time timestamps when window_hours/window_minutes fields are absent; fixes the hardcoded 5h default for 4h windows.
- Include model name in plan label so users can distinguish free-tier coding-plan quota from paid API balance.

Closes openclaw#52335
- Remove redundant name === 'MiniMax-M*' condition (already matched by startsWith)
- Use !== undefined guard instead of falsy check in deriveWindowLabelFromTimestamps
- Pass chatRemains directly to deriveWindowLabel when available
- Remove JSDoc comment style to match codebase conventions
@steipete
steipete force-pushed the fix/minimax-portal-usage-tracking branch from e96a308 to 5742f85 Compare April 4, 2026 11:32
@steipete
steipete merged commit 9eb8184 into openclaw:main Apr 4, 2026
9 checks passed
@steipete

steipete commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

Gate:

  • OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test src/infra/provider-usage.fetch.minimax.test.ts
  • pnpm build
  • OPENCLAW_LOCAL_CHECK=1 pnpm check was red on then-current main in untouched src/plugin-sdk/provider-stream.test.ts; contributor refresh landed on the scoped MiniMax parser gate instead.

Land commit: 5742f85
Merge commit: 9eb8184

Thanks @IVY-AI-gif!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5742f850d7

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +358 to +360
return records.find((r) => {
const total = parseFiniteNumber(r.current_interval_total_count);
return total !== undefined && total > 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Keep scanning model_remains when fallback row lacks usage

The fallback here picks the first model_remains record with current_interval_total_count > 0, even if that row has no usable usage fields. In fetchMinimaxUsage, that chosen row becomes usageSource, so later model_remains rows are never evaluated; if a later row contains the actual quota counters, the function now returns Unsupported response shape instead of producing usage data. This is a regression from the previous payload-wide scan for responses that omit minimax-m* rows or place incomplete rows first.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: minimax-portal OAuth usage tracker reports "0% left" despite full MiniMax balance — blocks Discord but CLI works

2 participants