Adapt MiniMax auth plugin compatibility (#913)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Haze <hazeone@users.noreply.github.com>
This commit is contained in:
Haze
2026-04-24 18:14:13 +08:00
committed by GitHub
parent 4271419abb
commit 89dd765fd6
4 changed files with 581 additions and 59 deletions

View File

@@ -30,6 +30,16 @@ vi.mock('electron', () => ({
},
}));
vi.mock('@electron/utils/paths', async () => {
const actual = await vi.importActual<typeof import('@electron/utils/paths')>('@electron/utils/paths');
const resolvedDir = join(testHome, '.openclaw-test-openclaw');
return {
...actual,
getOpenClawResolvedDir: () => resolvedDir,
getOpenClawDir: () => resolvedDir,
};
});
async function writeOpenClawJson(config: unknown): Promise<void> {
const openclawDir = join(testHome, '.openclaw');
await mkdir(openclawDir, { recursive: true });
@@ -494,6 +504,58 @@ describe('sanitizeOpenClawConfig', () => {
expect(dingtalk.clientId).toBe('dt-client-id');
expect(dingtalk.clientSecret).toBe('dt-secret');
});
it('removes stale minimax-portal-auth plugin entries when merged minimax plugin is installed', async () => {
await writeOpenClawJson({
plugins: {
allow: ['minimax-portal-auth', 'custom-plugin'],
entries: {
'minimax-portal-auth': { enabled: true },
'custom-plugin': { enabled: true },
},
},
models: {
providers: {
'minimax-portal': {
baseUrl: 'https://api.minimax.io/anthropic',
api: 'anthropic-messages',
},
},
},
});
const openclawDir = join(testHome, '.openclaw-package-sanitize');
await mkdir(join(openclawDir, 'dist', 'extensions', 'minimax'), { recursive: true });
await writeFile(
join(openclawDir, 'dist', 'extensions', 'minimax', 'openclaw.plugin.json'),
JSON.stringify({
id: 'minimax',
providers: ['minimax', 'minimax-portal'],
legacyPluginIds: ['minimax-portal-auth'],
}, null, 2),
'utf8',
);
vi.doMock('@electron/utils/paths', async () => {
const actual = await vi.importActual<typeof import('@electron/utils/paths')>('@electron/utils/paths');
return {
...actual,
getOpenClawResolvedDir: () => openclawDir,
};
});
const { sanitizeOpenClawConfig } = await import('@electron/utils/openclaw-auth');
await sanitizeOpenClawConfig();
const result = await readOpenClawJson();
const plugins = result.plugins as Record<string, unknown>;
const allow = plugins.allow as string[];
const entries = plugins.entries as Record<string, Record<string, unknown>>;
expect(allow).toEqual(['custom-plugin']);
expect(entries['minimax-portal-auth']).toBeUndefined();
expect(entries['custom-plugin']).toEqual({ enabled: true });
});
});
describe('syncProviderConfigToOpenClaw', () => {
@@ -504,6 +566,100 @@ describe('syncProviderConfigToOpenClaw', () => {
await rm(testUserData, { recursive: true, force: true });
});
it('uses legacy minimax-portal-auth plugin registration when only the legacy plugin exists', async () => {
await writeOpenClawJson({
models: { providers: {} },
});
const openclawDir = join(testHome, '.openclaw-package-old');
await mkdir(join(openclawDir, 'extensions', 'minimax-portal-auth'), { recursive: true });
await writeFile(
join(openclawDir, 'extensions', 'minimax-portal-auth', 'openclaw.plugin.json'),
JSON.stringify({
id: 'minimax-portal-auth',
providers: ['minimax-portal'],
}, null, 2),
'utf8',
);
vi.doMock('@electron/utils/paths', async () => {
const actual = await vi.importActual<typeof import('@electron/utils/paths')>('@electron/utils/paths');
return {
...actual,
getOpenClawResolvedDir: () => openclawDir,
};
});
const { syncProviderConfigToOpenClaw } = await import('@electron/utils/openclaw-auth');
await syncProviderConfigToOpenClaw('minimax-portal', 'MiniMax-M2.7', {
baseUrl: 'https://api.minimax.io/anthropic',
api: 'anthropic-messages',
apiKeyEnv: 'minimax-oauth',
});
const result = await readOpenClawJson();
const plugins = result.plugins as Record<string, unknown>;
const allow = plugins.allow as string[];
const entries = plugins.entries as Record<string, Record<string, unknown>>;
expect(allow).toContain('minimax-portal-auth');
expect(entries['minimax-portal-auth']).toEqual({ enabled: true });
expect(entries.minimax).toBeUndefined();
});
it('uses merged minimax plugin registration and removes stale legacy ids when minimax plugin is installed', async () => {
await writeOpenClawJson({
plugins: {
allow: ['minimax-portal-auth', 'custom-plugin'],
entries: {
'minimax-portal-auth': { enabled: true },
'custom-plugin': { enabled: true },
},
},
models: { providers: {} },
});
const openclawDir = join(testHome, '.openclaw-package-new');
await mkdir(join(openclawDir, 'dist', 'extensions', 'minimax'), { recursive: true });
await writeFile(
join(openclawDir, 'dist', 'extensions', 'minimax', 'openclaw.plugin.json'),
JSON.stringify({
id: 'minimax',
providers: ['minimax', 'minimax-portal'],
legacyPluginIds: ['minimax-portal-auth'],
}, null, 2),
'utf8',
);
vi.doMock('@electron/utils/paths', async () => {
const actual = await vi.importActual<typeof import('@electron/utils/paths')>('@electron/utils/paths');
return {
...actual,
getOpenClawResolvedDir: () => openclawDir,
};
});
const { syncProviderConfigToOpenClaw } = await import('@electron/utils/openclaw-auth');
await syncProviderConfigToOpenClaw('minimax-portal', 'MiniMax-M2.7', {
baseUrl: 'https://api.minimax.io/anthropic',
api: 'anthropic-messages',
apiKeyEnv: 'minimax-oauth',
});
const result = await readOpenClawJson();
const plugins = result.plugins as Record<string, unknown>;
const allow = plugins.allow as string[];
const entries = plugins.entries as Record<string, Record<string, unknown>>;
expect(allow).toContain('minimax');
expect(allow).toContain('custom-plugin');
expect(allow).not.toContain('minimax-portal-auth');
expect(entries.minimax).toEqual({ enabled: true });
expect(entries['minimax-portal-auth']).toBeUndefined();
});
it('writes moonshot web search config to plugin config instead of tools.web.search.kimi', async () => {
await writeOpenClawJson({
models: {
@@ -719,4 +875,104 @@ describe('auth-backed provider discovery', () => {
expect(result.providers).toEqual({});
await expect(getActiveOpenClawProviders()).resolves.toEqual(new Set());
});
it('removes merged and legacy minimax plugin registrations when deleting the provider', async () => {
await writeOpenClawJson({
plugins: {
allow: ['minimax', 'minimax-portal-auth', 'custom-plugin'],
entries: {
minimax: { enabled: true },
'minimax-portal-auth': { enabled: true },
'custom-plugin': { enabled: true },
},
},
models: {
providers: {
'minimax-portal': {
baseUrl: 'https://api.minimax.io/anthropic',
api: 'anthropic-messages',
},
},
},
});
const openclawDir = join(testHome, '.openclaw-package-new');
await mkdir(join(openclawDir, 'dist', 'extensions', 'minimax'), { recursive: true });
await writeFile(
join(openclawDir, 'dist', 'extensions', 'minimax', 'openclaw.plugin.json'),
JSON.stringify({
id: 'minimax',
providers: ['minimax', 'minimax-portal'],
legacyPluginIds: ['minimax-portal-auth'],
}, null, 2),
'utf8',
);
vi.doMock('@electron/utils/paths', async () => {
const actual = await vi.importActual<typeof import('@electron/utils/paths')>('@electron/utils/paths');
return {
...actual,
getOpenClawResolvedDir: () => openclawDir,
};
});
const { removeProviderFromOpenClaw } = await import('@electron/utils/openclaw-auth');
await removeProviderFromOpenClaw('minimax-portal');
const result = await readOpenClawJson();
const plugins = result.plugins as Record<string, unknown>;
const allow = plugins.allow as string[];
const entries = plugins.entries as Record<string, Record<string, unknown>>;
expect(allow).toEqual(['custom-plugin']);
expect(entries.minimax).toBeUndefined();
expect(entries['minimax-portal-auth']).toBeUndefined();
expect(entries['custom-plugin']).toEqual({ enabled: true });
});
it('sanitizes stale minimax-portal-auth entries when merged minimax plugin is installed', async () => {
await writeOpenClawJson({
plugins: {
allow: ['minimax-portal-auth', 'custom-plugin'],
entries: {
'minimax-portal-auth': { enabled: true },
'custom-plugin': { enabled: true },
},
},
});
const openclawDir = join(testHome, '.openclaw-package-new');
await mkdir(join(openclawDir, 'dist', 'extensions', 'minimax'), { recursive: true });
await writeFile(
join(openclawDir, 'dist', 'extensions', 'minimax', 'openclaw.plugin.json'),
JSON.stringify({
id: 'minimax',
providers: ['minimax', 'minimax-portal'],
legacyPluginIds: ['minimax-portal-auth'],
}, null, 2),
'utf8',
);
vi.doMock('@electron/utils/paths', async () => {
const actual = await vi.importActual<typeof import('@electron/utils/paths')>('@electron/utils/paths');
return {
...actual,
getOpenClawResolvedDir: () => openclawDir,
};
});
const { sanitizeOpenClawConfig } = await import('@electron/utils/openclaw-auth');
await sanitizeOpenClawConfig();
const result = await readOpenClawJson();
const plugins = result.plugins as Record<string, unknown>;
const allow = plugins.allow as string[];
const entries = plugins.entries as Record<string, Record<string, unknown>>;
expect(allow).toEqual(['custom-plugin']);
expect(entries['minimax-portal-auth']).toBeUndefined();
expect(entries['custom-plugin']).toEqual({ enabled: true });
});
});

View File

@@ -291,6 +291,49 @@ describe('ProviderService.listAccounts (openclaw.json as sole source of truth)',
expect(ids).toContain('minimax-portal-cn-uuid');
});
it('seeds a MiniMax CN account when minimax-portal baseUrl points at the CN endpoint', async () => {
mocks.listProviderAccounts.mockResolvedValue([]);
mocks.getActiveOpenClawProviders.mockResolvedValue(new Set(['minimax-portal']));
mocks.getOpenClawProvidersConfig.mockResolvedValue({
providers: {
'minimax-portal': { baseUrl: 'https://api.minimaxi.com/anthropic' },
},
defaultModel: undefined,
});
mocks.getProviderDefinition.mockImplementation((key: string) => {
if (key === 'minimax-portal-cn') {
return {
id: 'minimax-portal-cn',
name: 'MiniMax (CN)',
defaultAuthMode: 'oauth_device',
defaultModelId: 'MiniMax-M2.7',
providerConfig: {
baseUrl: 'https://api.minimaxi.com/anthropic',
api: 'anthropic-messages',
},
};
}
return undefined;
});
const result = await service.listAccounts();
expect(mocks.saveProviderAccount).toHaveBeenCalledWith(
expect.objectContaining({
id: 'minimax-portal',
vendorId: 'minimax-portal-cn',
label: 'MiniMax (CN)',
baseUrl: 'https://api.minimaxi.com/anthropic',
}),
);
expect(result).toHaveLength(1);
expect(result[0]).toEqual(expect.objectContaining({
id: 'minimax-portal',
vendorId: 'minimax-portal-cn',
label: 'MiniMax (CN)',
}));
});
it('seeds builtin providers discovered from auth profiles without explicit models.providers entries', async () => {
mocks.listProviderAccounts.mockResolvedValue([]);
mocks.getActiveOpenClawProviders.mockResolvedValue(new Set(['openai', 'anthropic']));