feat(minimax): migrate OAuth to account/oauth2 endpoints + add refresh#82056
feat(minimax): migrate OAuth to account/oauth2 endpoints + add refresh#82056kapelame wants to merge 1 commit into
Conversation
|
Codex review: needs real behavior proof before merge. Summary Reproducibility: yes. source-level: current main stores MiniMax OAuth refresh tokens and promises auto-refresh in provider notes, but minimax-portal has no refreshOAuth hook. I did not run a live token-expiry refresh repro in this read-only review. Real behavior proof Next step before merge Security Review findings
Review detailsBest possible solution: Land the provider-owned MiniMax account/oauth2 refresh implementation after preserving server refresh failure reasons, updating MiniMax OAuth docs, and adding real refresh-path proof. Do we have a high-confidence way to reproduce the issue? Yes, source-level: current main stores MiniMax OAuth refresh tokens and promises auto-refresh in provider notes, but minimax-portal has no refreshOAuth hook. I did not run a live token-expiry refresh repro in this read-only review. Is this the best way to solve the issue? No, not yet: provider-owned refreshOAuth is the right seam and the MiniMax endpoint migration matches upstream CLI behavior, but the patch needs to preserve refresh error reasons, update docs, and show the real refresh path before merge. Full review comments:
Overall correctness: patch is incorrect Acceptance criteria:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 930852af2959. |
688d951 to
afcb4c3
Compare
|
Force-pushed
const [url] = fetchMock.mock.calls[0] as [string, RequestInit];
expect(url).toBe("https://account.minimax.io/oauth2/token");Local re-verify: The remaining |
afcb4c3 to
62a11f5
Compare
|
Force-pushed
Local re-verify on |
|
Status update on the latest CI run (after ✅ All OAuth-related checks now pass (the changes that needed fixing from the previous round):
❌ The only remaining failure is unrelated to this PR: That file was last modified by It's the same situation as the Telegram/grammy duplicate-package mismatch I noted in the original PR description: pre-existing main-state breakage that this PR neither caused nor scopes to fix. Happy to rebase once |
Migrates the MiniMax portal OAuth flow from the legacy
api.{minimax.io,minimaxi.com}/oauth/* surface to the canonical
account.{minimax.io,minimaxi.com}/oauth2/* surface, and finally
implements the refresh path that provider-registration.ts:176
already promised users ("MiniMax OAuth tokens auto-refresh").
The legacy endpoints still respond, but `account/oauth2/*` is what
MiniMax platform team treats as canonical and what the existing
mmx-cli + the in-flight Hermes / opencode integrations target.
OpenClaw's `78257093-7e40-4613-99e0-527b14b39113` client_id is
verified registered on the new backend for both global + CN
(POST account.{minimax.io,minimaxi.com}/oauth2/device/code with
this client_id returns HTTP 200 + valid user_code).
Endpoint migration (extensions/minimax/oauth.ts):
| | Before | After |
|---|---|---|
| Global base | `https://api.minimax.io` | `https://account.minimax.io` |
| CN base | `https://api.minimaxi.com` | `https://account.minimaxi.com` |
| Device code | `{base}/oauth/code` | `{base}/oauth2/device/code` |
| Token | `{base}/oauth/token` | `{base}/oauth2/token` |
| Grant type | `…:grant-type:user_code` | `…:grant-type:device_code` (RFC 8628) |
| Scope | `group_id profile model.completion` | `openid profile coding_plan` |
| Body extra | `response_type: "code"` | (removed — new endpoint doesn't expect it) |
New refresh path:
- `oauth.ts` exports `refreshMiniMaxPortalOAuth({refreshToken, region})`
→ MiniMaxOAuthToken. Throws clearly when the server signals the
refresh_token is no longer valid (90-day window elapsed, token
rotated, grant revoked) — caller should treat as re-login required.
- `oauth.runtime.ts` re-exports it for the existing lazy-import boundary.
- `provider-registration.ts`:
- Login flow now persists `enterpriseUrl: baseUrl` on the
OAuthCredential via `credentialExtra`, so the refresh callback
can derive which region's OAuth backend to hit (the refresh
contract only receives the credential).
- PORTAL provider's `registerProvider({...})` block adds
`refreshOAuth: async (cred) => await refreshMiniMaxPortalOAuthCredential(cred)`,
matching the established pattern from
`extensions/openai/openai-codex-provider.ts:582`. The wrapper
derives region from cred.enterpriseUrl (`*.minimaxi.com` ⇒ CN,
else global), calls the refresh, returns the rotated credential.
Tests:
- New `extensions/minimax/oauth.test.ts` (6 tests) covers:
- global / CN endpoint URLs and grant_type=refresh_token body
- server omitting refresh_token → fall back to previous one
- non-2xx → throws (re-login signal)
- status≠"success" with HTTP 200 → throws (refresh_token_reused etc.)
- missing access_token in success payload → throws
- `extensions/minimax/index.test.ts` mock for `./oauth.runtime.js` now
also stubs `refreshMiniMaxPortalOAuth` so tests that touch the new
re-exported symbol don't see undefined.
62a11f5 to
b51d772
Compare
|
Force-pushed
Net: oauth.ts goes from +97/-8 to +53/-8. Whole-PR diff drops from +269/-9 to +225/-9. The remaining +45 lines vs Tests still 115/115 (two test assertions adjusted to match the unified error message).
|
Summary
Migrates the MiniMax portal OAuth flow from the legacy
api.{minimax.io,minimaxi.com}/oauth/*surface to the canonicalaccount.{minimax.io,minimaxi.com}/oauth2/*surface, and finally implements the refresh path thatextensions/minimax/provider-registration.ts:176already promised users ("MiniMax OAuth tokens auto-refresh. Re-run login if refresh fails or access is revoked." — that note was aspirational; before this PR there was no refresh implementation, so a 2-day access_token expiry forced re-login).The legacy endpoints still respond, but
account/oauth2/*is what the MiniMax platform team treats as canonical and what the existingmmx-cliplus the in-flight Hermes (NousResearch/hermes-agent#25581) and opencode (anomalyco/opencode#27460) integrations target.OpenClaw's existing
78257093-7e40-4613-99e0-527b14b39113client_id is verified registered on the new backend for both global + CN, so no re-registration needed.Endpoint migration (
extensions/minimax/oauth.ts)https://api.minimax.iohttps://account.minimax.iohttps://api.minimaxi.comhttps://account.minimaxi.com{base}/oauth/code{base}/oauth2/device/code{base}/oauth/token{base}/oauth2/token…:grant-type:user_code…:grant-type:device_code(RFC 8628)group_id profile model.completionopenid profile coding_planresponse_type: "code"(sent in body)New refresh path
oauth.tsexportsrefreshMiniMaxPortalOAuth({ refreshToken, region })→MiniMaxOAuthToken. Throws clearly when the server signals therefresh_tokenis no longer valid (90-day window elapsed, token already rotated, grant revoked) — caller treats as re-login required.oauth.runtime.tsre-exports it for the existing lazy-import boundary.provider-registration.ts:enterpriseUrl: baseUrlon theOAuthCredentialviacredentialExtra, so the refresh callback can derive which region's OAuth backend to hit (therefreshOAuthcontract only receives the credential).registerProvider({...})block addsrefreshOAuth: async (cred) => await refreshMiniMaxPortalOAuthCredential(cred), matching the established pattern fromextensions/openai/openai-codex-provider.ts:582. The wrapper derives region fromcred.enterpriseUrl(*.minimaxi.com⇒ CN, else global), calls the refresh, returns the rotated credential.Tests
extensions/minimax/oauth.test.ts(6 tests) covers:grant_type=refresh_tokenbodyrefresh_token→ fall back to previous onestatus≠"success"with HTTP 200 → throws (refresh_token_reusedetc.)access_tokenin success payload → throwsextensions/minimax/index.test.tsmock for./oauth.runtime.jsnow also stubsrefreshMiniMaxPortalOAuthso tests that touch the new re-exported symbol don't see undefined.scripts/check-no-raw-channel-fetch.mjsallowlist updated to reflect new line numbers inoauth.tsafter this patch (existingoauth.ts:107→:106after one removed body line, plusoauth.ts:254for the new refresh fetch).Real behavior proof
Behavior addressed: MiniMax portal OAuth login + token refresh in
extensions/minimax/. Before this PR, login worked against the legacyapi.*/oauth/*endpoints but refresh was never implemented, so users hit re-login every 2 days when the access_token expired.Real environment tested: macOS local checkout on
feat/minimax-oauth2-migrate-and-refresh. Build viapnpm openclaw(run-node.mjspath) against this branch's compiled output. Live OAuth round-trip against the production MiniMax OAuth backend with a real Token Plan account.Exact steps or command run after this patch:
Then in the interactive flow: select
MiniMax OAuth (Global)at the auth-method picker, browser opens the verification URL, approve in browser.Evidence after fix: verbatim terminal output from the run above (build chatter trimmed for readability).
Observed result after fix:
MiniMax OAuth (Global)→ branch'screateMinimaxOAuthMethod("global")registered correctly.https://platform.minimax.io/oauth-authorize?user_code=TUE3-FXNG&client=OpenClaw— the&client=OpenClawquery param confirms MiniMax'saccount.*/oauth2/*backend recognizes OpenClaw's existing78257093-...client_id by name (no re-registration needed).MiniMax OAuth complete.Current Plan: Max – High-Speed,Valid until: 10/10/2026) → access_token + refresh_token successfully exchanged.Auth profile: minimax-portal:default (minimax-portal/oauth)written to~/.openclaw/openclaw.json→ credentials persisted.Default model available: minimax-portal/MiniMax-M2.7→ portal catalog resolution works under the new endpoint.Local supplemental proof (additional to the runtime evidence above):
pnpm test extensions/minimax→ 11 test files / 115 tests all pass;pnpm tsgo:extensions:testproduces zerominimax-scoped errors.What was not tested:
refresh_tokenexpiry path was not exercised live (would require sitting on a token for ~3 months and waiting for the server to revoke it). Behavior is covered byoauth.test.tscases that exercise the server returninginvalid_grant/refresh_token_reused/status="error"and assertrefreshMiniMaxPortalOAuththrows.refreshOAuthcallback invocation byOAuthManagerwas not exercised end-to-end in this run (would require the access_token to age pastexpireswhile the OpenClaw process is running). Wired exactly per the established pattern inextensions/openai/openai-codex-provider.ts:582.pnpm check:changedtypecheck failure inextensions/telegram/(grammy / @grammyjs/types duplicate-package mismatch) is unrelated to this PR — it reproduces onorigin/mainwithout these changes. Did not address it here.Notes
This PR makes good on the existing user-facing note
"MiniMax OAuth tokens auto-refresh. Re-run login if refresh fails or access is revoked."(provider-registration.ts:176) — that text was previously aspirational. After this PR, refresh actually runs via the OpenClawOAuthManager→refreshOAuthplugin hook →refreshMiniMaxPortalOAuthCredential, with proper failure semantics (90-day refresh_token expiry surfaces as a clean "re-login required" path instead of silent 401s).