mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-10-16 14:57:05 +08:00
feat(kimi): enhance Kimi provider with Codex support and UI updates
This commit is contained in:
@@ -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 : '',
|
||||
|
||||
@@ -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: [],
|
||||
});
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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([]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user