mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 01:01:23 +00:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -54,6 +54,7 @@ describe("buildAuthChoiceOptions", () => {
|
||||
});
|
||||
|
||||
expect(options.some((opt) => opt.value === "minimax-api")).toBe(true);
|
||||
expect(options.some((opt) => opt.value === "minimax-api-key-cn")).toBe(true);
|
||||
expect(options.some((opt) => opt.value === "minimax-api-lightning")).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ const AUTH_CHOICE_GROUP_DEFS: {
|
||||
value: "minimax",
|
||||
label: "MiniMax",
|
||||
hint: "M2.5 (recommended)",
|
||||
choices: ["minimax-portal", "minimax-api", "minimax-api-lightning"],
|
||||
choices: ["minimax-portal", "minimax-api", "minimax-api-key-cn", "minimax-api-lightning"],
|
||||
},
|
||||
{
|
||||
value: "moonshot",
|
||||
@@ -286,6 +286,11 @@ const BASE_AUTH_CHOICE_OPTIONS: ReadonlyArray<AuthChoiceOption> = [
|
||||
hint: "Claude, GPT, Gemini via opencode.ai/zen",
|
||||
},
|
||||
{ value: "minimax-api", label: "MiniMax M2.5" },
|
||||
{
|
||||
value: "minimax-api-key-cn",
|
||||
label: "MiniMax M2.5 (CN)",
|
||||
hint: "China endpoint (api.minimaxi.com)",
|
||||
},
|
||||
{
|
||||
value: "minimax-api-lightning",
|
||||
label: "MiniMax M2.5 Lightning",
|
||||
|
||||
@@ -10,7 +10,9 @@ import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
|
||||
import {
|
||||
applyAuthProfileConfig,
|
||||
applyMinimaxApiConfig,
|
||||
applyMinimaxApiConfigCn,
|
||||
applyMinimaxApiProviderConfig,
|
||||
applyMinimaxApiProviderConfigCn,
|
||||
applyMinimaxConfig,
|
||||
applyMinimaxProviderConfig,
|
||||
setMinimaxApiKey,
|
||||
@@ -97,6 +99,49 @@ export async function applyAuthChoiceMiniMax(
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
|
||||
if (params.authChoice === "minimax-api-key-cn") {
|
||||
const modelId = "MiniMax-M2.5";
|
||||
let hasCredential = false;
|
||||
const envKey = resolveEnvApiKey("minimax");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing MINIMAX_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
await setMinimaxApiKey(envKey.apiKey, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
}
|
||||
if (!hasCredential) {
|
||||
const key = await params.prompter.text({
|
||||
message: "Enter MiniMax China API key",
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
await setMinimaxApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||
}
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: "minimax:default",
|
||||
provider: "minimax",
|
||||
mode: "api_key",
|
||||
});
|
||||
{
|
||||
const modelRef = `minimax/${modelId}`;
|
||||
const applied = await applyDefaultModelChoice({
|
||||
config: nextConfig,
|
||||
setDefaultModel: params.setDefaultModel,
|
||||
defaultModel: modelRef,
|
||||
applyDefaultConfig: applyMinimaxApiConfigCn,
|
||||
applyProviderConfig: applyMinimaxApiProviderConfigCn,
|
||||
noteAgentModel,
|
||||
prompter: params.prompter,
|
||||
});
|
||||
nextConfig = applied.config;
|
||||
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
||||
}
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
|
||||
if (params.authChoice === "minimax") {
|
||||
const applied = await applyDefaultModelChoice({
|
||||
config: nextConfig,
|
||||
|
||||
@@ -6,7 +6,11 @@ import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import type { AuthChoice } from "./onboard-types.js";
|
||||
import { applyAuthChoice, resolvePreferredProviderForAuthChoice } from "./auth-choice.js";
|
||||
import { ZAI_CODING_CN_BASE_URL, ZAI_CODING_GLOBAL_BASE_URL } from "./onboard-auth.js";
|
||||
import {
|
||||
MINIMAX_CN_API_BASE_URL,
|
||||
ZAI_CODING_CN_BASE_URL,
|
||||
ZAI_CODING_GLOBAL_BASE_URL,
|
||||
} from "./onboard-auth.js";
|
||||
|
||||
vi.mock("../providers/github-copilot-auth.js", () => ({
|
||||
githubCopilotLoginCommand: vi.fn(async () => {}),
|
||||
@@ -209,6 +213,60 @@ describe("applyAuthChoice", () => {
|
||||
expect(parsed.profiles?.["minimax:default"]?.key).toBe("sk-minimax-test");
|
||||
});
|
||||
|
||||
it("prompts and writes MiniMax API key when selecting minimax-api-key-cn", async () => {
|
||||
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
|
||||
process.env.OPENCLAW_STATE_DIR = tempStateDir;
|
||||
process.env.OPENCLAW_AGENT_DIR = path.join(tempStateDir, "agent");
|
||||
process.env.PI_CODING_AGENT_DIR = process.env.OPENCLAW_AGENT_DIR;
|
||||
|
||||
const text = vi.fn().mockResolvedValue("sk-minimax-test");
|
||||
const select: WizardPrompter["select"] = vi.fn(
|
||||
async (params) => params.options[0]?.value as never,
|
||||
);
|
||||
const multiselect: WizardPrompter["multiselect"] = vi.fn(async () => []);
|
||||
const prompter: WizardPrompter = {
|
||||
intro: vi.fn(noopAsync),
|
||||
outro: vi.fn(noopAsync),
|
||||
note: vi.fn(noopAsync),
|
||||
select,
|
||||
multiselect,
|
||||
text,
|
||||
confirm: vi.fn(async () => false),
|
||||
progress: vi.fn(() => ({ update: noop, stop: noop })),
|
||||
};
|
||||
const runtime: RuntimeEnv = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn((code: number) => {
|
||||
throw new Error(`exit:${code}`);
|
||||
}),
|
||||
};
|
||||
|
||||
const result = await applyAuthChoice({
|
||||
authChoice: "minimax-api-key-cn",
|
||||
config: {},
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
});
|
||||
|
||||
expect(text).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: "Enter MiniMax China API key" }),
|
||||
);
|
||||
expect(result.config.auth?.profiles?.["minimax:default"]).toMatchObject({
|
||||
provider: "minimax",
|
||||
mode: "api_key",
|
||||
});
|
||||
expect(result.config.models?.providers?.minimax?.baseUrl).toBe(MINIMAX_CN_API_BASE_URL);
|
||||
|
||||
const authProfilePath = authProfilePathFor(requireAgentDir());
|
||||
const raw = await fs.readFile(authProfilePath, "utf8");
|
||||
const parsed = JSON.parse(raw) as {
|
||||
profiles?: Record<string, { key?: string }>;
|
||||
};
|
||||
expect(parsed.profiles?.["minimax:default"]?.key).toBe("sk-minimax-test");
|
||||
});
|
||||
|
||||
it("prompts and writes Synthetic API key when selecting synthetic-api-key", async () => {
|
||||
tempStateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
|
||||
process.env.OPENCLAW_STATE_DIR = tempStateDir;
|
||||
|
||||
@@ -34,6 +34,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
|
||||
"copilot-proxy": "copilot-proxy",
|
||||
"minimax-cloud": "minimax",
|
||||
"minimax-api": "minimax",
|
||||
"minimax-api-key-cn": "minimax",
|
||||
"minimax-api-lightning": "minimax",
|
||||
minimax: "lmstudio",
|
||||
"opencode-zen": "opencode",
|
||||
|
||||
@@ -2,7 +2,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { signalPlugin } from "../../extensions/signal/src/channel.js";
|
||||
import { setActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import { createIMessageTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
import { createIMessageTestPlugin } from "../test-utils/imessage-test-plugin.js";
|
||||
|
||||
const configMocks = vi.hoisted(() => ({
|
||||
readConfigFileSnapshot: vi.fn(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
DEFAULT_MINIMAX_CONTEXT_WINDOW,
|
||||
DEFAULT_MINIMAX_MAX_TOKENS,
|
||||
MINIMAX_API_BASE_URL,
|
||||
MINIMAX_CN_API_BASE_URL,
|
||||
MINIMAX_HOSTED_COST,
|
||||
MINIMAX_HOSTED_MODEL_ID,
|
||||
MINIMAX_HOSTED_MODEL_REF,
|
||||
@@ -148,7 +149,37 @@ export function applyMinimaxHostedConfig(
|
||||
// MiniMax Anthropic-compatible API (platform.minimax.io/anthropic)
|
||||
export function applyMinimaxApiProviderConfig(
|
||||
cfg: OpenClawConfig,
|
||||
modelId: string = "MiniMax-M2.1",
|
||||
modelId: string = "MiniMax-M2.5",
|
||||
): OpenClawConfig {
|
||||
return applyMinimaxApiProviderConfigWithBaseUrl(cfg, modelId, MINIMAX_API_BASE_URL);
|
||||
}
|
||||
|
||||
export function applyMinimaxApiConfig(
|
||||
cfg: OpenClawConfig,
|
||||
modelId: string = "MiniMax-M2.5",
|
||||
): OpenClawConfig {
|
||||
return applyMinimaxApiConfigWithBaseUrl(cfg, modelId, MINIMAX_API_BASE_URL);
|
||||
}
|
||||
|
||||
// MiniMax China API (api.minimaxi.com)
|
||||
export function applyMinimaxApiProviderConfigCn(
|
||||
cfg: OpenClawConfig,
|
||||
modelId: string = "MiniMax-M2.5",
|
||||
): OpenClawConfig {
|
||||
return applyMinimaxApiProviderConfigWithBaseUrl(cfg, modelId, MINIMAX_CN_API_BASE_URL);
|
||||
}
|
||||
|
||||
export function applyMinimaxApiConfigCn(
|
||||
cfg: OpenClawConfig,
|
||||
modelId: string = "MiniMax-M2.5",
|
||||
): OpenClawConfig {
|
||||
return applyMinimaxApiConfigWithBaseUrl(cfg, modelId, MINIMAX_CN_API_BASE_URL);
|
||||
}
|
||||
|
||||
function applyMinimaxApiProviderConfigWithBaseUrl(
|
||||
cfg: OpenClawConfig,
|
||||
modelId: string,
|
||||
baseUrl: string,
|
||||
): OpenClawConfig {
|
||||
const providers = { ...cfg.models?.providers };
|
||||
const existingProvider = providers.minimax;
|
||||
@@ -164,7 +195,7 @@ export function applyMinimaxApiProviderConfig(
|
||||
const normalizedApiKey = resolvedApiKey?.trim() === "minimax" ? "" : resolvedApiKey;
|
||||
providers.minimax = {
|
||||
...existingProviderRest,
|
||||
baseUrl: MINIMAX_API_BASE_URL,
|
||||
baseUrl,
|
||||
api: "anthropic-messages",
|
||||
...(normalizedApiKey?.trim() ? { apiKey: normalizedApiKey } : {}),
|
||||
models: mergedModels.length > 0 ? mergedModels : [apiModel],
|
||||
@@ -189,11 +220,12 @@ export function applyMinimaxApiProviderConfig(
|
||||
};
|
||||
}
|
||||
|
||||
export function applyMinimaxApiConfig(
|
||||
function applyMinimaxApiConfigWithBaseUrl(
|
||||
cfg: OpenClawConfig,
|
||||
modelId: string = "MiniMax-M2.1",
|
||||
modelId: string,
|
||||
baseUrl: string,
|
||||
): OpenClawConfig {
|
||||
const next = applyMinimaxApiProviderConfig(cfg, modelId);
|
||||
const next = applyMinimaxApiProviderConfigWithBaseUrl(cfg, modelId, baseUrl);
|
||||
return {
|
||||
...next,
|
||||
agents: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { QIANFAN_BASE_URL, QIANFAN_DEFAULT_MODEL_ID } from "../agents/models-con
|
||||
|
||||
export const DEFAULT_MINIMAX_BASE_URL = "https://api.minimax.io/v1";
|
||||
export const MINIMAX_API_BASE_URL = "https://api.minimax.io/anthropic";
|
||||
export const MINIMAX_CN_API_BASE_URL = "https://api.minimaxi.com/anthropic";
|
||||
export const MINIMAX_HOSTED_MODEL_ID = "MiniMax-M2.1";
|
||||
export const MINIMAX_HOSTED_MODEL_REF = `minimax/${MINIMAX_HOSTED_MODEL_ID}`;
|
||||
export const DEFAULT_MINIMAX_CONTEXT_WINDOW = 200000;
|
||||
|
||||
@@ -38,7 +38,9 @@ export {
|
||||
} from "./onboard-auth.config-core.js";
|
||||
export {
|
||||
applyMinimaxApiConfig,
|
||||
applyMinimaxApiConfigCn,
|
||||
applyMinimaxApiProviderConfig,
|
||||
applyMinimaxApiProviderConfigCn,
|
||||
applyMinimaxConfig,
|
||||
applyMinimaxHostedConfig,
|
||||
applyMinimaxHostedProviderConfig,
|
||||
@@ -92,6 +94,7 @@ export {
|
||||
KIMI_CODING_MODEL_ID,
|
||||
KIMI_CODING_MODEL_REF,
|
||||
MINIMAX_API_BASE_URL,
|
||||
MINIMAX_CN_API_BASE_URL,
|
||||
MINIMAX_HOSTED_MODEL_ID,
|
||||
MINIMAX_HOSTED_MODEL_REF,
|
||||
MOONSHOT_BASE_URL,
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function setupInternalHooks(
|
||||
"Hooks let you automate actions when agent commands are issued.",
|
||||
"Example: Save session context to memory when you issue /new.",
|
||||
"",
|
||||
"Learn more: https://docs.openclaw.ai/hooks",
|
||||
"Learn more: https://docs.openclaw.ai/automation/hooks",
|
||||
].join("\n"),
|
||||
"Hooks",
|
||||
);
|
||||
|
||||
@@ -155,6 +155,72 @@ async function expectApiKeyProfile(params: {
|
||||
}
|
||||
|
||||
describe("onboard (non-interactive): provider auth", () => {
|
||||
it("stores MiniMax API key and uses global baseUrl by default", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-minimax-", async ({ configPath, runtime }) => {
|
||||
await runNonInteractive(
|
||||
{
|
||||
nonInteractive: true,
|
||||
authChoice: "minimax-api",
|
||||
minimaxApiKey: "sk-minimax-test",
|
||||
skipHealth: true,
|
||||
skipChannels: true,
|
||||
skipSkills: true,
|
||||
json: true,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
const cfg = await readJsonFile<{
|
||||
auth?: { profiles?: Record<string, { provider?: string; mode?: string }> };
|
||||
agents?: { defaults?: { model?: { primary?: string } } };
|
||||
models?: { providers?: Record<string, { baseUrl?: string }> };
|
||||
}>(configPath);
|
||||
|
||||
expect(cfg.auth?.profiles?.["minimax:default"]?.provider).toBe("minimax");
|
||||
expect(cfg.auth?.profiles?.["minimax:default"]?.mode).toBe("api_key");
|
||||
expect(cfg.models?.providers?.minimax?.baseUrl).toBe("https://api.minimax.io/anthropic");
|
||||
expect(cfg.agents?.defaults?.model?.primary).toBe("minimax/MiniMax-M2.5");
|
||||
await expectApiKeyProfile({
|
||||
profileId: "minimax:default",
|
||||
provider: "minimax",
|
||||
key: "sk-minimax-test",
|
||||
});
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
it("supports MiniMax CN API endpoint auth choice", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-minimax-cn-", async ({ configPath, runtime }) => {
|
||||
await runNonInteractive(
|
||||
{
|
||||
nonInteractive: true,
|
||||
authChoice: "minimax-api-key-cn",
|
||||
minimaxApiKey: "sk-minimax-test",
|
||||
skipHealth: true,
|
||||
skipChannels: true,
|
||||
skipSkills: true,
|
||||
json: true,
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
const cfg = await readJsonFile<{
|
||||
auth?: { profiles?: Record<string, { provider?: string; mode?: string }> };
|
||||
agents?: { defaults?: { model?: { primary?: string } } };
|
||||
models?: { providers?: Record<string, { baseUrl?: string }> };
|
||||
}>(configPath);
|
||||
|
||||
expect(cfg.auth?.profiles?.["minimax:default"]?.provider).toBe("minimax");
|
||||
expect(cfg.auth?.profiles?.["minimax:default"]?.mode).toBe("api_key");
|
||||
expect(cfg.models?.providers?.minimax?.baseUrl).toBe("https://api.minimaxi.com/anthropic");
|
||||
expect(cfg.agents?.defaults?.model?.primary).toBe("minimax/MiniMax-M2.5");
|
||||
await expectApiKeyProfile({
|
||||
profileId: "minimax:default",
|
||||
provider: "minimax",
|
||||
key: "sk-minimax-test",
|
||||
});
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
it("stores Z.AI API key and uses global baseUrl by default", async () => {
|
||||
await withOnboardEnv("openclaw-onboard-zai-", async ({ configPath, runtime }) => {
|
||||
await runNonInteractive(
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
applyQianfanConfig,
|
||||
applyKimiCodeConfig,
|
||||
applyMinimaxApiConfig,
|
||||
applyMinimaxApiConfigCn,
|
||||
applyMinimaxConfig,
|
||||
applyMoonshotConfig,
|
||||
applyMoonshotConfigCn,
|
||||
@@ -570,6 +571,7 @@ export async function applyNonInteractiveAuthChoice(params: {
|
||||
if (
|
||||
authChoice === "minimax-cloud" ||
|
||||
authChoice === "minimax-api" ||
|
||||
authChoice === "minimax-api-key-cn" ||
|
||||
authChoice === "minimax-api-lightning"
|
||||
) {
|
||||
const resolved = await resolveNonInteractiveApiKey({
|
||||
@@ -592,8 +594,10 @@ export async function applyNonInteractiveAuthChoice(params: {
|
||||
mode: "api_key",
|
||||
});
|
||||
const modelId =
|
||||
authChoice === "minimax-api-lightning" ? "MiniMax-M2.1-lightning" : "MiniMax-M2.1";
|
||||
return applyMinimaxApiConfig(nextConfig, modelId);
|
||||
authChoice === "minimax-api-lightning" ? "MiniMax-M2.5-Lightning" : "MiniMax-M2.5";
|
||||
return authChoice === "minimax-api-key-cn"
|
||||
? applyMinimaxApiConfigCn(nextConfig, modelId)
|
||||
: applyMinimaxApiConfig(nextConfig, modelId);
|
||||
}
|
||||
|
||||
if (authChoice === "minimax") {
|
||||
|
||||
@@ -37,6 +37,7 @@ export type AuthChoice =
|
||||
| "minimax-cloud"
|
||||
| "minimax"
|
||||
| "minimax-api"
|
||||
| "minimax-api-key-cn"
|
||||
| "minimax-api-lightning"
|
||||
| "minimax-portal"
|
||||
| "opencode-zen"
|
||||
|
||||
Reference in New Issue
Block a user