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:
@@ -44,25 +44,89 @@ func intValue(body map[string]any, key string, fallback int) int {
|
||||
}
|
||||
|
||||
func decodeHTTPResponse(resp *http.Response) (map[string]any, error) {
|
||||
result, _, err := decodeHTTPResponseForProtocol(resp, "")
|
||||
return result, err
|
||||
}
|
||||
|
||||
func decodeHTTPResponseForProtocol(resp *http.Response, protocol string) (map[string]any, *WireResponse, error) {
|
||||
defer resp.Body.Close()
|
||||
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 16*1024*1024))
|
||||
wire := &WireResponse{
|
||||
Protocol: strings.TrimSpace(protocol),
|
||||
StatusCode: resp.StatusCode,
|
||||
Headers: compatibleResponseHeaders(resp.Header),
|
||||
RawJSON: append([]byte(nil), raw...),
|
||||
}
|
||||
if len(raw) > 0 {
|
||||
_ = json.Unmarshal(raw, &wire.Body)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return nil, &ClientError{
|
||||
return nil, wire, &ClientError{
|
||||
Code: statusCodeName(resp.StatusCode),
|
||||
Message: errorMessage(raw, resp.Status),
|
||||
StatusCode: resp.StatusCode,
|
||||
RequestID: requestIDFromHTTPResponse(resp),
|
||||
Retryable: HTTPRetryable(resp.StatusCode),
|
||||
Wire: wire,
|
||||
}
|
||||
}
|
||||
var out map[string]any
|
||||
if len(raw) == 0 {
|
||||
return map[string]any{}, nil
|
||||
wire.Body = map[string]any{}
|
||||
return map[string]any{}, wire, nil
|
||||
}
|
||||
if err := json.Unmarshal(raw, &out); err != nil {
|
||||
return nil, &ClientError{Code: "invalid_response", Message: err.Error(), Retryable: false}
|
||||
return nil, wire, &ClientError{Code: "invalid_response", Message: err.Error(), StatusCode: resp.StatusCode, Retryable: false, Wire: wire}
|
||||
}
|
||||
return out, nil
|
||||
wire.Body = cloneBody(out)
|
||||
return out, wire, nil
|
||||
}
|
||||
|
||||
func compatibleResponseHeaders(source http.Header) map[string][]string {
|
||||
if len(source) == 0 {
|
||||
return nil
|
||||
}
|
||||
allowed := map[string]bool{
|
||||
"content-type": true, "retry-after": true,
|
||||
"x-request-id": true, "request-id": true,
|
||||
"x-ratelimit-limit-requests": true, "x-ratelimit-remaining-requests": true,
|
||||
"x-ratelimit-reset-requests": true, "x-ratelimit-limit-tokens": true,
|
||||
"x-ratelimit-remaining-tokens": true, "x-ratelimit-reset-tokens": true,
|
||||
"x-goog-request-id": true, "x-goog-upload-status": true,
|
||||
"x-goog-upload-url": true, "x-goog-upload-size-received": true,
|
||||
}
|
||||
out := map[string][]string{}
|
||||
for key, values := range source {
|
||||
if !allowed[strings.ToLower(strings.TrimSpace(key))] {
|
||||
continue
|
||||
}
|
||||
out[http.CanonicalHeaderKey(key)] = append([]string(nil), values...)
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func notifySubmissionStarted(request Request) error {
|
||||
if request.OnUpstreamSubmissionStarted == nil {
|
||||
return nil
|
||||
}
|
||||
return request.OnUpstreamSubmissionStarted()
|
||||
}
|
||||
|
||||
func notifyResponseReceived(request Request) error {
|
||||
if request.OnUpstreamResponseReceived == nil {
|
||||
return nil
|
||||
}
|
||||
return request.OnUpstreamResponseReceived()
|
||||
}
|
||||
|
||||
func notifyWireResponse(request Request, wire *WireResponse) error {
|
||||
if wire == nil || request.OnUpstreamWireResponse == nil {
|
||||
return nil
|
||||
}
|
||||
return request.OnUpstreamWireResponse(wire)
|
||||
}
|
||||
|
||||
func decodeOpenAIStreamResponse(resp *http.Response, onDelta StreamDelta) (map[string]any, error) {
|
||||
|
||||
Reference in New Issue
Block a user