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:
刘佳林 committed 2026-09-01 16:50:04 +08:00
1 parent a2fb09260a
commit e377c4358c
20 files changed
+280 -89

No files matched your search

+1 -1
View File
@@ -43,7 +43,7 @@ const (
const (
APIProtocolChatCompletions = "chat_completions" // OpenAI Chat Completions(默认)
APIProtocolAnthropic = "anthropic" // 原生 Anthropic /v1/messages(适配 Claude Code)
APIProtocolResponses = "responses" // OpenAI Responses(仅 deepseek,适配 Codex)
APIProtocolResponses = "responses" // OpenAI Responses(deepseek / kimi 原生端点,适配 Codex)
APIProtocolAdaptive = "adaptive" // 按入站协议优先选择供应商原生端点
)
+32 -3
View File
@@ -1387,8 +1387,8 @@ func (a *Account) IsCodingPlan() bool {
// GetAPIProtocol 返回国产供应商账号的上游 API 协议。存储于
// credentials["api_protocol"];缺失或与平台不匹配时回退 chat_completions
// (与既有行为完全一致)。responses 协议仅 deepseek 支持(官方原生 /responses
// 端点,适配 Codex);kimi/zhipu 无此端点。
// (与既有行为完全一致)。responses 协议仅 deepseek / kimi 支持(官方原生
// Responses 端点,适配 Codex);zhipu 无此端点。
func (a *Account) GetAPIProtocol() string {
if a == nil || !a.IsCNProvider() {
return APIProtocolChatCompletions
@@ -1399,7 +1399,7 @@ func (a *Account) GetAPIProtocol() string {
case APIProtocolAnthropic:
return APIProtocolAnthropic
case APIProtocolResponses:
if a.Platform == PlatformDeepseek {
if a.SupportsNativeCNResponses() {
return APIProtocolResponses
}
case APIProtocolChatCompletions:
@@ -1408,6 +1408,35 @@ func (a *Account) GetAPIProtocol() string {
return APIProtocolChatCompletions
}
// SupportsNativeCNResponses 报告该国产供应商是否提供原生 Responses 端点。
// DeepSeek 官方为 /responses(无 /v1);Kimi 按量付费与 Coding Plan 均为
// /v1/responses(moonshot.cn / kimi.com/coding)。
func (a *Account) SupportsNativeCNResponses() bool {
if a == nil {
return false
}
switch a.Platform {
case PlatformDeepseek, PlatformKimi:
return true
default:
return false
}
}
// UsesNativeCNResponses 报告当前账号是否应按原生 Responses 协议转发
// (显式 responses,或 adaptive 且平台具备原生端点)。
func (a *Account) UsesNativeCNResponses() bool {
if a == nil || !a.SupportsNativeCNResponses() {
return false
}
switch a.GetAPIProtocol() {
case APIProtocolResponses, APIProtocolAdaptive:
return true
default:
return false
}
}
// IsAdaptiveAPIProtocol 报告账号是否按入站协议动态选择供应商原生端点。
func (a *Account) IsAdaptiveAPIProtocol() bool {
return a.GetAPIProtocol() == APIProtocolAdaptive
@@ -18,8 +18,8 @@ import (
const accountTestSuppressCompletionContextKey = "account_test_suppress_completion"
// testCNProviderAdaptiveConnection verifies every native endpoint used by an
// adaptive CN-provider account. Kimi and Zhipu use Chat Completions plus
// Anthropic; DeepSeek additionally uses its native Responses endpoint.
// adaptive CN-provider account. Zhipu uses Chat Completions plus Anthropic;
// DeepSeek and Kimi additionally use their native Responses endpoints.
func (s *AccountTestService) testCNProviderAdaptiveConnection(c *gin.Context, account *Account, modelID string, prompt string) error {
testModelID := strings.TrimSpace(modelID)
if testModelID == "" {
@@ -44,7 +44,7 @@ func (s *AccountTestService) testCNProviderAdaptiveConnection(c *gin.Context, ac
return err
}
if account.Platform == PlatformDeepseek {
if account.SupportsNativeCNResponses() {
if err := s.testCNProviderAdaptiveResponsesConnection(c, account, testModelID, authToken); err != nil {
return err
}
@@ -159,8 +159,8 @@ func (s *AccountTestService) testCNProviderAdaptiveResponsesConnection(c *gin.Co
apiURL := buildOpenAIResponsesURLForPlatform(account.Platform, baseURL)
payload := createOpenAITestPayload(testModelID, false)
// DeepSeek's native Responses endpoint is stateless and does not need the
// OpenAI probe's synthetic instructions.
// DeepSeek / Kimi native Responses endpoints are stateless and do not need
// the OpenAI probe's synthetic instructions.
delete(payload, "instructions")
payloadBytes, _ := json.Marshal(payload)
payloadBytes = normalizeDeepSeekResponsesRequestBody(account, payloadBytes)
@@ -83,36 +83,25 @@ data: {"type":"response.completed"}
}
func TestAccountTestService_AdaptiveChatOnlyProvidersTestChatAndAnthropicEndpoints(t *testing.T) {
for index, testCase := range []struct {
name string
platform string
model string
}{
{name: "Kimi", platform: PlatformKimi, model: "kimi-k2.5"},
{name: "Zhipu", platform: PlatformZhipu, model: "glm-4.7"},
} {
t.Run(testCase.name, func(t *testing.T) {
account := adaptiveCNAccountTestAccount(int64(301+index), testCase.platform)
svc, upstream := adaptiveCNAccountTestService(
account,
adaptiveCNChatTestResponse(),
adaptiveCNAnthropicTestResponse(),
)
c, recorder := newTestContext()
account := adaptiveCNAccountTestAccount(301, PlatformZhipu)
svc, upstream := adaptiveCNAccountTestService(
account,
adaptiveCNChatTestResponse(),
adaptiveCNAnthropicTestResponse(),
)
c, recorder := newTestContext()
err := svc.TestAccountConnection(c, account.ID, testCase.model, "hello", AccountTestModeDefault)
err := svc.TestAccountConnection(c, account.ID, "glm-4.7", "hello", AccountTestModeDefault)
require.NoError(t, err)
require.Len(t, upstream.requests, 2)
require.Equal(t, "http://chat.example/v1/chat/completions", upstream.requests[0].URL.String())
require.Equal(t, "http://anthropic.example/v1/messages", upstream.requests[1].URL.String())
require.Equal(t, "Bearer sk-adaptive-test", upstream.requests[0].Header.Get("Authorization"))
require.Equal(t, "sk-adaptive-test", upstream.requests[1].Header.Get("x-api-key"))
require.Equal(t, 1, strings.Count(recorder.Body.String(), `"type":"test_start"`))
require.Equal(t, 1, strings.Count(recorder.Body.String(), `"type":"test_complete"`))
require.Contains(t, recorder.Body.String(), "已通过原生 /v1/messages 验证")
})
}
require.NoError(t, err)
require.Len(t, upstream.requests, 2)
require.Equal(t, "http://chat.example/v1/chat/completions", upstream.requests[0].URL.String())
require.Equal(t, "http://anthropic.example/v1/messages", upstream.requests[1].URL.String())
require.Equal(t, "Bearer sk-adaptive-test", upstream.requests[0].Header.Get("Authorization"))
require.Equal(t, "sk-adaptive-test", upstream.requests[1].Header.Get("x-api-key"))
require.Equal(t, 1, strings.Count(recorder.Body.String(), `"type":"test_start"`))
require.Equal(t, 1, strings.Count(recorder.Body.String(), `"type":"test_complete"`))
require.Contains(t, recorder.Body.String(), "已通过原生 /v1/messages 验证")
}
func TestAccountTestService_AdaptiveDeepSeekAlsoTestsResponsesEndpoint(t *testing.T) {
@@ -139,6 +128,30 @@ func TestAccountTestService_AdaptiveDeepSeekAlsoTestsResponsesEndpoint(t *testin
require.Contains(t, recorder.Body.String(), "已通过原生 /responses 验证")
}
func TestAccountTestService_AdaptiveKimiAlsoTestsResponsesEndpoint(t *testing.T) {
account := adaptiveCNAccountTestAccount(306, PlatformKimi)
svc, upstream := adaptiveCNAccountTestService(
account,
adaptiveCNChatTestResponse(),
adaptiveCNAnthropicTestResponse(),
adaptiveCNResponsesTestResponse(),
)
c, recorder := newTestContext()
err := svc.TestAccountConnection(c, account.ID, "k3-256k", "", AccountTestModeDefault)
require.NoError(t, err)
require.Len(t, upstream.requests, 3)
require.Equal(t, "http://responses.example/v1/responses", upstream.requests[2].URL.String())
require.Equal(t, HTTPUpstreamProfileOpenAI, HTTPUpstreamProfileFromContext(upstream.requests[2].Context()))
require.Equal(t, "Bearer sk-adaptive-test", upstream.requests[2].Header.Get("Authorization"))
require.True(t, gjson.GetBytes(upstream.bodies[2], "stream").Bool())
require.False(t, gjson.GetBytes(upstream.bodies[2], "store").Bool())
require.False(t, gjson.GetBytes(upstream.bodies[2], "instructions").Exists())
require.Equal(t, 1, strings.Count(recorder.Body.String(), `"type":"test_complete"`))
require.Contains(t, recorder.Body.String(), "已通过原生 /responses 验证")
}
func TestAccountTestService_AdaptiveStopsAndNamesFailingEndpoint(t *testing.T) {
account := adaptiveCNAccountTestAccount(303, PlatformDeepseek)
svc, upstream := adaptiveCNAccountTestService(
@@ -116,10 +116,10 @@ func TestAdaptiveProtocolRoutesResponsesShapedChatToNativeResponses(t *testing.T
func TestAdaptiveProtocolConvertsResponsesShapedChatForChatOnlyProvider(t *testing.T) {
gin.SetMode(gin.TestMode)
body := []byte(`{"model":"kimi-k2.5","input":"hello","max_output_tokens":32,"stream":false}`)
body := []byte(`{"model":"glm-4.7","input":"hello","max_output_tokens":32,"stream":false}`)
upstream := &httpUpstreamRecorder{err: errors.New("stop after capture")}
svc := &OpenAIGatewayService{cfg: rawChatCompletionsTestConfig(), httpUpstream: upstream}
account := adaptiveProtocolTestAccount(PlatformKimi, map[string]any{
account := adaptiveProtocolTestAccount(PlatformZhipu, map[string]any{
APIProtocolChatCompletions: "http://chat.example",
APIProtocolAnthropic: "http://anthropic.example",
})
@@ -131,6 +131,24 @@ func TestAdaptiveProtocolConvertsResponsesShapedChatForChatOnlyProvider(t *testi
require.False(t, gjson.GetBytes(upstream.lastBody, "input").Exists())
}
func TestAdaptiveProtocolRoutesKimiResponsesShapedChatToNativeResponses(t *testing.T) {
gin.SetMode(gin.TestMode)
body := []byte(`{"model":"k3-256k","input":"hello","max_output_tokens":32,"stream":false}`)
upstream := &httpUpstreamRecorder{err: errors.New("stop after capture")}
svc := &OpenAIGatewayService{cfg: rawChatCompletionsTestConfig(), httpUpstream: upstream}
account := adaptiveProtocolTestAccount(PlatformKimi, map[string]any{
APIProtocolChatCompletions: "http://chat.example",
APIProtocolAnthropic: "http://anthropic.example",
APIProtocolResponses: "http://responses.example/v1",
})
_, err := svc.ForwardAsChatCompletions(context.Background(), adaptiveProtocolTestContext("/v1/chat/completions", body), account, body, "", "")
require.Error(t, err)
require.Equal(t, "http://responses.example/v1/responses", upstream.lastReq.URL.String())
require.True(t, gjson.GetBytes(upstream.lastBody, "input").Exists())
require.False(t, gjson.GetBytes(upstream.lastBody, "messages").Exists())
}
func TestAdaptiveProtocolRoutesMessagesToNativeAnthropic(t *testing.T) {
gin.SetMode(gin.TestMode)
body := []byte(`{"model":"glm-4.7","max_tokens":32,"messages":[{"role":"user","content":"hello"}],"stream":false}`)
@@ -147,21 +165,42 @@ func TestAdaptiveProtocolRoutesMessagesToNativeAnthropic(t *testing.T) {
require.Equal(t, "glm-4.7", gjson.GetBytes(upstream.lastBody, "model").String())
}
func TestAdaptiveProtocolConvertsKimiResponsesToChatCompletions(t *testing.T) {
func TestAdaptiveProtocolRoutesKimiResponsesToNativeResponses(t *testing.T) {
gin.SetMode(gin.TestMode)
body := []byte(`{"model":"kimi-k2.5","input":"hello","stream":false}`)
body := []byte(`{"model":"k3-256k","input":"hello","store":true,"previous_response_id":"resp_old","stream":false}`)
upstream := &httpUpstreamRecorder{err: errors.New("stop after capture")}
svc := &OpenAIGatewayService{cfg: rawChatCompletionsTestConfig(), httpUpstream: upstream}
account := adaptiveProtocolTestAccount(PlatformKimi, map[string]any{
APIProtocolChatCompletions: "http://chat.example",
APIProtocolAnthropic: "http://anthropic.example",
APIProtocolResponses: "http://responses.example/v1",
})
_, err := svc.Forward(context.Background(), adaptiveProtocolTestContext("/v1/responses", body), account, body)
require.Error(t, err)
require.Equal(t, "http://chat.example/v1/chat/completions", upstream.lastReq.URL.String())
require.True(t, gjson.GetBytes(upstream.lastBody, "messages").IsArray())
require.False(t, gjson.GetBytes(upstream.lastBody, "input").Exists())
require.Equal(t, "http://responses.example/v1/responses", upstream.lastReq.URL.String())
require.True(t, gjson.GetBytes(upstream.lastBody, "input").Exists())
require.False(t, gjson.GetBytes(upstream.lastBody, "messages").Exists())
require.False(t, gjson.GetBytes(upstream.lastBody, "store").Bool())
require.False(t, gjson.GetBytes(upstream.lastBody, "previous_response_id").Exists())
}
func TestAdaptiveProtocolRoutesKimiCodingResponsesToNativeResponses(t *testing.T) {
gin.SetMode(gin.TestMode)
body := []byte(`{"model":"k3-256k","input":"hello","stream":false}`)
upstream := &httpUpstreamRecorder{err: errors.New("stop after capture")}
svc := &OpenAIGatewayService{cfg: rawChatCompletionsTestConfig(), httpUpstream: upstream}
account := adaptiveProtocolTestAccount(PlatformKimi, map[string]any{
APIProtocolChatCompletions: "https://api.kimi.com/coding/v1",
APIProtocolAnthropic: "https://api.kimi.com/coding",
APIProtocolResponses: "https://api.kimi.com/coding/v1",
})
account.Credentials["account_mode"] = AccountModeCoding
_, err := svc.Forward(context.Background(), adaptiveProtocolTestContext("/v1/responses", body), account, body)
require.Error(t, err)
require.Equal(t, "https://api.kimi.com/coding/v1/responses", upstream.lastReq.URL.String())
require.True(t, gjson.GetBytes(upstream.lastBody, "input").Exists())
}
func TestAdaptiveProtocolRoutesDeepSeekResponsesToNativeResponses(t *testing.T) {
+30 -3
View File
@@ -469,7 +469,7 @@ func TestBuildUpstreamModelsRequest_CNProviders(t *testing.T) {
}
// TestGetAPIProtocol 验证协议凭证维度的平台校验矩阵:
// responses 仅 deepseek;缺失/非法值回退 chat_completions(与旧行为一致)。
// responses 仅 deepseek / kimi;缺失/非法值回退 chat_completions(与旧行为一致)。
func TestGetAPIProtocol(t *testing.T) {
t.Parallel()
@@ -486,15 +486,24 @@ func TestGetAPIProtocol(t *testing.T) {
require.Equal(t, APIProtocolAnthropic, mk(PlatformKimi, APIProtocolAnthropic).GetAPIProtocol())
require.Equal(t, APIProtocolAnthropic, mk(PlatformDeepseek, APIProtocolAnthropic).GetAPIProtocol())
require.Equal(t, APIProtocolResponses, mk(PlatformDeepseek, APIProtocolResponses).GetAPIProtocol())
require.Equal(t, APIProtocolResponses, mk(PlatformKimi, APIProtocolResponses).GetAPIProtocol())
require.Equal(t, APIProtocolAdaptive, mk(PlatformKimi, APIProtocolAdaptive).GetAPIProtocol())
require.Equal(t, APIProtocolAdaptive, mk(PlatformZhipu, APIProtocolAdaptive).GetAPIProtocol())
require.Equal(t, APIProtocolAdaptive, mk(PlatformDeepseek, APIProtocolAdaptive).GetAPIProtocol())
require.Equal(t, APIProtocolChatCompletions, mk(PlatformKimi, APIProtocolResponses).GetAPIProtocol(), "kimi 无 responses 端点")
require.Equal(t, APIProtocolChatCompletions, mk(PlatformZhipu, APIProtocolResponses).GetAPIProtocol(), "zhipu 无 responses 端点")
require.Equal(t, APIProtocolChatCompletions, mk(PlatformKimi, "bogus").GetAPIProtocol(), "非法值回退默认")
require.Equal(t, APIProtocolChatCompletions, (&Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}).GetAPIProtocol(), "非 CN 供应商恒为默认")
}
func TestSupportsNativeCNResponses(t *testing.T) {
t.Parallel()
require.True(t, (&Account{Platform: PlatformDeepseek}).SupportsNativeCNResponses())
require.True(t, (&Account{Platform: PlatformKimi}).SupportsNativeCNResponses())
require.True(t, (&Account{Platform: PlatformKimi, Credentials: map[string]any{"account_mode": AccountModeCoding}}).SupportsNativeCNResponses())
require.False(t, (&Account{Platform: PlatformZhipu}).SupportsNativeCNResponses())
require.False(t, (&Account{Platform: PlatformOpenAI}).SupportsNativeCNResponses())
}
func TestAdaptiveProtocolBaseURLs(t *testing.T) {
t.Parallel()
@@ -639,10 +648,12 @@ func TestBuildOpenAIResponsesURLForPlatform(t *testing.T) {
require.Equal(t, "https://relay.example.com/v1/responses", buildOpenAIResponsesURLForPlatform(PlatformDeepseek, "https://relay.example.com/v1"))
require.Equal(t, "https://api.openai.com/v1/responses", buildOpenAIResponsesURLForPlatform(PlatformOpenAI, "https://api.openai.com"))
require.Equal(t, "https://open.bigmodel.cn/api/paas/v4/responses", buildOpenAIResponsesURLForPlatform(PlatformZhipu, "https://open.bigmodel.cn/api/paas/v4"))
require.Equal(t, "https://api.moonshot.cn/v1/responses", buildOpenAIResponsesURLForPlatform(PlatformKimi, "https://api.moonshot.cn/v1"))
require.Equal(t, "https://api.kimi.com/coding/v1/responses", buildOpenAIResponsesURLForPlatform(PlatformKimi, "https://api.kimi.com/coding/v1"))
}
// TestNormalizeDeepSeekResponsesRequestBody 无状态适配:强制 store=false、
// 清除 previous_response_id;非 deepseek responses 协议原样返回。
// 清除 previous_response_id;非原生 CN Responses 协议原样返回。
func TestNormalizeDeepSeekResponsesRequestBody(t *testing.T) {
t.Parallel()
@@ -668,6 +679,22 @@ func TestNormalizeDeepSeekResponsesRequestBody(t *testing.T) {
deepseekCC := &Account{Platform: PlatformDeepseek, Type: AccountTypeAPIKey}
require.Equal(t, string(body), string(normalizeDeepSeekResponsesRequestBody(deepseekCC, body)))
kimiResponses := &Account{
Platform: PlatformKimi, Type: AccountTypeAPIKey,
Credentials: map[string]any{"api_protocol": APIProtocolResponses},
}
kimiNormalized := normalizeDeepSeekResponsesRequestBody(kimiResponses, body)
require.False(t, gjson.GetBytes(kimiNormalized, "store").Bool())
require.False(t, gjson.GetBytes(kimiNormalized, "previous_response_id").Exists())
kimiCodingAdaptive := &Account{
Platform: PlatformKimi, Type: AccountTypeAPIKey,
Credentials: map[string]any{"api_protocol": APIProtocolAdaptive, "account_mode": AccountModeCoding},
}
kimiCodingNormalized := normalizeDeepSeekResponsesRequestBody(kimiCodingAdaptive, body)
require.False(t, gjson.GetBytes(kimiCodingNormalized, "store").Bool())
require.False(t, gjson.GetBytes(kimiCodingNormalized, "previous_response_id").Exists())
// openai 账号原样返回
openai := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
require.Equal(t, string(body), string(normalizeDeepSeekResponsesRequestBody(openai, body)))
@@ -123,12 +123,12 @@ func (s *AccountTestService) ProbeOpenAIAPIKeyResponsesSupport(ctx context.Conte
return
}
if account.IsCNProvider() {
// 国产 OpenAI 兼容上游(kimi/zhipu/deepseek)普遍仅支持 /v1/chat/completions,
// 不存在 /v1/responses 端点。直接落标 false 走 Chat Completions 直转,跳过网络探测。
// 例外:deepseek 的固定 responses 和 adaptive 账号使用官方原生 /responses
// 端点,落标 force_responses;其余协议显式重置为 auto,避免切换后残留强制模式。
if account.GetAPIProtocol() == APIProtocolResponses ||
(account.Platform == PlatformDeepseek && account.IsAdaptiveAPIProtocol()) {
// 国产 OpenAI 兼容上游默认仅支持 /v1/chat/completions。直接落标 false
// 走 Chat Completions 直转,跳过网络探测。
// 例外:deepseek / kimi 的固定 responses 和 adaptive 账号使用官方原生
// Responses 端点,落标 force_responses;其余协议显式重置为 auto,避免
// 切换后残留强制模式。
if account.UsesNativeCNResponses() {
_ = s.accountRepo.UpdateExtra(ctx, account.ID, map[string]any{
openai_compat.ExtraKeyResponsesMode: string(openai_compat.ResponsesSupportModeForceResponses),
openai_compat.ExtraKeyResponsesSupported: true,
@@ -60,7 +60,8 @@ func TestProbeOpenAIAPIKeyResponsesSupportCNProviders(t *testing.T) {
}{
{name: "deepseek adaptive supports responses", id: 201, platform: PlatformDeepseek, protocol: APIProtocolAdaptive, wantSupport: true, wantMode: string(openai_compat.ResponsesSupportModeForceResponses)},
{name: "deepseek chat clears forced responses", id: 202, platform: PlatformDeepseek, protocol: APIProtocolChatCompletions, wantSupport: false, wantMode: string(openai_compat.ResponsesSupportModeAuto)},
{name: "kimi adaptive falls back to chat", id: 203, platform: PlatformKimi, protocol: APIProtocolAdaptive, wantSupport: false, wantMode: string(openai_compat.ResponsesSupportModeAuto)},
{name: "kimi adaptive supports responses", id: 203, platform: PlatformKimi, protocol: APIProtocolAdaptive, wantSupport: true, wantMode: string(openai_compat.ResponsesSupportModeForceResponses)},
{name: "kimi responses protocol supports responses", id: 205, platform: PlatformKimi, protocol: APIProtocolResponses, wantSupport: true, wantMode: string(openai_compat.ResponsesSupportModeForceResponses)},
{name: "zhipu adaptive falls back to chat", id: 204, platform: PlatformZhipu, protocol: APIProtocolAdaptive, wantSupport: false, wantMode: string(openai_compat.ResponsesSupportModeAuto)},
}
@@ -102,13 +102,13 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
isResponsesShape := !gjson.GetBytes(body, "messages").Exists() && gjson.GetBytes(body, "input").Exists()
// 自适应账号的标准 Chat Completions 入站使用供应商原生 CC 端点。
// Responses 形状下,DeepSeek 继续走下方原生 Responses 链;Kimi/GLM
// Responses 形状下,DeepSeek / Kimi 继续走下方原生 Responses 链;GLM
// 没有 Responses 端点,先转换成 Chat Completions 再直转。
if account.IsAdaptiveAPIProtocol() {
if !isResponsesShape {
return s.forwardAsRawChatCompletions(ctx, c, account, body, defaultMappedModel)
}
if account.Platform != PlatformDeepseek {
if !account.SupportsNativeCNResponses() {
var responsesReq apicompat.ResponsesRequest
if err := json.Unmarshal(body, &responsesReq); err != nil {
return nil, fmt.Errorf("parse responses-shaped chat completions request: %w", err)
@@ -126,7 +126,7 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
}
return s.forwardAsRawChatCompletions(ctx, c, account, chatBody, defaultMappedModel)
}
// DeepSeek 原生 Responses 请求继续走下方 Responses→Chat 回程转换。
// DeepSeek / Kimi 原生 Responses 请求继续走下方 Responses→Chat 回程转换。
}
// 入口分流(国产供应商 Anthropic 协议):上游为供应商原生 Anthropic 端点,
@@ -39,7 +39,8 @@ var openaiCCRawAllowedHeaders = map[string]bool{
// `{base_url}/v1/chat/completions`,**不**做 CC↔Responses 协议转换。
//
// 适用场景:account.platform=openai && account.type=apikey && 上游已被探测确认
// 不支持 /v1/responses 端点(如 DeepSeek/Kimi/GLM/Qwen 等第三方 OpenAI 兼容上游)。
// 不支持 /v1/responses 端点(如 GLM/Qwen 等第三方 OpenAI 兼容上游);CN 供应商
// 固定 chat_completions 协议也走此路径。
//
// 与 ForwardAsChatCompletions 的关键差异:
//
@@ -133,8 +133,8 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
}
}
nativeDeepSeekResponses := account.Platform == PlatformDeepseek &&
(account.GetAPIProtocol() == APIProtocolResponses || account.IsAdaptiveAPIProtocol())
nativeCNResponses := account.UsesNativeCNResponses()
nativeDeepSeekResponses := account.Platform == PlatformDeepseek && nativeCNResponses
if nativeDeepSeekResponses && account.Type == AccountTypeAPIKey && !compactPath &&
needsOpenAIResponsesClientToolAdaptation(body) {
adaptedBody, mapping, adaptErr := adaptOpenAIResponsesClientTools(body)
@@ -360,7 +360,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
instructions := gjson.GetBytes(body, "instructions")
instructionsEmpty := !instructions.Exists() || instructions.Type != gjson.String || strings.TrimSpace(instructions.String()) == ""
if instructionsEmpty && account.UsesOpenAICodexProtocol() && !compatMessagesBridge && !nativeDeepSeekResponses {
if instructionsEmpty && account.UsesOpenAICodexProtocol() && !compatMessagesBridge && !nativeCNResponses {
markPatchSet("instructions", defaultCodexSynthInstructions(reqModel))
}
@@ -1268,13 +1268,13 @@ func shouldForwardOpenAIResponsesViaRawChatCompletions(account *Account) bool {
return false
}
if account.IsCNProvider() {
// CN 的显式协议配置优先于异步探针 Extra;adaptive 仅 DeepSeek 有原生
// Responses,Kimi/GLM 回退 Chat Completions。
// CN 的显式协议配置优先于异步探针 Extra;adaptive 仅 DeepSeek / Kimi
// 有原生 Responses,GLM 回退 Chat Completions。
switch account.GetAPIProtocol() {
case APIProtocolChatCompletions:
return true
case APIProtocolAdaptive:
return account.Platform != PlatformDeepseek
return !account.SupportsNativeCNResponses()
default:
return false
}
@@ -1298,7 +1298,7 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin.
case AccountTypeAPIKey:
// API Key accounts use Platform API or custom base URL
baseURL := account.GetOpenAIBaseURL()
if account.Platform == PlatformDeepseek && account.IsAdaptiveAPIProtocol() {
if account.UsesNativeCNResponses() && account.IsAdaptiveAPIProtocol() {
baseURL = account.GetCNProtocolBaseURL(APIProtocolResponses)
}
if baseURL == "" {
@@ -1315,7 +1315,7 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin.
}
targetURL = appendOpenAIResponsesRequestPathSuffix(targetURL, openAIResponsesRequestPathSuffix(c))
// DeepSeek 原生 Responses 端点为无状态实现:强制 store=false、清除
// DeepSeek / Kimi 原生 Responses 端点为无状态实现:强制 store=false、清除
// previous_response_id,避免携带状态字段被上游拒绝。
body = normalizeDeepSeekResponsesRequestBody(account, body)
@@ -591,6 +591,9 @@ func (s *OpenAIGatewayService) buildUpstreamRequestOpenAIPassthrough(
}
case AccountTypeAPIKey:
baseURL := account.GetOpenAIBaseURL()
if account.UsesNativeCNResponses() && account.IsAdaptiveAPIProtocol() {
baseURL = account.GetCNProtocolBaseURL(APIProtocolResponses)
}
if baseURL != "" {
validatedURL, err := s.validateUpstreamBaseURL(baseURL)
if err != nil {
@@ -601,7 +604,7 @@ func (s *OpenAIGatewayService) buildUpstreamRequestOpenAIPassthrough(
}
targetURL = appendOpenAIResponsesRequestPathSuffix(targetURL, openAIResponsesRequestPathSuffix(c))
// DeepSeek 原生 Responses 端点为无状态实现(见 normalizeDeepSeekResponsesRequestBody)。
// DeepSeek / Kimi 原生 Responses 端点为无状态实现(见 normalizeDeepSeekResponsesRequestBody)。
body = normalizeDeepSeekResponsesRequestBody(account, body)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, targetURL, bytes.NewReader(body))
@@ -118,12 +118,12 @@ func deleteOpenAIResponsesNoneReasoningEffortFromObject(account *Account, body m
}
}
// normalizeDeepSeekResponsesRequestBody 适配 DeepSeek 无状态 Responses 端点:
// 强制 store=false 并清除 previous_response_id(官方 /responses 不支持服务端
// 状态存储,携带这些字段会被拒绝)。非 deepseek responses 协议账号原样返回。
// normalizeDeepSeekResponsesRequestBody 适配无状态 CN Responses 端点:
// 强制 store=false 并清除 previous_response_id(DeepSeek / Kimi 官方
// Responses 均不支持服务端状态存储,携带这些字段会被拒绝)。
// 非原生 Responses 协议账号原样返回。
func normalizeDeepSeekResponsesRequestBody(account *Account, body []byte) []byte {
if account == nil || account.Platform != PlatformDeepseek ||
(account.GetAPIProtocol() != APIProtocolResponses && !account.IsAdaptiveAPIProtocol()) {
if account == nil || !account.UsesNativeCNResponses() {
return body
}
normalized, err := sjson.SetBytes(body, "store", false)
@@ -41,6 +41,9 @@ func (s *OpenAIGatewayService) buildOpenAIResponsesWSURL(account *Account) (stri
}
case AccountTypeAPIKey:
baseURL := account.GetOpenAIBaseURL()
if account.UsesNativeCNResponses() && account.IsAdaptiveAPIProtocol() {
baseURL = account.GetCNProtocolBaseURL(APIProtocolResponses)
}
if baseURL == "" {
targetURL = openaiPlatformAPIURL
} else {