mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-28 16:09:34 +00:00
feat(kimi): forward native OpenAI Responses API
Moonshot pay-as-you-go and Kimi For Coding both expose native /v1/responses. Stop converting Kimi Responses traffic to Chat Completions, and treat Kimi like DeepSeek for protocol selection while keeping /v1/responses URL construction.
This commit is contained in:
@@ -1319,7 +1319,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="form.platform !== 'deepseek'" class="input-hint">
|
||||
<p v-if="!cnSupportsNativeResponses(form.platform)" class="input-hint">
|
||||
{{ t('admin.accounts.cnProviders.apiProtocol.responsesFallbackDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -3814,6 +3814,7 @@ import {
|
||||
applyAntigravityProjectID,
|
||||
applyHeaderOverride,
|
||||
applyInterceptWarmup,
|
||||
cnSupportsNativeResponses,
|
||||
defaultCNAdaptiveBaseUrls,
|
||||
defaultCNBaseUrl,
|
||||
isHeaderOverrideCapable,
|
||||
@@ -4006,7 +4007,7 @@ const upstreamBillingAutoProbeEnabled = ref(true)
|
||||
// ── 国产供应商(Kimi / Zhipu / DeepSeek)账号类型、API 协议与端点 ──
|
||||
const accountMode = ref<CnAccountMode>('payg')
|
||||
// API 协议决定转发端点与格式:cc=现有转换链,anthropic=原生直通(Claude Code),
|
||||
// responses=deepseek 原生 Responses 端点(Codex)。与账号类型正交。
|
||||
// responses=deepseek / kimi 原生 Responses 端点(Codex)。与账号类型正交。
|
||||
const apiProtocol = ref<CnApiProtocol>('adaptive')
|
||||
// 智谱团队版 Coding Plan:组织/项目 ID,写入 credentials 供额度探测切换团队端点
|
||||
const zhipuOrganization = ref('')
|
||||
@@ -4027,14 +4028,14 @@ const cnPresetPlatform = computed<'kimi' | 'zhipu' | 'deepseek'>(() => {
|
||||
}
|
||||
return 'kimi'
|
||||
})
|
||||
// 当前平台可选的协议档(responses 仅 deepseek)。
|
||||
// 当前平台可选的协议档(responses 仅 deepseek / kimi)。
|
||||
const cnProtocolOptions = computed<Array<{ value: CnApiProtocol; labelKey: string }>>(() => {
|
||||
const opts: Array<{ value: CnApiProtocol; labelKey: string }> = [
|
||||
{ value: 'adaptive', labelKey: 'adaptive' },
|
||||
{ value: 'chat_completions', labelKey: 'chatCompletions' },
|
||||
{ value: 'anthropic', labelKey: 'anthropic' }
|
||||
]
|
||||
if (form.platform === 'deepseek') {
|
||||
if (cnSupportsNativeResponses(form.platform)) {
|
||||
opts.push({ value: 'responses', labelKey: 'responses' })
|
||||
}
|
||||
return opts
|
||||
@@ -4044,7 +4045,7 @@ const cnAdaptiveProtocolOptions = computed<Array<{ value: CnNativeApiProtocol; l
|
||||
{ value: 'chat_completions', labelKey: 'chatCompletions' },
|
||||
{ value: 'anthropic', labelKey: 'anthropic' }
|
||||
]
|
||||
if (form.platform === 'deepseek') opts.push({ value: 'responses', labelKey: 'responses' })
|
||||
if (cnSupportsNativeResponses(form.platform)) opts.push({ value: 'responses', labelKey: 'responses' })
|
||||
return opts
|
||||
})
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
<input v-model="editAdaptiveBaseUrls[item.value]" type="text" class="input" />
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="account.platform !== 'deepseek'" class="input-hint">
|
||||
<p v-if="!cnSupportsNativeResponses(account.platform)" class="input-hint">
|
||||
{{ t('admin.accounts.cnProviders.apiProtocol.responsesFallbackDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -2909,6 +2909,7 @@ import {
|
||||
isHeaderOverrideCapable,
|
||||
splitHeaderOverridesObject,
|
||||
validateHeaderOverrideRows,
|
||||
cnSupportsNativeResponses,
|
||||
defaultCNAdaptiveBaseUrls,
|
||||
defaultCNBaseUrl,
|
||||
HEADER_OVERRIDE_ENABLED_CREDENTIAL_KEY,
|
||||
@@ -3057,7 +3058,7 @@ const cnProtocolOptions = computed<Array<{ value: CnApiProtocol; labelKey: strin
|
||||
{ value: 'chat_completions', labelKey: 'chatCompletions' },
|
||||
{ value: 'anthropic', labelKey: 'anthropic' }
|
||||
]
|
||||
if (props.account?.platform === 'deepseek') {
|
||||
if (cnSupportsNativeResponses(props.account?.platform ?? '')) {
|
||||
opts.push({ value: 'responses', labelKey: 'responses' })
|
||||
}
|
||||
return opts
|
||||
@@ -3067,7 +3068,7 @@ const editAdaptiveProtocolOptions = computed<Array<{ value: CnNativeApiProtocol;
|
||||
{ value: 'chat_completions', labelKey: 'chatCompletions' },
|
||||
{ value: 'anthropic', labelKey: 'anthropic' }
|
||||
]
|
||||
if (props.account?.platform === 'deepseek') opts.push({ value: 'responses', labelKey: 'responses' })
|
||||
if (cnSupportsNativeResponses(props.account?.platform ?? '')) opts.push({ value: 'responses', labelKey: 'responses' })
|
||||
return opts
|
||||
})
|
||||
watch(editApiProtocol, (protocol, previousProtocol) => {
|
||||
@@ -3947,7 +3948,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
storedProtocol === 'responses'
|
||||
? storedProtocol
|
||||
: 'chat_completions'
|
||||
if (newAccount.platform !== 'deepseek' && editApiProtocol.value === 'responses') {
|
||||
if (!cnSupportsNativeResponses(newAccount.platform) && editApiProtocol.value === 'responses') {
|
||||
editApiProtocol.value = 'chat_completions'
|
||||
}
|
||||
const adaptiveDefaults = defaultCNAdaptiveBaseUrls(newAccount.platform, editAccountMode.value)
|
||||
|
||||
@@ -375,7 +375,31 @@ describe('CreateAccountModal OpenAI long-context billing', () => {
|
||||
base_url: 'https://api.moonshot.cn/v1',
|
||||
api_base_urls: {
|
||||
chat_completions: 'https://api.moonshot.cn/v1',
|
||||
anthropic: 'https://api.moonshot.cn/anthropic'
|
||||
anthropic: 'https://api.moonshot.cn/anthropic',
|
||||
responses: 'https://api.moonshot.cn/v1'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('submits adaptive Kimi Coding Plan Responses endpoint', async () => {
|
||||
const wrapper = mountModal()
|
||||
await selectButtonByText(wrapper, 'Kimi')
|
||||
await selectButtonByText(wrapper, 'admin.accounts.cnProviders.accountMode.coding')
|
||||
await wrapper.get('form#create-account-form input[type="text"]').setValue('Kimi coding')
|
||||
await wrapper.get('form#create-account-form input[type="password"]').setValue('sk-kimi-coding')
|
||||
|
||||
await wrapper.get('form#create-account-form').trigger('submit.prevent')
|
||||
await flushPromises()
|
||||
|
||||
expect(createAccountMock).toHaveBeenCalledTimes(1)
|
||||
expect(createAccountMock.mock.calls[0]?.[0]?.credentials).toMatchObject({
|
||||
account_mode: 'coding',
|
||||
api_protocol: 'adaptive',
|
||||
base_url: 'https://api.kimi.com/coding/v1',
|
||||
api_base_urls: {
|
||||
chat_completions: 'https://api.kimi.com/coding/v1',
|
||||
anthropic: 'https://api.kimi.com/coding',
|
||||
responses: 'https://api.kimi.com/coding/v1'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -355,6 +355,39 @@ describe('EditAccountModal', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves adaptive Kimi Responses endpoint on submit', async () => {
|
||||
const account = buildAccount()
|
||||
account.platform = 'kimi'
|
||||
account.credentials = {
|
||||
api_key: 'sk-kimi',
|
||||
account_mode: 'payg',
|
||||
api_protocol: 'adaptive',
|
||||
base_url: 'https://api.moonshot.cn/v1',
|
||||
api_base_urls: {
|
||||
chat_completions: 'https://api.moonshot.cn/v1',
|
||||
anthropic: 'https://api.moonshot.cn/anthropic',
|
||||
responses: 'https://api.moonshot.cn/v1'
|
||||
}
|
||||
}
|
||||
updateAccountMock.mockReset().mockResolvedValue(account)
|
||||
checkMixedChannelRiskMock.mockReset().mockResolvedValue({ has_risk: false })
|
||||
|
||||
const wrapper = mountModal(account)
|
||||
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
|
||||
|
||||
expect(updateAccountMock).toHaveBeenCalledTimes(1)
|
||||
expect(updateAccountMock.mock.calls[0]?.[1]?.credentials).toMatchObject({
|
||||
account_mode: 'payg',
|
||||
api_protocol: 'adaptive',
|
||||
base_url: 'https://api.moonshot.cn/v1',
|
||||
api_base_urls: {
|
||||
chat_completions: 'https://api.moonshot.cn/v1',
|
||||
anthropic: 'https://api.moonshot.cn/anthropic',
|
||||
responses: 'https://api.moonshot.cn/v1'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves adaptive GLM endpoints on submit', async () => {
|
||||
const account = buildAccount()
|
||||
account.platform = 'zhipu'
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { defaultCNAdaptiveBaseUrls } from '../credentialsBuilder'
|
||||
import { cnSupportsNativeResponses, defaultCNAdaptiveBaseUrls } from '../credentialsBuilder'
|
||||
|
||||
describe('cnSupportsNativeResponses', () => {
|
||||
it('is true for DeepSeek and Kimi only', () => {
|
||||
expect(cnSupportsNativeResponses('deepseek')).toBe(true)
|
||||
expect(cnSupportsNativeResponses('kimi')).toBe(true)
|
||||
expect(cnSupportsNativeResponses('zhipu')).toBe(false)
|
||||
expect(cnSupportsNativeResponses('openai')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('defaultCNAdaptiveBaseUrls', () => {
|
||||
it('resolves Kimi endpoints by account mode', () => {
|
||||
expect(defaultCNAdaptiveBaseUrls('kimi', 'payg')).toEqual({
|
||||
chat_completions: 'https://api.moonshot.cn/v1',
|
||||
anthropic: 'https://api.moonshot.cn/anthropic',
|
||||
responses: ''
|
||||
responses: 'https://api.moonshot.cn/v1'
|
||||
})
|
||||
expect(defaultCNAdaptiveBaseUrls('kimi', 'coding')).toEqual({
|
||||
chat_completions: 'https://api.kimi.com/coding/v1',
|
||||
anthropic: 'https://api.kimi.com/coding',
|
||||
responses: ''
|
||||
responses: 'https://api.kimi.com/coding/v1'
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -256,10 +256,15 @@ export const GROK_BASE_URL_PRESETS: GrokBaseUrlPreset[] = [
|
||||
|
||||
export type CnAccountMode = 'payg' | 'coding'
|
||||
|
||||
/** 仅 deepseek 支持原生 responses;adaptive 会按入站协议选择原生端点。 */
|
||||
/** deepseek / kimi 支持原生 responses;adaptive 会按入站协议选择原生端点。 */
|
||||
export type CnApiProtocol = 'adaptive' | 'chat_completions' | 'anthropic' | 'responses'
|
||||
export type CnNativeApiProtocol = Exclude<CnApiProtocol, 'adaptive'>
|
||||
|
||||
/** DeepSeek 与 Kimi(按量付费 / Coding Plan)提供原生 Responses 端点。 */
|
||||
export function cnSupportsNativeResponses(platform: string): boolean {
|
||||
return platform === 'deepseek' || platform === 'kimi'
|
||||
}
|
||||
|
||||
export interface CnBaseUrlPreset {
|
||||
mode: CnAccountMode
|
||||
protocol: CnApiProtocol
|
||||
@@ -273,8 +278,10 @@ export const CN_BASE_URL_PRESETS: Record<'kimi' | 'zhipu' | 'deepseek', CnBaseUr
|
||||
kimi: [
|
||||
{ mode: 'payg', protocol: 'chat_completions', label: 'Moonshot', url: 'https://api.moonshot.cn/v1' },
|
||||
{ mode: 'payg', protocol: 'anthropic', label: 'Moonshot Anthropic', url: 'https://api.moonshot.cn/anthropic' },
|
||||
{ mode: 'payg', protocol: 'responses', label: 'Moonshot Responses', url: 'https://api.moonshot.cn/v1' },
|
||||
{ mode: 'coding', protocol: 'chat_completions', label: 'Kimi For Coding', url: 'https://api.kimi.com/coding/v1' },
|
||||
{ mode: 'coding', protocol: 'anthropic', label: 'Kimi Coding Anthropic', url: 'https://api.kimi.com/coding' }
|
||||
{ mode: 'coding', protocol: 'anthropic', label: 'Kimi Coding Anthropic', url: 'https://api.kimi.com/coding' },
|
||||
{ mode: 'coding', protocol: 'responses', label: 'Kimi Coding Responses', url: 'https://api.kimi.com/coding/v1' }
|
||||
],
|
||||
zhipu: [
|
||||
{ mode: 'payg', protocol: 'chat_completions', label: 'GLM PaaS', url: 'https://open.bigmodel.cn/api/paas/v4' },
|
||||
@@ -307,7 +314,7 @@ export function defaultCNBaseUrl(
|
||||
return ''
|
||||
}
|
||||
}
|
||||
// responses 仅 deepseek:base 与 chat_completions 相同(端点路径差异由后端处理)。
|
||||
// responses:Kimi / DeepSeek 的 base 与 chat_completions 相同(端点路径差异由后端处理)。
|
||||
switch (platform) {
|
||||
case 'kimi':
|
||||
return mode === 'coding' ? 'https://api.kimi.com/coding/v1' : 'https://api.moonshot.cn/v1'
|
||||
@@ -330,7 +337,7 @@ export function defaultCNAdaptiveBaseUrls(
|
||||
return {
|
||||
chat_completions: defaultCNBaseUrl(platform, mode, 'chat_completions'),
|
||||
anthropic: defaultCNBaseUrl(platform, mode, 'anthropic'),
|
||||
responses: platform === 'deepseek' ? defaultCNBaseUrl(platform, mode, 'responses') : ''
|
||||
responses: cnSupportsNativeResponses(platform) ? defaultCNBaseUrl(platform, mode, 'responses') : ''
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user