From ecd4c9c4f5750d106aa706397a6f46fb89b05dbd Mon Sep 17 00:00:00 2001 From: Tobias Bischoff <> Date: Thu, 8 Jan 2026 15:10:18 +0100 Subject: [PATCH] Onboarding: add MiniMax hosted API key option --- docs/cli/index.md | 3 +- scripts/bench-model.ts | 2 +- src/agents/minimax.live.test.ts | 2 +- src/agents/model-auth.ts | 1 + src/cli/program.ts | 5 +- src/commands/auth-choice-options.ts | 1 + src/commands/auth-choice.ts | 22 +++++++ src/commands/configure.ts | 18 ++++++ src/commands/onboard-auth.ts | 85 +++++++++++++++++++++++++ src/commands/onboard-non-interactive.ts | 16 +++++ src/commands/onboard-types.ts | 2 + 11 files changed, 153 insertions(+), 4 deletions(-) diff --git a/docs/cli/index.md b/docs/cli/index.md index 0c9c52108..d5b7acd4c 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -166,8 +166,9 @@ Options: - `--workspace ` - `--non-interactive` - `--mode ` -- `--auth-choice ` +- `--auth-choice ` - `--anthropic-api-key ` +- `--minimax-api-key ` - `--gateway-port ` - `--gateway-bind ` - `--gateway-auth ` diff --git a/scripts/bench-model.ts b/scripts/bench-model.ts index 32ed20ad0..0b3a60d01 100644 --- a/scripts/bench-model.ts +++ b/scripts/bench-model.ts @@ -88,7 +88,7 @@ async function main(): Promise { const minimaxBaseUrl = process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/v1"; const minimaxModelId = - process.env.MINIMAX_MODEL?.trim() || "minimax-m2.1"; + process.env.MINIMAX_MODEL?.trim() || "MiniMax-M2.1"; const minimaxModel: Model<"openai-completions"> = { id: minimaxModelId, diff --git a/src/agents/minimax.live.test.ts b/src/agents/minimax.live.test.ts index 666943876..53f033af1 100644 --- a/src/agents/minimax.live.test.ts +++ b/src/agents/minimax.live.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest"; const MINIMAX_KEY = process.env.MINIMAX_API_KEY ?? ""; const MINIMAX_BASE_URL = process.env.MINIMAX_BASE_URL?.trim() || "https://api.minimax.io/v1"; -const MINIMAX_MODEL = process.env.MINIMAX_MODEL?.trim() || "minimax-m2.1"; +const MINIMAX_MODEL = process.env.MINIMAX_MODEL?.trim() || "MiniMax-M2.1"; const LIVE = process.env.MINIMAX_LIVE_TEST === "1" || process.env.LIVE === "1"; const describeLive = LIVE && MINIMAX_KEY ? describe : describe.skip; diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 1716f7800..da6786a3e 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -135,6 +135,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { cerebras: "CEREBRAS_API_KEY", xai: "XAI_API_KEY", openrouter: "OPENROUTER_API_KEY", + minimax: "MINIMAX_API_KEY", zai: "ZAI_API_KEY", mistral: "MISTRAL_API_KEY", }; diff --git a/src/cli/program.ts b/src/cli/program.ts index 8c09a5758..4b777094d 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -232,9 +232,10 @@ export function buildProgram() { .option("--mode ", "Wizard mode: local|remote") .option( "--auth-choice ", - "Auth: oauth|claude-cli|openai-codex|codex-cli|antigravity|apiKey|minimax|skip", + "Auth: oauth|claude-cli|openai-codex|codex-cli|antigravity|apiKey|minimax-cloud|minimax|skip", ) .option("--anthropic-api-key ", "Anthropic API key") + .option("--minimax-api-key ", "MiniMax API key") .option("--gateway-port ", "Gateway port") .option("--gateway-bind ", "Gateway bind: loopback|lan|tailnet|auto") .option("--gateway-auth ", "Gateway auth: off|token|password") @@ -264,10 +265,12 @@ export function buildProgram() { | "codex-cli" | "antigravity" | "apiKey" + | "minimax-cloud" | "minimax" | "skip" | undefined, anthropicApiKey: opts.anthropicApiKey as string | undefined, + minimaxApiKey: opts.minimaxApiKey as string | undefined, gatewayPort: typeof opts.gatewayPort === "string" ? Number.parseInt(opts.gatewayPort, 10) diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 4feacf9f2..50cd7c766 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -77,6 +77,7 @@ export function buildAuthChoiceOptions(params: { label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)", }); options.push({ value: "apiKey", label: "Anthropic API key" }); + options.push({ value: "minimax-cloud", label: "MiniMax M2.1 (minimax.io)" }); options.push({ value: "minimax", label: "Minimax M2.1 (LM Studio)" }); if (params.includeSkip) { options.push({ value: "skip", label: "Skip for now" }); diff --git a/src/commands/auth-choice.ts b/src/commands/auth-choice.ts index 195bcf50b..b7febead2 100644 --- a/src/commands/auth-choice.ts +++ b/src/commands/auth-choice.ts @@ -28,8 +28,12 @@ import { import { applyAuthProfileConfig, applyMinimaxConfig, + applyMinimaxHostedConfig, + applyMinimaxHostedProviderConfig, applyMinimaxProviderConfig, + MINIMAX_HOSTED_MODEL_REF, setAnthropicApiKey, + setMinimaxApiKey, writeOAuthCredentials, } from "./onboard-auth.js"; import { openUrl } from "./onboard-helpers.js"; @@ -397,6 +401,24 @@ export async function applyAuthChoice(params: { provider: "anthropic", mode: "api_key", }); + } else if (params.authChoice === "minimax-cloud") { + const key = await params.prompter.text({ + message: "Enter MiniMax API key", + validate: (value) => (value?.trim() ? undefined : "Required"), + }); + await setMinimaxApiKey(String(key).trim(), params.agentDir); + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "minimax:default", + provider: "minimax", + mode: "api_key", + }); + if (params.setDefaultModel) { + nextConfig = applyMinimaxHostedConfig(nextConfig); + } else { + nextConfig = applyMinimaxHostedProviderConfig(nextConfig); + agentModelOverride = MINIMAX_HOSTED_MODEL_REF; + await noteAgentModel(MINIMAX_HOSTED_MODEL_REF); + } } else if (params.authChoice === "minimax") { if (params.setDefaultModel) { nextConfig = applyMinimaxConfig(nextConfig); diff --git a/src/commands/configure.ts b/src/commands/configure.ts index 549e3d95d..ef85bd0ce 100644 --- a/src/commands/configure.ts +++ b/src/commands/configure.ts @@ -52,7 +52,9 @@ import { healthCommand } from "./health.js"; import { applyAuthProfileConfig, applyMinimaxConfig, + applyMinimaxHostedConfig, setAnthropicApiKey, + setMinimaxApiKey, writeOAuthCredentials, } from "./onboard-auth.js"; import { @@ -296,6 +298,7 @@ async function promptAuthConfig( | "codex-cli" | "antigravity" | "apiKey" + | "minimax-cloud" | "minimax" | "skip"; @@ -522,6 +525,21 @@ async function promptAuthConfig( provider: "anthropic", mode: "api_key", }); + } else if (authChoice === "minimax-cloud") { + const key = guardCancel( + await text({ + message: "Enter MiniMax API key", + validate: (value) => (value?.trim() ? undefined : "Required"), + }), + runtime, + ); + await setMinimaxApiKey(String(key).trim()); + next = applyAuthProfileConfig(next, { + profileId: "minimax:default", + provider: "minimax", + mode: "api_key", + }); + next = applyMinimaxHostedConfig(next); } else if (authChoice === "minimax") { next = applyMinimaxConfig(next); } diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index db51f4b84..b65c72a88 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -3,6 +3,12 @@ import { resolveDefaultAgentDir } from "../agents/agent-scope.js"; import { upsertAuthProfile } from "../agents/auth-profiles.js"; import type { ClawdbotConfig } from "../config/config.js"; +const DEFAULT_MINIMAX_BASE_URL = "https://api.minimax.io/v1"; +export const MINIMAX_HOSTED_MODEL_ID = "MiniMax-M2.1"; +const DEFAULT_MINIMAX_CONTEXT_WINDOW = 200000; +const DEFAULT_MINIMAX_MAX_TOKENS = 8192; +export const MINIMAX_HOSTED_MODEL_REF = `minimax/${MINIMAX_HOSTED_MODEL_ID}`; + export async function writeOAuthCredentials( provider: OAuthProvider, creds: OAuthCredentials, @@ -33,6 +39,19 @@ export async function setAnthropicApiKey(key: string, agentDir?: string) { }); } +export async function setMinimaxApiKey(key: string, agentDir?: string) { + // Write to the multi-agent path so gateway finds credentials on startup + upsertAuthProfile({ + profileId: "minimax:default", + credential: { + type: "api_key", + provider: "minimax", + key, + }, + agentDir: agentDir ?? resolveDefaultAgentDir(), + }); +} + export function applyAuthProfileConfig( cfg: ClawdbotConfig, params: { @@ -119,6 +138,49 @@ export function applyMinimaxProviderConfig( }; } +export function applyMinimaxHostedProviderConfig( + cfg: ClawdbotConfig, + params?: { baseUrl?: string }, +): ClawdbotConfig { + const models = { ...cfg.agent?.models }; + models[MINIMAX_HOSTED_MODEL_REF] = { + ...models[MINIMAX_HOSTED_MODEL_REF], + alias: models[MINIMAX_HOSTED_MODEL_REF]?.alias ?? "Minimax", + }; + + const providers = { ...cfg.models?.providers }; + if (!providers.minimax) { + providers.minimax = { + baseUrl: params?.baseUrl?.trim() || DEFAULT_MINIMAX_BASE_URL, + apiKey: "minimax", + api: "openai-completions", + models: [ + { + id: MINIMAX_HOSTED_MODEL_ID, + name: "MiniMax M2.1", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: DEFAULT_MINIMAX_CONTEXT_WINDOW, + maxTokens: DEFAULT_MINIMAX_MAX_TOKENS, + }, + ], + }; + } + + return { + ...cfg, + agent: { + ...cfg.agent, + models, + }, + models: { + mode: cfg.models?.mode ?? "merge", + providers, + }, + }; +} + export function applyMinimaxConfig(cfg: ClawdbotConfig): ClawdbotConfig { const next = applyMinimaxProviderConfig(cfg); return { @@ -138,3 +200,26 @@ export function applyMinimaxConfig(cfg: ClawdbotConfig): ClawdbotConfig { }, }; } + +export function applyMinimaxHostedConfig( + cfg: ClawdbotConfig, + params?: { baseUrl?: string }, +): ClawdbotConfig { + const next = applyMinimaxHostedProviderConfig(cfg, params); + return { + ...next, + agent: { + ...next.agent, + model: { + ...(next.agent?.model && + "fallbacks" in (next.agent.model as Record) + ? { + fallbacks: (next.agent.model as { fallbacks?: string[] }) + .fallbacks, + } + : undefined), + primary: MINIMAX_HOSTED_MODEL_REF, + }, + }, + }; +} diff --git a/src/commands/onboard-non-interactive.ts b/src/commands/onboard-non-interactive.ts index 7e3821fa1..8338484c6 100644 --- a/src/commands/onboard-non-interactive.ts +++ b/src/commands/onboard-non-interactive.ts @@ -25,7 +25,9 @@ import { healthCommand } from "./health.js"; import { applyAuthProfileConfig, applyMinimaxConfig, + applyMinimaxHostedConfig, setAnthropicApiKey, + setMinimaxApiKey, } from "./onboard-auth.js"; import { applyWizardMetadata, @@ -117,6 +119,20 @@ export async function runNonInteractiveOnboarding( provider: "anthropic", mode: "api_key", }); + } else if (authChoice === "minimax-cloud") { + const key = opts.minimaxApiKey?.trim(); + if (!key) { + runtime.error("Missing --minimax-api-key"); + runtime.exit(1); + return; + } + await setMinimaxApiKey(key); + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "minimax:default", + provider: "minimax", + mode: "api_key", + }); + nextConfig = applyMinimaxHostedConfig(nextConfig); } else if (authChoice === "claude-cli") { const store = ensureAuthProfileStore(); if (!store.profiles[CLAUDE_CLI_PROFILE_ID]) { diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index 09feace3b..53333e5ab 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -9,6 +9,7 @@ export type AuthChoice = | "codex-cli" | "antigravity" | "apiKey" + | "minimax-cloud" | "minimax" | "skip"; export type GatewayAuthChoice = "off" | "token" | "password"; @@ -24,6 +25,7 @@ export type OnboardOptions = { nonInteractive?: boolean; authChoice?: AuthChoice; anthropicApiKey?: string; + minimaxApiKey?: string; gatewayPort?: number; gatewayBind?: GatewayBind; gatewayAuth?: GatewayAuthChoice;