diff --git a/plugins/minimax-usage-source/server/usage.test.ts b/plugins/minimax-usage-source/server/usage.test.ts index 7450399c3b..57b58418ac 100644 --- a/plugins/minimax-usage-source/server/usage.test.ts +++ b/plugins/minimax-usage-source/server/usage.test.ts @@ -23,60 +23,6 @@ function writeMiniMaxCredentials( writeFileSync(join(dir, ".mmx", "credentials.json"), JSON.stringify(payload)); } -/** - * Writes the OAuth login the `mcode` CLI keeps under - * `~/.minimax/auth////`. - */ -function writeMiniMaxOAuthLogin( - dir: string, - options: { - buildEnv?: string; - region?: string; - clientId?: string; - accessToken?: string; - expiresAtMs?: number; - } = {}, -): string { - const buildEnv = options.buildEnv ?? "prod"; - const region = options.region ?? "cn"; - const clientId = options.clientId ?? "mcode-public"; - const clientDir = join(dir, ".minimax", "auth", buildEnv, region, clientId); - mkdirSync(clientDir, { recursive: true }); - writeFileSync( - join(clientDir, "auth.json"), - JSON.stringify({ - schemaVersion: 1, - records: { - "com.minimax.mcode.oauth.prod.cn": { - schemaVersion: 1, - accessToken: options.accessToken ?? "mmoat_fixture_token", - refreshToken: "mmort_fixture_token", - tokenType: "Bearer", - clientId, - scopes: ["agent.default"], - audience: "agent-backend", - ...(options.expiresAtMs === undefined ? {} : { expiresAtMs: options.expiresAtMs }), - }, - }, - }), - ); - writeFileSync( - join(clientDir, "auth-state.json"), - JSON.stringify({ - schemaVersion: 2, - status: "authenticated", - storeKind: "file", - clientId, - scopes: ["agent.default"], - audience: "agent-backend", - buildEnv, - region, - ...(options.expiresAtMs === undefined ? {} : { expiresAtMs: options.expiresAtMs }), - }), - ); - return join(clientDir, "auth.json"); -} - function mockFetch(handlers: Map Response>): typeof fetch { return vi.fn(async (url: RequestInfo | URL) => { const key = url.toString(); @@ -185,7 +131,7 @@ describe("minimax usage source", () => { const miniMax = findProvider(await service().listUsage(), "minimax"); - expect(requestedUrl).toBe("https://api.minimax.io/v1/api/openplatform/coding_plan/remains"); + expect(requestedUrl).toBe("https://api.minimax.io/v1/token_plan/remains"); expect(authorization).toBe("Bearer minimax_test_token"); expect(miniMax).toMatchObject({ status: "available", @@ -212,88 +158,6 @@ describe("minimax usage source", () => { expect(await discover()).toEqual([]); }); - it("discovers the OAuth login the mcode CLI stores under ~/.minimax/auth", async () => { - const authPath = writeMiniMaxOAuthLogin(homeDir, { - accessToken: "mmoat_fixture_token", - }); - - const accounts = await discover(); - - expect(accounts).toHaveLength(1); - expect(accounts[0]?.input).toEqual({ - store: "oauth", - locator: authPath, - baseUrl: "https://api.minimaxi.com", - }); - }); - - it("uses the region host the CLI logged in against", async () => { - writeMiniMaxOAuthLogin(homeDir, { region: "en" }); - - const accounts = await discover(); - - expect(accounts[0]?.input).toMatchObject({ baseUrl: "https://api.minimax.io" }); - }); - - it("fetches MiniMax usage from the CLI OAuth login", async () => { - writeMiniMaxOAuthLogin(homeDir, { accessToken: "mmoat_fixture_token" }); - let requestedUrl: string | null = null; - let authorization: string | null = null; - fetchApi = (async (url: RequestInfo | URL, init?: RequestInit) => { - requestedUrl = url.toString(); - authorization = (init?.headers as Record | undefined)?.Authorization ?? null; - return jsonResponse({ - model_remains: [ - { - model_name: "general", - end_time: Date.parse("2026-06-19T05:00:00.000Z"), - weekly_end_time: Date.parse("2026-06-26T00:00:00.000Z"), - current_interval_remaining_percent: 87, - current_weekly_remaining_percent: 78, - }, - ], - }); - }) as unknown as typeof fetch; - - const miniMax = findProvider(await service().listUsage(), "minimax"); - - expect(requestedUrl).toBe("https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains"); - expect(authorization).toBe("Bearer mmoat_fixture_token"); - expect(miniMax).toMatchObject({ - status: "available", - windows: expect.arrayContaining([ - expect.objectContaining({ id: "interval_general", usedPct: 13, remainingPct: 87 }), - expect.objectContaining({ id: "weekly_general", usedPct: 22, remainingPct: 78 }), - ]), - }); - }); - - it("reports an expired CLI OAuth login as expired", async () => { - writeMiniMaxOAuthLogin(homeDir, { - accessToken: "mmoat_fixture_token", - expiresAtMs: Date.now() - 60_000, - }); - let requested = false; - fetchApi = (async () => { - requested = true; - return jsonResponse({ model_remains: [] }); - }) as unknown as typeof fetch; - - const miniMax = findProvider(await service().listUsage(), "minimax"); - - expect(requested).toBe(false); - expect(miniMax).toMatchObject({ - status: "unavailable", - problem: { kind: "expired" }, - }); - }); - - it("ignores a CLI auth directory that has no access token", async () => { - writeMiniMaxOAuthLogin(homeDir, { accessToken: "" }); - - expect(await discover()).toEqual([]); - }); - it("reads MiniMax OAuth credentials from the CLI credentials file", async () => { writeMiniMaxCredentials( homeDir, @@ -309,9 +173,7 @@ describe("minimax usage source", () => { await service().listUsage(); - expect(requestedUrl).toBe( - "https://account.example.com/v1/api/openplatform/coding_plan/remains", - ); + expect(requestedUrl).toBe("https://account.example.com/v1/token_plan/remains"); }); it("falls back to MiniMax api_key in the CLI config file", async () => { @@ -327,7 +189,7 @@ describe("minimax usage source", () => { const miniMax = findProvider(await service().listUsage(), "minimax"); - expect(requestedUrl).toBe("https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains"); + expect(requestedUrl).toBe("https://api.minimaxi.com/v1/token_plan/remains"); expect(miniMax.status).toBe("unavailable"); }); @@ -336,7 +198,7 @@ describe("minimax usage source", () => { fetchApi = mockFetch( new Map([ [ - "https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains", + "https://api.minimaxi.com/v1/token_plan/remains", () => jsonResponse({ model_remains: null, @@ -360,7 +222,7 @@ describe("minimax usage source", () => { fetchApi = mockFetch( new Map([ [ - "https://api.minimax.io/v1/api/openplatform/coding_plan/remains", + "https://api.minimax.io/v1/token_plan/remains", () => jsonResponse({ model_remains: null }), ], ]), @@ -380,7 +242,7 @@ describe("minimax usage source", () => { fetchApi = mockFetch( new Map([ [ - "https://api.minimax.io/v1/api/openplatform/coding_plan/remains", + "https://api.minimax.io/v1/token_plan/remains", () => jsonResponse({ base_resp: { status_code: 0, status_msg: "success" }, @@ -413,7 +275,7 @@ describe("minimax usage source", () => { fetchApi = mockFetch( new Map([ [ - "https://api.minimax.io/v1/api/openplatform/coding_plan/remains", + "https://api.minimax.io/v1/token_plan/remains", () => jsonResponse({ model_remains: [ diff --git a/plugins/minimax-usage-source/server/usage.ts b/plugins/minimax-usage-source/server/usage.ts index 92d483d7db..7bf602cff1 100644 --- a/plugins/minimax-usage-source/server/usage.ts +++ b/plugins/minimax-usage-source/server/usage.ts @@ -1,7 +1,7 @@ import type { UsageInput } from "../shared/input.js"; import { promises as fs } from "node:fs"; import { homedir } from "node:os"; -import { join, dirname } from "node:path"; +import { join } from "node:path"; import { z } from "zod"; import { unavailable, @@ -20,10 +20,6 @@ const ApiOptionalStringSchema = z.preprocess( const MINIMAX_GLOBAL_BASE_URL = "https://api.minimax.io"; const MINIMAX_CN_BASE_URL = "https://api.minimaxi.com"; -// The coding-plan quota endpoint the CLI itself calls. The older -// `/v1/token_plan/remains` path is not the one the agent backend serves. -const MINIMAX_CODING_PLAN_PATH = "/v1/api/openplatform/coding_plan/remains"; - const MiniMaxModelRemainSchema = z.object({ model_name: ApiOptionalStringSchema, start_time: ApiNumberSchema.optional(), @@ -72,31 +68,6 @@ const MiniMaxConfigSchema = z.object({ oauth: MiniMaxCredentialsSchema.optional(), }); -/** - * The `mcode` CLI stores its OAuth login under `~/.minimax/auth////`. - * Each client directory holds an `auth.json` with the token and an - * `auth-state.json` with the region and expiry. - */ -const MiniMaxOAuthAuthSchema = z.object({ - schemaVersion: z.number().optional(), - records: z.record( - z.string(), - z.object({ - accessToken: z.string().optional(), - refreshToken: z.string().optional(), - expiresAtMs: z.number().optional(), - }), - ), -}); - -const MiniMaxOAuthStateSchema = z.object({ - status: z.string().optional(), - clientId: z.string().optional(), - buildEnv: z.string().optional(), - region: z.string().optional(), - expiresAtMs: z.number().optional(), -}); - type MiniMaxModelRemain = z.infer; interface MiniMaxResolvedAuth { @@ -183,7 +154,7 @@ export async function fetchUsage( if (auth.expiresAt && Date.parse(auth.expiresAt) <= Date.now()) return unavailable({ kind: "expired", expiresAt: new Date(auth.expiresAt).toISOString() }); - const res = await fetchApi(`${auth.baseUrl}${MINIMAX_CODING_PLAN_PATH}`, { + const res = await fetchApi(`${auth.baseUrl}/v1/token_plan/remains`, { signal: AbortSignal.timeout(15_000), headers: { Authorization: `Bearer ${auth.token}`, @@ -236,9 +207,6 @@ async function readAuth( ? { token, baseUrl: resolveBaseUrl({ baseUrl: process.env.MINIMAX_BASE_URL }) } : undefined; } - if (input.store === "oauth") { - return readOAuthAuth(input); - } try { const raw: unknown = JSON.parse(await fs.readFile(input.locator, "utf8")); if (input.store === "credentials") { @@ -271,101 +239,6 @@ async function readAuth( return undefined; } } - -/** - * Reads the OAuth login the `mcode` CLI writes to - * `~/.minimax/auth////auth.json`. The region - * decides which host serves the account, and the token lives inside a - * `records` map keyed by the OAuth grant. - */ -async function readOAuthAuth( - input: UsageInput, -): Promise<(MiniMaxResolvedAuth & { expiresAt?: string }) | undefined> { - try { - const raw: unknown = JSON.parse(await fs.readFile(input.locator, "utf8")); - const auth = MiniMaxOAuthAuthSchema.parse(raw); - const record = Object.values(auth.records).find((entry) => entry.accessToken); - if (!record?.accessToken) return undefined; - - const state = await readOAuthState(input.locator); - const expiresAtMs = record.expiresAtMs ?? state?.expiresAtMs; - return { - token: record.accessToken, - baseUrl: resolveBaseUrl({ - baseUrl: input.baseUrl, - region: state?.region, - }), - expiresAt: typeof expiresAtMs === "number" ? new Date(expiresAtMs).toISOString() : undefined, - }; - } catch { - return undefined; - } -} - -async function readOAuthState( - authPath: string, -): Promise | undefined> { - try { - const statePath = join(dirname(authPath), "auth-state.json"); - const raw: unknown = JSON.parse(await fs.readFile(statePath, "utf8")); - return MiniMaxOAuthStateSchema.parse(raw); - } catch { - return undefined; - } -} -/** - * Locates the `mcode` CLI's OAuth login. The layout is - * `~/.minimax/auth////auth.json`, so the client - * directory is found by scanning the region directories rather than by - * assuming a fixed client id. - */ -async function discoverOAuthLogins(): Promise { - const authRoot = join(homedir(), ".minimax", "auth"); - const inputs: UsageInput[] = []; - let buildEnvDirs: string[]; - try { - buildEnvDirs = (await fs.readdir(authRoot, { withFileTypes: true })) - .filter((entry) => entry.isDirectory()) - .map((entry) => entry.name); - } catch { - return inputs; - } - - for (const buildEnv of buildEnvDirs) { - const envRoot = join(authRoot, buildEnv); - let regionDirs: string[]; - try { - regionDirs = (await fs.readdir(envRoot, { withFileTypes: true })) - .filter((entry) => entry.isDirectory()) - .map((entry) => entry.name); - } catch { - continue; - } - for (const region of regionDirs) { - const regionRoot = join(envRoot, region); - let clientDirs: string[]; - try { - clientDirs = (await fs.readdir(regionRoot, { withFileTypes: true })) - .filter((entry) => entry.isDirectory()) - .map((entry) => entry.name); - } catch { - continue; - } - for (const clientId of clientDirs) { - const authPath = join(regionRoot, clientId, "auth.json"); - const auth = await readOAuthAuth({ store: "oauth", locator: authPath }); - if (!auth) continue; - inputs.push({ - store: "oauth", - locator: authPath, - baseUrl: auth.baseUrl, - }); - } - } - } - return inputs; -} - export async function discover(): Promise { const candidates: UsageInput[] = [ { store: "env", locator: "MINIMAX_API_KEY" }, @@ -375,14 +248,5 @@ export async function discover(): Promise { const accounts: UsageAccount[] = []; for (const input of candidates) if (await readAuth(input)) accounts.push({ key: "default", input }); - if (accounts.length > 0) return accounts; - - const oauthInputs = await discoverOAuthLogins(); - for (const input of oauthInputs) { - if (await readAuth(input)) { - accounts.push({ key: "default", input }); - break; - } - } return accounts; } diff --git a/plugins/minimax-usage-source/shared/input.ts b/plugins/minimax-usage-source/shared/input.ts index 4bee83584e..22e12a603f 100644 --- a/plugins/minimax-usage-source/shared/input.ts +++ b/plugins/minimax-usage-source/shared/input.ts @@ -1,13 +1,8 @@ import { z } from "zod"; export const inputSchema = z .object({ - store: z.enum(["env", "credentials", "config", "oauth"]), + store: z.enum(["env", "credentials", "config"]), locator: z.string().min(1), - /** - * Base URL for the coding-plan quota endpoint. The CLI's OAuth login is - * region-scoped (cn vs en), and each region serves its own host. - */ - baseUrl: z.string().optional(), }) .strict(); export type UsageInput = z.infer; diff --git a/plugins/zai-usage-source/server/usage.test.ts b/plugins/zai-usage-source/server/usage.test.ts index 1c9a940787..b5670d003a 100644 --- a/plugins/zai-usage-source/server/usage.test.ts +++ b/plugins/zai-usage-source/server/usage.test.ts @@ -32,6 +32,7 @@ describe("zai usage source", () => { process.env["USERPROFILE"] = homeDir; for (const key of [ "APPDATA", + "XDG_CONFIG_HOME", "COPILOT_TOKEN", "GITHUB_TOKEN", "GITHUB_PAT",