feat(kimi): enhance Kimi provider with Codex support and UI updates

This commit is contained in:
Supra4E8C
2026-09-02 00:00:18 +08:00
parent 49d51030cf
commit af4eb92eb7
6 changed files with 71 additions and 17 deletions
@@ -69,6 +69,18 @@
}
}
.itemKimi {
&.active {
border-color: color-mix(in srgb, #1783ff 30%, transparent);
background: color-mix(in srgb, #1783ff 10%, var(--bg-primary));
color: color-mix(in srgb, #1783ff 82%, var(--text-primary));
.itemSubtitle {
color: color-mix(in srgb, #1783ff 82%, var(--text-primary));
}
}
}
.itemLeft {
display: flex;
align-items: center;
@@ -38,7 +38,13 @@ export function ProviderCategoryList({ groups, activeBrand, onSelect }: Provider
const total = group.resources.length;
const activeCount = group.resources.filter((r) => !r.disabled).length;
const logo = PROVIDER_LOGOS[group.id];
const itemClass = `${styles.item} ${active ? styles.active : ''}`;
const itemClass = [
styles.item,
active ? styles.active : '',
group.id === 'kimi' ? styles.itemKimi : '',
]
.filter(Boolean)
.join(' ');
const logoClassName = [
styles.logo,
logo?.transparent ? styles.logoTransparent : '',
+16 -5
View File
@@ -18,7 +18,7 @@ export const KIMI_BASE_URL_OPTIONS = [
descriptionKey: 'domestic',
baseUrl: KIMI_DOMESTIC_OPENAI_BASE_URL,
openaiBaseUrl: KIMI_DOMESTIC_OPENAI_BASE_URL,
codexBaseUrl: '',
codexBaseUrl: KIMI_DOMESTIC_OPENAI_BASE_URL,
anthropicBaseUrl: KIMI_DOMESTIC_ANTHROPIC_BASE_URL,
geminiBaseUrl: '',
},
@@ -27,13 +27,13 @@ export const KIMI_BASE_URL_OPTIONS = [
descriptionKey: 'overseas',
baseUrl: KIMI_OPENAI_BASE_URL,
openaiBaseUrl: KIMI_OPENAI_BASE_URL,
codexBaseUrl: '',
codexBaseUrl: KIMI_OPENAI_BASE_URL,
anthropicBaseUrl: KIMI_ANTHROPIC_BASE_URL,
geminiBaseUrl: '',
},
] as const;
export const KIMI_PROTOCOL_LABELS = ['openai', 'anthropic'] as const;
export const KIMI_PROTOCOL_LABELS = ['openai', 'anthropic', 'codexResponses'] as const;
export const getKimiAffiliateUrl = (language: string | undefined | null): string =>
language?.toLowerCase().startsWith('zh')
@@ -54,6 +54,7 @@ export const resolveKimiBaseUrl = (value: string | undefined | null): string =>
(option) =>
normalized === normalizeBaseUrl(option.baseUrl) ||
normalized === normalizeBaseUrl(option.openaiBaseUrl) ||
normalized === normalizeBaseUrl(option.codexBaseUrl) ||
normalized === normalizeBaseUrl(option.anthropicBaseUrl)
);
if (matched) return matched.baseUrl;
@@ -72,7 +73,7 @@ export const getKimiProtocolUrls = (value: string | undefined | null) => {
return {
anthropic: matched.anthropicBaseUrl,
openai: matched.openaiBaseUrl,
codex: '',
codex: matched.codexBaseUrl,
gemini: '',
};
};
@@ -95,6 +96,14 @@ export const isKimiClaudeProvider = (config: ProviderKeyConfig | undefined | nul
);
};
export const isKimiCodexProvider = (config: ProviderKeyConfig | undefined | null): boolean => {
if (!config) return false;
const baseUrl = normalizeBaseUrl(config.baseUrl);
return KIMI_BASE_URL_OPTIONS.some(
(option) => baseUrl === normalizeBaseUrl(option.codexBaseUrl)
);
};
export const buildKimiRaw = (config: Config | null | undefined): SponsorProviderRaw => ({
openai: (config?.openaiCompatibility ?? [])
.map((item, index) => ({ config: item, index: item.sourceIndex ?? index }))
@@ -102,6 +111,8 @@ export const buildKimiRaw = (config: Config | null | undefined): SponsorProvider
claude: (config?.claudeApiKeys ?? [])
.map((item, index) => ({ config: item, index }))
.filter((item) => isKimiClaudeProvider(item.config)),
codex: [],
codex: (config?.codexApiKeys ?? [])
.map((item, index) => ({ config: item, index }))
.filter((item) => isKimiCodexProvider(item.config)),
gemini: [],
});
+1 -1
View File
@@ -184,7 +184,7 @@ const SPONSOR_DEFINITIONS: Record<SponsorProviderBrand, SponsorProviderDefinitio
brand: 'kimi',
displayName: KIMI_DISPLAY_NAME,
providerName: KIMI_PROVIDER_NAME,
protocols: ['openai', 'claude'],
protocols: ['openai', 'claude', 'codex'],
protocolLabels: KIMI_PROTOCOL_LABELS,
defaultProtocol: 'openai',
baseUrlOptions: KIMI_BASE_URL_OPTIONS,
@@ -73,7 +73,12 @@ import {
isInfistarGeminiProvider,
isInfistarOpenAIProvider,
} from './infistar';
import { buildKimiRaw, isKimiClaudeProvider, isKimiOpenAIProvider } from './kimi';
import {
buildKimiRaw,
isKimiClaudeProvider,
isKimiCodexProvider,
isKimiOpenAIProvider,
} from './kimi';
import {
getSponsorProviderDefinition,
isTemporarilyHiddenSponsorBrand,
@@ -487,7 +492,8 @@ export function useProviderWorkbench(): UseProviderWorkbenchResult {
(fennoAIHidden || !isFennoAICodexProvider(item)) &&
(qiniuCloudHidden || !isQiniuCloudCodexProvider(item)) &&
!isLmuAICodexProvider(item) &&
!isInfistarCodexProvider(item)
!isInfistarCodexProvider(item) &&
!isKimiCodexProvider(item)
) {
out.push(codexToResource(item, index));
}
+27 -8
View File
@@ -1,5 +1,8 @@
import { afterEach, describe, expect, test } from 'bun:test';
import { buildOpenAIChatCompletionsEndpoint } from '../src/components/providers/utils';
import {
buildCodexResponsesEndpoint,
buildOpenAIChatCompletionsEndpoint,
} from '../src/components/providers/utils';
import {
KIMI_ANTHROPIC_BASE_URL,
KIMI_CHINESE_AFFILIATE_URL,
@@ -13,6 +16,7 @@ import {
getKimiAffiliateUrl,
getKimiProtocolUrls,
isKimiClaudeProvider,
isKimiCodexProvider,
isKimiOpenAIProvider,
resolveKimiBaseUrl,
} from '../src/features/providers/kimi';
@@ -29,23 +33,30 @@ afterEach(() => {
});
describe('Kimi provider', () => {
test('defaults to the domestic OpenAI-compatible and Claude protocol endpoints', () => {
test('defaults to the domestic OpenAI-compatible, Claude, and Responses endpoints', () => {
expect(getKimiProtocolUrls(undefined)).toEqual({
openai: 'https://api.moonshot.cn/v1',
anthropic: 'https://api.moonshot.cn/anthropic',
codex: '',
codex: 'https://api.moonshot.cn/v1',
gemini: '',
});
expect(getKimiProtocolUrls(KIMI_OPENAI_BASE_URL)).toEqual({
openai: 'https://api.moonshot.ai/v1',
anthropic: 'https://api.moonshot.ai/anthropic',
codex: '',
codex: 'https://api.moonshot.ai/v1',
gemini: '',
});
expect(buildOpenAIChatCompletionsEndpoint(KIMI_OPENAI_BASE_URL)).toBe(
'https://api.moonshot.ai/v1/chat/completions'
);
expect(getSponsorProviderDefinition('kimi').protocols).toEqual(['openai', 'claude']);
expect(buildCodexResponsesEndpoint(KIMI_OPENAI_BASE_URL)).toBe(
'https://api.moonshot.ai/v1/responses'
);
expect(getSponsorProviderDefinition('kimi').protocols).toEqual([
'openai',
'claude',
'codex',
]);
});
test('offers overseas and domestic URLs and maps the domestic protocol endpoints', () => {
@@ -67,7 +78,7 @@ describe('Kimi provider', () => {
expect(getKimiProtocolUrls(KIMI_DOMESTIC_OPENAI_BASE_URL)).toEqual({
openai: 'https://api.moonshot.cn/v1',
anthropic: 'https://api.moonshot.cn/anthropic',
codex: '',
codex: 'https://api.moonshot.cn/v1',
gemini: '',
});
});
@@ -130,9 +141,13 @@ describe('Kimi provider', () => {
expect(
isKimiClaudeProvider({ apiKey: 'sk-test', baseUrl: KIMI_DOMESTIC_ANTHROPIC_BASE_URL })
).toBeTrue();
expect(isKimiCodexProvider({ apiKey: 'sk-test', baseUrl: KIMI_OPENAI_BASE_URL })).toBeTrue();
expect(
isKimiCodexProvider({ apiKey: 'sk-test', baseUrl: KIMI_DOMESTIC_OPENAI_BASE_URL })
).toBeTrue();
});
test('aggregates only the Kimi OpenAI-compatible and Claude configs', () => {
test('aggregates only the Kimi OpenAI-compatible, Claude, and Responses configs', () => {
const raw = buildKimiRaw({
openaiCompatibility: [
{ name: 'kimi', baseUrl: KIMI_OPENAI_BASE_URL },
@@ -142,11 +157,15 @@ describe('Kimi provider', () => {
{ apiKey: 'kimi-key', baseUrl: KIMI_ANTHROPIC_BASE_URL },
{ apiKey: 'other-key', baseUrl: 'https://api.anthropic.com' },
],
codexApiKeys: [
{ apiKey: 'kimi-response-key', baseUrl: KIMI_OPENAI_BASE_URL },
{ apiKey: 'other-response-key', baseUrl: 'https://example.com/v1' },
],
});
expect(raw.openai.map((item) => item.index)).toEqual([0]);
expect(raw.claude.map((item) => item.index)).toEqual([0]);
expect(raw.codex).toEqual([]);
expect(raw.codex.map((item) => item.index)).toEqual([0]);
expect(raw.gemini).toEqual([]);
});
});