diff --git a/docs/providers/minimax.md b/docs/providers/minimax.md index 79b07a7b3..52eadf7d0 100644 --- a/docs/providers/minimax.md +++ b/docs/providers/minimax.md @@ -35,7 +35,24 @@ MiniMax highlights these improvements in M2.1: ## Choose a setup -### MiniMax M2.1 — recommended +### MiniMax OAuth (Coding Plan) — recommended + +**Best for:** quick setup with MiniMax Coding Plan via OAuth, no API key required. + +Enable the bundled OAuth plugin and authenticate: + +```bash +moltbot plugins enable minimax-portal-auth +moltbot gateway restart +moltbot onboard --auth-choice minimax-portal +``` +You will be prompted to select an endpoint: +- **Global** - International users (`api.minimax.io`) +- **CN** - Users in China (`api.minimaxi.com`) + +See [MiniMax OAuth plugin README](https://github.com/moltbot/moltbot/tree/main/extensions/minimax-portal-auth) for details. + +### MiniMax M2.1 (API key) **Best for:** hosted MiniMax with Anthropic-compatible API. @@ -143,6 +160,7 @@ Use the interactive config wizard to set MiniMax without editing JSON: 3) Choose **MiniMax M2.1**. 4) Pick your default model when prompted. + ## Configuration options - `models.providers.minimax.baseUrl`: prefer `https://api.minimax.io/anthropic` (Anthropic-compatible); `https://api.minimax.io/v1` is optional for OpenAI-compatible payloads. diff --git a/extensions/minimax-portal-auth/README.md b/extensions/minimax-portal-auth/README.md index 317c1bc53..d1866ec41 100644 --- a/extensions/minimax-portal-auth/README.md +++ b/extensions/minimax-portal-auth/README.md @@ -29,5 +29,5 @@ You will be prompted to select an endpoint: ## Notes -- MiniMax OAuth uses a device-code login flow. -- Tokens auto-refresh; re-run login if refresh fails or access is revoked. +- MiniMax OAuth uses a user-code login flow. +- Currently, OAuth login is supported only for the Coding plan \ No newline at end of file diff --git a/extensions/minimax-portal-auth/index.ts b/extensions/minimax-portal-auth/index.ts index c4d50f215..f36b26b24 100644 --- a/extensions/minimax-portal-auth/index.ts +++ b/extensions/minimax-portal-auth/index.ts @@ -44,6 +44,10 @@ function createOAuthHandler(region: MiniMaxRegion) { progress.stop("MiniMax OAuth complete"); + if (result.notification_message) { + await ctx.prompter.note(result.notification_message, "MiniMax OAuth"); + } + const profileId = `${PROVIDER_ID}:default`; const baseUrl = result.resourceUrl || defaultBaseUrl; @@ -85,7 +89,8 @@ function createOAuthHandler(region: MiniMaxRegion) { agents: { defaults: { models: { - "MiniMax-M2.1": { alias: "minimax" }, + "MiniMax-M2.1": { alias: "minimax-m2.1" }, + "MiniMax-M2.1-lightning": { alias: "minimax-m2.1-lightning" }, }, }, }, @@ -94,10 +99,12 @@ function createOAuthHandler(region: MiniMaxRegion) { notes: [ "MiniMax OAuth tokens auto-refresh. Re-run login if refresh fails or access is revoked.", `Base URL defaults to ${defaultBaseUrl}. Override models.providers.${PROVIDER_ID}.baseUrl if needed.`, + ...(result.notification_message ? [result.notification_message] : []), ], }; } catch (err) { - progress.stop("MiniMax OAuth failed"); + const errorMsg = err instanceof Error ? err.message : String(err); + progress.stop(`MiniMax OAuth failed: ${errorMsg}`); await ctx.prompter.note( "If OAuth fails, verify your MiniMax account has portal access and try again.", "MiniMax OAuth", diff --git a/extensions/minimax-portal-auth/oauth.ts b/extensions/minimax-portal-auth/oauth.ts index 6cc060975..9945ac551 100644 --- a/extensions/minimax-portal-auth/oauth.ts +++ b/extensions/minimax-portal-auth/oauth.ts @@ -29,10 +29,8 @@ function getOAuthEndpoints(region: MiniMaxRegion) { export type MiniMaxOAuthAuthorization = { user_code: string; verification_uri: string; - expires_in: number; + expired_in: number; interval?: number; - has_benefit: boolean; - benefit_message: string; state: string; }; @@ -41,6 +39,7 @@ export type MiniMaxOAuthToken = { refresh: string; expires: number; resourceUrl?: string; + notification_message?: string; }; type TokenPending = { status: "pending"; message?: string }; @@ -127,6 +126,7 @@ async function pollOAuthToken(params: { }); if (!response.ok) { + const text = await response.text(); let payload: { status?: string; base_resp?: { status_code?: number; status_msg?: string }; @@ -134,11 +134,11 @@ async function pollOAuthToken(params: { try { payload = (await response.json()) as typeof payload; } catch { - return { status: "error", message: response.statusText }; + return { status: "error", message: text || "MiniMax OAuth failed to parse response.",}; } return { status: "error", - message: payload?.base_resp?.status_msg ?? response.statusText, + message: text || "MiniMax OAuth failed to parse response.", }; } @@ -149,7 +149,13 @@ async function pollOAuthToken(params: { expired_in?: number | null; token_type?: string; resource_url?: string; + notification_message?: string; }; + + if (tokenPayload.status === "error") { + return { status: "error", message: "An error occurred. Please try again later"}; + } + if (tokenPayload.status != "success") { return { status: "pending", message: "current user code is not authorized" }; } @@ -163,8 +169,9 @@ async function pollOAuthToken(params: { token: { access: tokenPayload.access_token, refresh: tokenPayload.refresh_token, - expires: Date.now() + tokenPayload.expired_in * 1000, + expires: tokenPayload.expired_in, resourceUrl: tokenPayload.resource_url, + notification_message: tokenPayload.notification_message, }, }; } @@ -183,10 +190,8 @@ export async function loginMiniMaxPortalOAuth(params: { const noteLines = [ `Open ${verificationUrl} to approve access.`, `If prompted, enter the code ${oauth.user_code}.`, + `Interval: ${oauth.interval ?? "default (2000ms)"}, Expires in: ${oauth.expired_in}ms`, ]; - if (oauth.has_benefit && oauth.benefit_message) { - noteLines.push("", oauth.benefit_message); - } await params.note(noteLines.join("\n"), "MiniMax OAuth"); try { @@ -195,11 +200,11 @@ export async function loginMiniMaxPortalOAuth(params: { // Fall back to manual copy/paste if browser open fails. } - const start = Date.now(); - let pollIntervalMs = oauth.interval ? oauth.interval * 1000 : 2000; - const timeoutMs = oauth.expires_in * 1000; + let pollIntervalMs = oauth.interval ? oauth.interval : 2000; + const expireTimeMs = oauth.expired_in; - while (Date.now() - start < timeoutMs) { + + while (Date.now() < expireTimeMs) { params.progress.update("Waiting for MiniMax OAuth approval…"); const result = await pollOAuthToken({ userCode: oauth.user_code, @@ -207,6 +212,15 @@ export async function loginMiniMaxPortalOAuth(params: { region, }); + // // Debug: print poll result + // await params.note( + // `status: ${result.status}` + + // (result.status === "success" ? `\ntoken: ${JSON.stringify(result.token, null, 2)}` : "") + + // (result.status === "error" ? `\nmessage: ${result.message}` : "") + + // (result.status === "pending" && result.message ? `\nmessage: ${result.message}` : ""), + // "MiniMax OAuth Poll Result", + // ); + if (result.status === "success") { return result.token; } diff --git a/src/commands/auth-choice.apply.minimax.ts b/src/commands/auth-choice.apply.minimax.ts index eab5d9423..57cd67749 100644 --- a/src/commands/auth-choice.apply.minimax.ts +++ b/src/commands/auth-choice.apply.minimax.ts @@ -29,11 +29,20 @@ export async function applyAuthChoiceMiniMax( ); }; if (params.authChoice === "minimax-portal") { + // Let user choose between Global/CN endpoints + const endpoint = await params.prompter.select({ + message: "Select MiniMax endpoint", + options: [ + { value: "oauth", label: "Global", hint: "OAuth for international users" }, + { value: "oauth-cn", label: "CN", hint: "OAuth for users in China" }, + ], + }); + return await applyAuthChoicePluginProvider(params, { authChoice: "minimax-portal", pluginId: "minimax-portal-auth", providerId: "minimax-portal", - methodId: "device", + methodId: endpoint as string, label: "MiniMax", }); }