From aa8240e4821e7b7a250b0ceeccfd8f2fff388410 Mon Sep 17 00:00:00 2001 From: Seefs Date: Fri, 6 Feb 2026 15:22:32 +0800 Subject: [PATCH 1/2] feat: /v1/messages -> /v1/responses --- relay/channel/openai/adaptor.go | 4 +- relay/channel/openai/chat_via_responses.go | 77 +++++++++++++++++----- relay/claude_handler.go | 17 +++++ 3 files changed, 79 insertions(+), 19 deletions(-) diff --git a/relay/channel/openai/adaptor.go b/relay/channel/openai/adaptor.go index f30a6876b..b69544238 100644 --- a/relay/channel/openai/adaptor.go +++ b/relay/channel/openai/adaptor.go @@ -171,7 +171,9 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { url = strings.Replace(url, "{model}", info.UpstreamModelName, -1) return url, nil default: - if info.RelayFormat == types.RelayFormatClaude || info.RelayFormat == types.RelayFormatGemini { + if (info.RelayFormat == types.RelayFormatClaude || info.RelayFormat == types.RelayFormatGemini) && + info.RelayMode != relayconstant.RelayModeResponses && + info.RelayMode != relayconstant.RelayModeResponsesCompact { return fmt.Sprintf("%s/v1/chat/completions", info.ChannelBaseUrl), nil } return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, info.RequestURLPath, info.ChannelType), nil diff --git a/relay/channel/openai/chat_via_responses.go b/relay/channel/openai/chat_via_responses.go index d00b53907..cd2de627f 100644 --- a/relay/channel/openai/chat_via_responses.go +++ b/relay/channel/openai/chat_via_responses.go @@ -71,12 +71,22 @@ func OaiResponsesToChatHandler(c *gin.Context, info *relaycommon.RelayInfo, resp chatResp.Usage = *usage } - chatBody, err := common.Marshal(chatResp) + var responseBody []byte + switch info.RelayFormat { + case types.RelayFormatClaude: + claudeResp := service.ResponseOpenAI2Claude(chatResp, info) + responseBody, err = common.Marshal(claudeResp) + case types.RelayFormatGemini: + geminiResp := service.ResponseOpenAI2Gemini(chatResp, info) + responseBody, err = common.Marshal(geminiResp) + default: + responseBody, err = common.Marshal(chatResp) + } if err != nil { return nil, types.NewOpenAIError(err, types.ErrorCodeJsonMarshalFailed, http.StatusInternalServerError) } - service.IOCopyBytesGracefully(c, resp, chatBody) + service.IOCopyBytesGracefully(c, resp, responseBody) return usage, nil } @@ -108,12 +118,39 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo toolCallCanonicalIDByItemID := make(map[string]string) //reasoningSummaryTextByKey := make(map[string]string) + if info.RelayFormat == types.RelayFormatClaude && info.ClaudeConvertInfo == nil { + info.ClaudeConvertInfo = &relaycommon.ClaudeConvertInfo{LastMessagesType: relaycommon.LastMessageTypeNone} + } + + sendChatChunk := func(chunk *dto.ChatCompletionsStreamResponse) bool { + if chunk == nil { + return true + } + if info.RelayFormat == types.RelayFormatOpenAI { + if err := helper.ObjectData(c, chunk); err != nil { + streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError) + return false + } + return true + } + + chunkData, err := common.Marshal(chunk) + if err != nil { + streamErr = types.NewOpenAIError(err, types.ErrorCodeJsonMarshalFailed, http.StatusInternalServerError) + return false + } + if err := HandleStreamFormat(c, info, string(chunkData), false, false); err != nil { + streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError) + return false + } + return true + } + sendStartIfNeeded := func() bool { if sentStart { return true } - if err := helper.ObjectData(c, helper.GenerateStartEmptyResponse(responseId, createAt, model, nil)); err != nil { - streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError) + if !sendChatChunk(helper.GenerateStartEmptyResponse(responseId, createAt, model, nil)) { return false } sentStart = true @@ -173,8 +210,7 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo }, }, } - if err := helper.ObjectData(c, chunk); err != nil { - streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError) + if !sendChatChunk(chunk) { return false } return true @@ -231,8 +267,7 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo }, }, } - if err := helper.ObjectData(c, chunk); err != nil { - streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError) + if !sendChatChunk(chunk) { return false } sawToolCall = true @@ -323,8 +358,7 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo }, }, } - if err := helper.ObjectData(c, chunk); err != nil { - streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError) + if !sendChatChunk(chunk) { return false } } @@ -419,13 +453,15 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo return false } if !sentStop { + if info.RelayFormat == types.RelayFormatClaude && info.ClaudeConvertInfo != nil { + info.ClaudeConvertInfo.Usage = usage + } finishReason := "stop" if sawToolCall && outputText.Len() == 0 { finishReason = "tool_calls" } stop := helper.GenerateStopResponse(responseId, createAt, model, finishReason) - if err := helper.ObjectData(c, stop); err != nil { - streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError) + if !sendChatChunk(stop) { return false } sentStop = true @@ -456,26 +492,31 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo } if !sentStart { - if err := helper.ObjectData(c, helper.GenerateStartEmptyResponse(responseId, createAt, model, nil)); err != nil { - return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError) + if !sendChatChunk(helper.GenerateStartEmptyResponse(responseId, createAt, model, nil)) { + return nil, streamErr } } if !sentStop { + if info.RelayFormat == types.RelayFormatClaude && info.ClaudeConvertInfo != nil { + info.ClaudeConvertInfo.Usage = usage + } finishReason := "stop" if sawToolCall && outputText.Len() == 0 { finishReason = "tool_calls" } stop := helper.GenerateStopResponse(responseId, createAt, model, finishReason) - if err := helper.ObjectData(c, stop); err != nil { - return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError) + if !sendChatChunk(stop) { + return nil, streamErr } } - if info.ShouldIncludeUsage && usage != nil { + if info.RelayFormat == types.RelayFormatOpenAI && info.ShouldIncludeUsage && usage != nil { if err := helper.ObjectData(c, helper.GenerateFinalUsageResponse(responseId, createAt, model, *usage)); err != nil { return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError) } } - helper.Done(c) + if info.RelayFormat == types.RelayFormatOpenAI { + helper.Done(c) + } return usage, nil } diff --git a/relay/claude_handler.go b/relay/claude_handler.go index 7e05116da..9342e6bbe 100644 --- a/relay/claude_handler.go +++ b/relay/claude_handler.go @@ -98,6 +98,23 @@ func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ } } + if !model_setting.GetGlobalSettings().PassThroughRequestEnabled && + !info.ChannelSetting.PassThroughBodyEnabled && + service.ShouldChatCompletionsUseResponsesGlobal(info.ChannelId, info.ChannelType, info.OriginModelName) { + openAIRequest, convErr := service.ClaudeToOpenAIRequest(*request, info) + if convErr != nil { + return types.NewError(convErr, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) + } + + usage, newApiErr := chatCompletionsViaResponses(c, info, adaptor, openAIRequest) + if newApiErr != nil { + return newApiErr + } + + service.PostClaudeConsumeQuota(c, info, usage) + return nil + } + var requestBody io.Reader if model_setting.GetGlobalSettings().PassThroughRequestEnabled || info.ChannelSetting.PassThroughBodyEnabled { body, err := common.GetRequestBody(c) From 3af53bdd414364a50af8105a66856731417b8ea3 Mon Sep 17 00:00:00 2001 From: Seefs Date: Fri, 6 Feb 2026 16:04:49 +0800 Subject: [PATCH 2/2] fix max_output_token --- service/openaicompat/chat_to_responses.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/service/openaicompat/chat_to_responses.go b/service/openaicompat/chat_to_responses.go index 76aa6d25d..c6cdc6944 100644 --- a/service/openaicompat/chat_to_responses.go +++ b/service/openaicompat/chat_to_responses.go @@ -323,6 +323,10 @@ func ChatCompletionsRequestToResponsesRequest(req *dto.GeneralOpenAIRequest) (*d if req.MaxCompletionTokens > maxOutputTokens { maxOutputTokens = req.MaxCompletionTokens } + // OpenAI Responses API rejects max_output_tokens < 16 when explicitly provided. + if maxOutputTokens > 0 && maxOutputTokens < 16 { + maxOutputTokens = 16 + } var topP *float64 if req.TopP != 0 {