feat: add Responses API compatibility

This commit is contained in:
2026-07-10 23:33:15 +08:00
parent b7351f3b9b
commit 8847d973a8
24 changed files with 5922 additions and 169 deletions
+73 -6
View File
@@ -20,20 +20,43 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
if apiKey == "" {
return Response{}, &ClientError{Code: "missing_credentials", Message: "openai api key is required", Retryable: false}
}
endpoint := openAIEndpoint(request.Kind)
protocol := request.UpstreamProtocol
if protocol == "" && request.Kind == "responses" {
protocol = ProtocolOpenAIResponses
}
endpointKind := request.Kind
if request.Kind == "responses" && protocol == ProtocolOpenAIChatCompletions {
endpointKind = "chat.completions"
}
endpoint := openAIEndpoint(endpointKind)
if endpoint == "" {
return Response{}, &ClientError{Code: "unsupported_kind", Message: "unsupported openai request kind", Retryable: false}
}
body := cloneBody(request.Body)
if request.Kind == "chat.completions" {
if request.Kind == "responses" && protocol == ProtocolOpenAIChatCompletions {
var convertErr error
body, convertErr = ResponsesRequestToChat(request.Body, request.PreviousResponseTurns)
if convertErr != nil {
return Response{}, convertErr
}
}
if endpointKind == "chat.completions" {
body = NormalizeChatCompletionRequestBody(body)
applyOpenAIChatReasoningParams(body, request.Candidate)
} else if request.Kind == "responses" {
delete(body, "messages")
if request.UpstreamPreviousResponseID != "" {
body["previous_response_id"] = request.UpstreamPreviousResponseID
} else {
delete(body, "previous_response_id")
}
}
body["model"] = upstreamModelName(request.Candidate)
stream := openAIEndpointSupportsStream(request.Kind) && (request.Stream || boolValue(body, "stream"))
ensureOpenAIStreamUsage(body, request.Kind, stream)
stream := openAIEndpointSupportsStream(endpointKind) && (request.Stream || boolValue(body, "stream"))
ensureOpenAIStreamUsage(body, endpointKind, stream)
raw, _ := json.Marshal(body)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, joinURL(openAIBaseURL(request.Kind, request.Candidate), endpoint), bytes.NewReader(raw))
upstreamEndpoint := joinURL(openAIBaseURL(endpointKind, request.Candidate), endpoint)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, upstreamEndpoint, bytes.NewReader(raw))
if err != nil {
return Response{}, err
}
@@ -45,7 +68,38 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
return Response{}, &ClientError{Code: "network", Message: err.Error(), Retryable: true}
}
requestID := requestIDFromHTTPResponse(resp)
result, err := decodeOpenAIResponse(resp, stream, request.StreamDelta)
var result map[string]any
upstreamResponseID := ""
if request.Kind == "responses" && protocol == ProtocolOpenAIResponses && stream {
result, upstreamResponseID, err = decodeNativeResponsesStream(resp, request.StreamDelta)
} else {
var streamDelta StreamDelta = request.StreamDelta
var adapter *chatResponsesStreamAdapter
if request.Kind == "responses" && protocol == ProtocolOpenAIChatCompletions && stream {
adapter = newChatResponsesStreamAdapter(request.PublicResponseID, request.Model)
streamDelta = func(event StreamDeltaEvent) error { return adapter.delta(event, request.StreamDelta) }
}
result, err = decodeOpenAIResponse(resp, stream, streamDelta)
if err == nil && endpointKind == "chat.completions" {
result = NormalizeChatCompletionResult(result)
}
if err == nil && request.Kind == "responses" && protocol == ProtocolOpenAIChatCompletions {
chatResult := result
upstreamResponseID = requestIDFromResult(chatResult)
result = ChatResultToResponse(chatResult, request.PublicResponseID, request.Model, request.Body)
if adapter != nil {
err = adapter.done(result, request.StreamDelta)
}
if err == nil {
return Response{
Result: result, InternalResult: chatResult, RequestID: firstNonEmptyString(requestID, upstreamResponseID), Usage: usageFromOpenAI(chatResult),
Progress: providerProgress(request), ResponseStartedAt: responseStartedAt, ResponseFinishedAt: time.Now(),
UpstreamProtocol: protocol, UpstreamEndpoint: endpoint, UpstreamResponseID: upstreamResponseID,
PublicResponseID: request.PublicResponseID, ResponseConverted: true,
}, nil
}
}
}
if err == nil && request.Kind == "chat.completions" {
result = NormalizeChatCompletionResult(result)
}
@@ -56,6 +110,15 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
if requestID == "" {
requestID = requestIDFromResult(result)
}
if request.Kind == "responses" && protocol == ProtocolOpenAIResponses {
if upstreamResponseID == "" {
upstreamResponseID = requestIDFromResult(result)
}
}
publicResponseID := request.PublicResponseID
if request.Kind == "responses" && protocol == ProtocolOpenAIResponses {
publicResponseID = upstreamResponseID
}
return Response{
Result: result,
RequestID: requestID,
@@ -64,6 +127,10 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
ResponseStartedAt: responseStartedAt,
ResponseFinishedAt: responseFinishedAt,
ResponseDurationMS: responseDurationMS(responseStartedAt, responseFinishedAt),
UpstreamProtocol: protocol,
UpstreamEndpoint: endpoint,
UpstreamResponseID: upstreamResponseID,
PublicResponseID: publicResponseID,
}, nil
}