feat(api): 统一官方兼容接口响应协议
兼容接口现在以入口协议作为最终响应协议,同协议保留官方 Wire 响应,跨协议统一转换成功、任务状态与错误结构。 同时修正异步提交状态边界,持久化兼容公开任务标识和官方提交响应,并新增迁移、流式响应及协议契约测试。 验证:go vet ./...;go test ./...;govulncheck ./...;pnpm lint;pnpm test;pnpm build;pnpm audit --audit-level high;pnpm openapi;全部 CI 脚本。
This commit is contained in:
@@ -70,23 +70,37 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
responseStartedAt := time.Now()
|
||||
if err := notifySubmissionStarted(request); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
resp, err := httpClient(request.HTTPClient, c.HTTPClient).Do(req)
|
||||
if err != nil {
|
||||
return Response{}, &ClientError{Code: "network", Message: err.Error(), Retryable: true}
|
||||
}
|
||||
if err := notifyResponseReceived(request); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
requestID := requestIDFromHTTPResponse(resp)
|
||||
var result map[string]any
|
||||
var wire *WireResponse
|
||||
upstreamResponseID := ""
|
||||
nativeStreamDelta := openAIWireStreamDelta(request.StreamDelta, openAIWireProtocol(endpointKind), resp)
|
||||
if request.Kind == "responses" && protocol == ProtocolOpenAIResponses && stream {
|
||||
result, upstreamResponseID, err = decodeNativeResponsesStream(resp, request.StreamDelta)
|
||||
result, upstreamResponseID, err = decodeNativeResponsesStream(resp, nativeStreamDelta)
|
||||
wire = &WireResponse{Protocol: openAIWireProtocol(endpointKind), StatusCode: resp.StatusCode, Headers: compatibleResponseHeaders(resp.Header)}
|
||||
} else {
|
||||
var streamDelta StreamDelta = request.StreamDelta
|
||||
var streamDelta StreamDelta = nativeStreamDelta
|
||||
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 stream {
|
||||
result, err = decodeOpenAIResponse(resp, true, streamDelta)
|
||||
wire = &WireResponse{Protocol: openAIWireProtocol(endpointKind), StatusCode: resp.StatusCode, Headers: compatibleResponseHeaders(resp.Header)}
|
||||
} else {
|
||||
result, wire, err = decodeHTTPResponseForProtocol(resp, openAIWireProtocol(endpointKind))
|
||||
}
|
||||
if err == nil && endpointKind == "chat.completions" {
|
||||
result = NormalizeChatCompletionResult(result)
|
||||
}
|
||||
@@ -103,6 +117,12 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
||||
Progress: providerProgress(request), ResponseStartedAt: responseStartedAt, ResponseFinishedAt: time.Now(),
|
||||
UpstreamProtocol: protocol, UpstreamEndpoint: endpoint, UpstreamResponseID: upstreamResponseID,
|
||||
PublicResponseID: request.PublicResponseID, ResponseConverted: true,
|
||||
Wire: func() *WireResponse {
|
||||
if wire != nil {
|
||||
wire.Converted = true
|
||||
}
|
||||
return wire
|
||||
}(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -138,9 +158,39 @@ func (c OpenAIClient) Run(ctx context.Context, request Request) (Response, error
|
||||
UpstreamEndpoint: endpoint,
|
||||
UpstreamResponseID: upstreamResponseID,
|
||||
PublicResponseID: publicResponseID,
|
||||
Wire: wire,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func openAIWireStreamDelta(next StreamDelta, protocol string, response *http.Response) StreamDelta {
|
||||
if next == nil {
|
||||
return nil
|
||||
}
|
||||
return func(event StreamDeltaEvent) error {
|
||||
event.WireProtocol = protocol
|
||||
if response != nil {
|
||||
event.WireStatusCode = response.StatusCode
|
||||
event.WireHeaders = compatibleResponseHeaders(response.Header)
|
||||
}
|
||||
return next(event)
|
||||
}
|
||||
}
|
||||
|
||||
func openAIWireProtocol(kind string) string {
|
||||
switch kind {
|
||||
case "chat.completions":
|
||||
return ProtocolOpenAIChatCompletions
|
||||
case "responses":
|
||||
return ProtocolOpenAIResponses
|
||||
case "embeddings":
|
||||
return ProtocolOpenAIEmbeddings
|
||||
case "images.generations", "images.edits":
|
||||
return ProtocolOpenAIImages
|
||||
default:
|
||||
return "openai_" + strings.ReplaceAll(kind, ".", "_")
|
||||
}
|
||||
}
|
||||
|
||||
func decodeOpenAIResponse(resp *http.Response, stream bool, onDelta StreamDelta) (map[string]any, error) {
|
||||
if stream {
|
||||
result, err := decodeOpenAIStreamResponse(resp, onDelta)
|
||||
|
||||
Reference in New Issue
Block a user