mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
* feat: add model alias overrides Preserve user model overrides across provider catalog refreshes and resolve effective model metadata for runtime, TUI, protocol, and ACP consumers. * fix: apply model display name overrides Show overridden model display names in the footer, welcome panel, status output, and model switch confirmations. * fix: pass through kimi effort when undeclared Keep support_efforts authoritative when declared, but pass requested Kimi thinking effort through when the model does not declare support_efforts. * fix: honor model overrides in effort commands Use effective model metadata for /effort choices and for always_thinking clamping when resolving thinking effort.
490 lines
16 KiB
TypeScript
490 lines
16 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import {
|
|
applyOpenPlatformConfig,
|
|
capabilitiesForModel,
|
|
fetchOpenPlatformModels,
|
|
filterModelsByPrefix,
|
|
getOpenPlatformById,
|
|
isOpenPlatformId,
|
|
OPEN_PLATFORMS,
|
|
OpenPlatformApiError,
|
|
removeOpenPlatformConfig,
|
|
type ManagedKimiConfigShape,
|
|
} from '../src/open-platform';
|
|
|
|
function makeModelsResponse(): Response {
|
|
return new Response(
|
|
JSON.stringify({
|
|
data: [
|
|
{
|
|
id: 'kimi-k2-0712-preview',
|
|
context_length: 256000,
|
|
supports_reasoning: true,
|
|
supports_image_in: true,
|
|
supports_video_in: true,
|
|
display_name: 'Kimi K2 0712 Preview',
|
|
},
|
|
{
|
|
id: 'kimi-k2-lite',
|
|
context_length: 128000,
|
|
supports_reasoning: false,
|
|
supports_image_in: false,
|
|
supports_video_in: false,
|
|
supports_tool_use: false,
|
|
},
|
|
{
|
|
id: 'non-kimi-model',
|
|
context_length: 1000,
|
|
supports_reasoning: false,
|
|
},
|
|
],
|
|
}),
|
|
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
);
|
|
}
|
|
|
|
describe('OPEN_PLATFORMS', () => {
|
|
it('contains moonshot.cn and moonshot.ai', () => {
|
|
expect(getOpenPlatformById('moonshot-cn')).toMatchObject({
|
|
name: 'Kimi Platform (API key · platform.kimi.com)',
|
|
baseUrl: 'https://api.moonshot.cn/v1',
|
|
consoleUrl: 'https://platform.kimi.com',
|
|
allowedPrefixes: ['kimi-k'],
|
|
});
|
|
expect(getOpenPlatformById('moonshot-ai')).toMatchObject({
|
|
name: 'Kimi Platform (API key · platform.kimi.ai)',
|
|
baseUrl: 'https://api.moonshot.ai/v1',
|
|
consoleUrl: 'https://platform.kimi.ai',
|
|
allowedPrefixes: ['kimi-k'],
|
|
});
|
|
expect(getOpenPlatformById('unknown')).toBeUndefined();
|
|
});
|
|
|
|
it('isOpenPlatformId works', () => {
|
|
expect(isOpenPlatformId('moonshot-cn')).toBe(true);
|
|
expect(isOpenPlatformId('moonshot-ai')).toBe(true);
|
|
expect(isOpenPlatformId('kimi-code')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('fetchOpenPlatformModels', () => {
|
|
it('lists and parses models from the platform endpoint', async () => {
|
|
const fetchMock = vi.fn(async () => makeModelsResponse());
|
|
const platform = getOpenPlatformById('moonshot-cn')!;
|
|
|
|
const models = await fetchOpenPlatformModels(platform, 'sk-test', fetchMock as unknown as typeof fetch);
|
|
|
|
expect(models).toHaveLength(3);
|
|
expect(models[0]).toMatchObject({
|
|
id: 'kimi-k2-0712-preview',
|
|
contextLength: 256000,
|
|
supportsReasoning: true,
|
|
supportsImageIn: true,
|
|
supportsVideoIn: true,
|
|
displayName: 'Kimi K2 0712 Preview',
|
|
});
|
|
expect(models[1]?.supportsToolUse).toBe(false);
|
|
expect(models[2]?.id).toBe('non-kimi-model');
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
'https://api.moonshot.cn/v1/models',
|
|
expect.objectContaining({
|
|
headers: expect.objectContaining({
|
|
Authorization: 'Bearer sk-test',
|
|
Accept: 'application/json',
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('surfaces API error messages and status on HTTP error', async () => {
|
|
const fetchMock = vi.fn(
|
|
async () =>
|
|
new Response(JSON.stringify({ error: { message: 'invalid API key' } }), { status: 401 }),
|
|
);
|
|
const platform = getOpenPlatformById('moonshot-cn')!;
|
|
|
|
const error = await fetchOpenPlatformModels(
|
|
platform,
|
|
'sk-bad',
|
|
fetchMock as unknown as typeof fetch,
|
|
).catch((caught: unknown) => caught);
|
|
|
|
expect(error).toBeInstanceOf(OpenPlatformApiError);
|
|
expect((error as OpenPlatformApiError).status).toBe(401);
|
|
expect((error as Error).message).toBe('invalid API key');
|
|
});
|
|
|
|
it('throws on unexpected response shape', async () => {
|
|
const fetchMock = vi.fn(async () => new Response(JSON.stringify({}), { status: 200 }));
|
|
const platform = getOpenPlatformById('moonshot-cn')!;
|
|
|
|
await expect(
|
|
fetchOpenPlatformModels(platform, 'sk-test', fetchMock as unknown as typeof fetch),
|
|
).rejects.toThrow(/Unexpected models response/);
|
|
});
|
|
});
|
|
|
|
describe('filterModelsByPrefix', () => {
|
|
it('filters by allowedPrefixes when present', () => {
|
|
const platform = getOpenPlatformById('moonshot-cn')!;
|
|
const models = [
|
|
{ id: 'kimi-k2-0712-preview', contextLength: 256000, supportsReasoning: true, supportsImageIn: true, supportsVideoIn: true },
|
|
{ id: 'gpt-4', contextLength: 1000, supportsReasoning: false, supportsImageIn: false, supportsVideoIn: false },
|
|
];
|
|
|
|
const filtered = filterModelsByPrefix(models as unknown as import('../src/managed-kimi-code').ManagedKimiCodeModelInfo[], platform);
|
|
expect(filtered).toHaveLength(1);
|
|
expect(filtered[0]?.id).toBe('kimi-k2-0712-preview');
|
|
});
|
|
|
|
it('returns all models when allowedPrefixes is absent', () => {
|
|
const platform: import('../src/open-platform').OpenPlatformDefinition = {
|
|
id: 'custom',
|
|
name: 'Custom',
|
|
baseUrl: 'https://example.com/v1',
|
|
};
|
|
const models = [
|
|
{ id: 'model-a', contextLength: 1000, supportsReasoning: false, supportsImageIn: false, supportsVideoIn: false },
|
|
{ id: 'model-b', contextLength: 2000, supportsReasoning: false, supportsImageIn: false, supportsVideoIn: false },
|
|
];
|
|
|
|
const filtered = filterModelsByPrefix(models as unknown as import('../src/managed-kimi-code').ManagedKimiCodeModelInfo[], platform);
|
|
expect(filtered).toHaveLength(2);
|
|
});
|
|
});
|
|
|
|
describe('fetchOpenPlatformModels supports_thinking_type', () => {
|
|
it('parses supports_thinking_type from the models endpoint', async () => {
|
|
const fetchMock = vi.fn(
|
|
async () =>
|
|
new Response(
|
|
JSON.stringify({
|
|
data: [
|
|
{
|
|
id: 'kimi-k2-deep',
|
|
context_length: 256000,
|
|
supports_reasoning: true,
|
|
supports_thinking_type: 'only',
|
|
},
|
|
{
|
|
id: 'kimi-k2-lite',
|
|
context_length: 128000,
|
|
supports_reasoning: false,
|
|
},
|
|
],
|
|
}),
|
|
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
),
|
|
);
|
|
const platform = getOpenPlatformById('moonshot-cn')!;
|
|
|
|
const models = await fetchOpenPlatformModels(platform, 'sk-test', fetchMock as unknown as typeof fetch);
|
|
|
|
expect(models[0]?.supportsThinkingType).toBe('only');
|
|
expect(models[1]?.supportsThinkingType).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('capabilitiesForModel', () => {
|
|
it("locks thinking on for 'only' models", () => {
|
|
const model = {
|
|
id: 'deep',
|
|
contextLength: 1000,
|
|
supportsReasoning: true,
|
|
supportsImageIn: false,
|
|
supportsVideoIn: false,
|
|
supportsToolUse: false,
|
|
supportsThinkingType: 'only' as const,
|
|
};
|
|
expect(capabilitiesForModel(model)).toEqual(['thinking', 'always_thinking']);
|
|
});
|
|
|
|
it("lets 'no' override the legacy supports_reasoning boolean", () => {
|
|
const model = {
|
|
id: 'plain',
|
|
contextLength: 1000,
|
|
supportsReasoning: true,
|
|
supportsImageIn: false,
|
|
supportsVideoIn: false,
|
|
supportsToolUse: false,
|
|
supportsThinkingType: 'no' as const,
|
|
};
|
|
expect(capabilitiesForModel(model)).toBeUndefined();
|
|
});
|
|
|
|
it("emits a plain toggleable thinking capability for 'both'", () => {
|
|
const model = {
|
|
id: 'toggle',
|
|
contextLength: 1000,
|
|
supportsReasoning: false,
|
|
supportsImageIn: false,
|
|
supportsVideoIn: false,
|
|
supportsToolUse: false,
|
|
supportsThinkingType: 'both' as const,
|
|
};
|
|
expect(capabilitiesForModel(model)).toEqual(['thinking']);
|
|
});
|
|
|
|
it('returns undefined for a model with no capabilities', () => {
|
|
const model = {
|
|
id: 'plain',
|
|
contextLength: 1000,
|
|
supportsReasoning: false,
|
|
supportsImageIn: false,
|
|
supportsVideoIn: false,
|
|
supportsToolUse: false,
|
|
};
|
|
expect(capabilitiesForModel(model)).toBeUndefined();
|
|
});
|
|
|
|
it('returns all caps for a full-featured model', () => {
|
|
const model = {
|
|
id: 'full',
|
|
contextLength: 1000,
|
|
supportsReasoning: true,
|
|
supportsImageIn: true,
|
|
supportsVideoIn: true,
|
|
supportsToolUse: true,
|
|
};
|
|
expect(capabilitiesForModel(model)).toEqual(['thinking', 'image_in', 'video_in', 'tool_use']);
|
|
});
|
|
});
|
|
|
|
describe('applyOpenPlatformConfig', () => {
|
|
it('writes provider, models, and defaults', () => {
|
|
const config: ManagedKimiConfigShape = {
|
|
providers: {},
|
|
};
|
|
const platform = getOpenPlatformById('moonshot-cn')!;
|
|
const models = [
|
|
{ id: 'kimi-k2-0712-preview', contextLength: 256000, supportsReasoning: true, supportsImageIn: true, supportsVideoIn: true, displayName: 'Kimi K2' },
|
|
{ id: 'kimi-k2-lite', contextLength: 128000, supportsReasoning: false, supportsImageIn: false, supportsVideoIn: false },
|
|
];
|
|
|
|
const result = applyOpenPlatformConfig(config, {
|
|
platform,
|
|
models,
|
|
selectedModel: models[0]!,
|
|
thinking: true,
|
|
apiKey: 'sk-test',
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
defaultModel: 'moonshot-cn/kimi-k2-0712-preview',
|
|
defaultThinking: true,
|
|
});
|
|
|
|
expect(config.providers['moonshot-cn']).toMatchObject({
|
|
type: 'kimi',
|
|
baseUrl: 'https://api.moonshot.cn/v1',
|
|
apiKey: 'sk-test',
|
|
});
|
|
expect(config.models?.['moonshot-cn/kimi-k2-0712-preview']).toMatchObject({
|
|
provider: 'moonshot-cn',
|
|
model: 'kimi-k2-0712-preview',
|
|
maxContextSize: 256000,
|
|
capabilities: ['thinking', 'image_in', 'video_in', 'tool_use'],
|
|
displayName: 'Kimi K2',
|
|
});
|
|
expect(config.defaultModel).toBe('moonshot-cn/kimi-k2-0712-preview');
|
|
expect(config.thinking?.enabled).toBe(true);
|
|
expect(config.services).toBeUndefined();
|
|
});
|
|
|
|
it('clears stale models for the same provider', () => {
|
|
const config: ManagedKimiConfigShape = {
|
|
providers: {
|
|
'moonshot-cn': { type: 'kimi', baseUrl: 'https://api.moonshot.cn/v1', apiKey: 'sk-old' },
|
|
},
|
|
models: {
|
|
'moonshot-cn/stale': { provider: 'moonshot-cn', model: 'stale', maxContextSize: 1000 },
|
|
'other/model': { provider: 'other', model: 'other-model', maxContextSize: 1000 },
|
|
},
|
|
};
|
|
const platform = getOpenPlatformById('moonshot-cn')!;
|
|
const models = [
|
|
{ id: 'kimi-k2-0712-preview', contextLength: 256000, supportsReasoning: true, supportsImageIn: true, supportsVideoIn: true },
|
|
];
|
|
|
|
applyOpenPlatformConfig(config, {
|
|
platform,
|
|
models,
|
|
selectedModel: models[0]!,
|
|
thinking: false,
|
|
apiKey: 'sk-new',
|
|
});
|
|
|
|
expect(config.models?.['moonshot-cn/stale']).toBeUndefined();
|
|
expect(config.models?.['other/model']).toBeDefined();
|
|
});
|
|
|
|
it('preserves hand-edited fields that upstream does not declare', () => {
|
|
const config: ManagedKimiConfigShape = {
|
|
providers: {
|
|
'moonshot-cn': { type: 'kimi', baseUrl: 'https://api.moonshot.cn/v1', apiKey: 'sk-old' },
|
|
},
|
|
models: {
|
|
'moonshot-cn/kimi-k2-0712-preview': {
|
|
provider: 'moonshot-cn',
|
|
model: 'kimi-k2-0712-preview',
|
|
maxContextSize: 256000,
|
|
maxOutputSize: 8192,
|
|
supportEfforts: ['low', 'high'],
|
|
} as Record<string, unknown>,
|
|
},
|
|
};
|
|
const platform = getOpenPlatformById('moonshot-cn')!;
|
|
const models = [
|
|
{
|
|
id: 'kimi-k2-0712-preview',
|
|
contextLength: 256000,
|
|
supportsReasoning: true,
|
|
supportsImageIn: true,
|
|
supportsVideoIn: true,
|
|
},
|
|
];
|
|
|
|
applyOpenPlatformConfig(config, {
|
|
platform,
|
|
models,
|
|
selectedModel: models[0]!,
|
|
thinking: false,
|
|
apiKey: 'sk-new',
|
|
});
|
|
|
|
const alias = config.models?.['moonshot-cn/kimi-k2-0712-preview'];
|
|
expect(alias?.['maxOutputSize']).toBe(8192);
|
|
expect(alias?.['supportEfforts']).toBeUndefined();
|
|
});
|
|
|
|
it('preserves open-platform overrides during refresh', () => {
|
|
const config: ManagedKimiConfigShape = {
|
|
providers: {
|
|
'moonshot-cn': { type: 'kimi', baseUrl: 'https://api.moonshot.cn/v1', apiKey: 'sk-old' },
|
|
},
|
|
models: {
|
|
'moonshot-cn/kimi-k2-0712-preview': {
|
|
provider: 'moonshot-cn',
|
|
model: 'kimi-k2-0712-preview',
|
|
maxContextSize: 256000,
|
|
overrides: { supportEfforts: ['low'] },
|
|
} as Record<string, unknown>,
|
|
},
|
|
};
|
|
const platform = getOpenPlatformById('moonshot-cn')!;
|
|
const models = [
|
|
{
|
|
id: 'kimi-k2-0712-preview',
|
|
contextLength: 256000,
|
|
supportsReasoning: true,
|
|
supportsImageIn: false,
|
|
supportsVideoIn: false,
|
|
supportEfforts: ['low', 'high'],
|
|
},
|
|
];
|
|
|
|
applyOpenPlatformConfig(config, {
|
|
platform,
|
|
models,
|
|
selectedModel: models[0]!,
|
|
thinking: false,
|
|
apiKey: 'sk-new',
|
|
});
|
|
|
|
const alias = config.models?.['moonshot-cn/kimi-k2-0712-preview'];
|
|
expect(alias?.['supportEfforts']).toEqual(['low', 'high']);
|
|
expect(alias?.['overrides']).toEqual({ supportEfforts: ['low'] });
|
|
});
|
|
|
|
it('writes a concrete effort into config.thinking when provided', () => {
|
|
const config: ManagedKimiConfigShape = { providers: {} };
|
|
const platform = getOpenPlatformById('moonshot-cn')!;
|
|
const models = [
|
|
{
|
|
id: 'kimi-k2-0712-preview',
|
|
contextLength: 256000,
|
|
supportsReasoning: true,
|
|
supportsImageIn: false,
|
|
supportsVideoIn: false,
|
|
},
|
|
];
|
|
|
|
applyOpenPlatformConfig(config, {
|
|
platform,
|
|
models,
|
|
selectedModel: models[0]!,
|
|
thinking: true,
|
|
effort: 'high',
|
|
apiKey: 'sk-test',
|
|
});
|
|
|
|
expect(config.thinking).toEqual({ enabled: true, effort: 'high' });
|
|
});
|
|
|
|
it('omits effort for a boolean on (no concrete effort)', () => {
|
|
const config: ManagedKimiConfigShape = { providers: {} };
|
|
const platform = getOpenPlatformById('moonshot-cn')!;
|
|
const models = [
|
|
{
|
|
id: 'kimi-k2-0712-preview',
|
|
contextLength: 256000,
|
|
supportsReasoning: true,
|
|
supportsImageIn: false,
|
|
supportsVideoIn: false,
|
|
},
|
|
];
|
|
|
|
applyOpenPlatformConfig(config, {
|
|
platform,
|
|
models,
|
|
selectedModel: models[0]!,
|
|
thinking: true,
|
|
apiKey: 'sk-test',
|
|
});
|
|
|
|
expect(config.thinking).toEqual({ enabled: true });
|
|
expect(config.thinking?.effort).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('removeOpenPlatformConfig', () => {
|
|
it('removes provider, its models, and defaultModel when matched', () => {
|
|
const config: ManagedKimiConfigShape = {
|
|
providers: {
|
|
'moonshot-cn': { type: 'kimi', baseUrl: 'https://api.moonshot.cn/v1', apiKey: 'sk-test' },
|
|
'other': { type: 'kimi', baseUrl: 'https://other.test/v1', apiKey: 'sk-other' },
|
|
},
|
|
models: {
|
|
'moonshot-cn/kimi-k2': { provider: 'moonshot-cn', model: 'kimi-k2', maxContextSize: 256000 },
|
|
'other/model': { provider: 'other', model: 'other-model', maxContextSize: 1000 },
|
|
},
|
|
defaultModel: 'moonshot-cn/kimi-k2',
|
|
};
|
|
|
|
removeOpenPlatformConfig(config, 'moonshot-cn');
|
|
|
|
expect(config.providers['moonshot-cn']).toBeUndefined();
|
|
expect(config.providers['other']).toBeDefined();
|
|
expect(config.models?.['moonshot-cn/kimi-k2']).toBeUndefined();
|
|
expect(config.models?.['other/model']).toBeDefined();
|
|
expect(config.defaultModel).toBeUndefined();
|
|
});
|
|
|
|
it('leaves defaultModel intact when it belongs to another provider', () => {
|
|
const config: ManagedKimiConfigShape = {
|
|
providers: {
|
|
'moonshot-cn': { type: 'kimi', baseUrl: 'https://api.moonshot.cn/v1', apiKey: 'sk-test' },
|
|
},
|
|
models: {
|
|
'moonshot-cn/kimi-k2': { provider: 'moonshot-cn', model: 'kimi-k2', maxContextSize: 256000 },
|
|
},
|
|
defaultModel: 'other/model',
|
|
};
|
|
|
|
removeOpenPlatformConfig(config, 'moonshot-cn');
|
|
|
|
expect(config.defaultModel).toBe('other/model');
|
|
});
|
|
});
|