style: fix lint errors in response.failed SSE writer
Errcheck flagged three unchecked strings.Builder.WriteString calls and gofmt rejected over-aligned trailing comment in the route table. Rewrite writeResponsesFailedSSE with json.Marshal on typed structs instead of Builder+strconv.Quote. Same wire format, but: - no unchecked Write returns to silence - strict JSON escaping (strconv.Quote emits \a and \v which are not valid JSON; Marshal handles all runes correctly) - omitempty model field via struct tag instead of conditional Builder - consistent with the json.Marshal style used elsewhere in handler/ Collapse trailing comment whitespace in stream_error_event_test.go to satisfy gofmt. All 30+ subtests in the package still pass.
This commit is contained in:
parent
a18738b29e
commit
53acde1efd
@ -1,9 +1,9 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
|
||||||
@ -11,6 +11,30 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// responsesFailedError 对齐 OpenAI Responses 协议 error 子对象。
|
||||||
|
type responsesFailedError struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// responsesFailedBody 对齐 apicompat.makeResponsesCompletedEvent 输出的 response 子对象字段集。
|
||||||
|
// Output 用空 slice(不是 nil)确保 marshal 为 `[]` 而非 `null`。
|
||||||
|
type responsesFailedBody struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Object string `json:"object"`
|
||||||
|
Model string `json:"model,omitempty"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Output []any `json:"output"`
|
||||||
|
Error responsesFailedError `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// responsesFailedEvent 是写入 SSE data 行的顶层结构。
|
||||||
|
// 故意不带 sequence_number:spec 标记可选,且本函数被调用时无法可靠拿到 last seq。
|
||||||
|
type responsesFailedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Response responsesFailedBody `json:"response"`
|
||||||
|
}
|
||||||
|
|
||||||
// writeResponsesFailedSSE emits a `response.failed` SSE event in the OpenAI
|
// writeResponsesFailedSSE emits a `response.failed` SSE event in the OpenAI
|
||||||
// Responses API protocol after the stream has already started.
|
// Responses API protocol after the stream has already started.
|
||||||
//
|
//
|
||||||
@ -33,27 +57,27 @@ func writeResponsesFailedSSE(c *gin.Context, errType, message string) bool {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
rid := synthesizeResponseID(c)
|
|
||||||
model := requestModel(c)
|
|
||||||
code := mapResponsesErrorCode(errType)
|
|
||||||
|
|
||||||
var b strings.Builder
|
payload, err := json.Marshal(responsesFailedEvent{
|
||||||
b.Grow(256 + len(message) + len(model))
|
Type: "response.failed",
|
||||||
b.WriteString(`{"type":"response.failed","response":{`)
|
Response: responsesFailedBody{
|
||||||
b.WriteString(`"id":`)
|
ID: synthesizeResponseID(c),
|
||||||
b.WriteString(strconv.Quote(rid))
|
Object: "response",
|
||||||
b.WriteString(`,"object":"response"`)
|
Model: requestModel(c),
|
||||||
if model != "" {
|
Status: "failed",
|
||||||
b.WriteString(`,"model":`)
|
Output: []any{},
|
||||||
b.WriteString(strconv.Quote(model))
|
Error: responsesFailedError{
|
||||||
|
Code: mapResponsesErrorCode(errType),
|
||||||
|
Message: message,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
b.WriteString(`,"status":"failed","output":[],"error":{"code":`)
|
|
||||||
b.WriteString(strconv.Quote(code))
|
|
||||||
b.WriteString(`,"message":`)
|
|
||||||
b.WriteString(strconv.Quote(message))
|
|
||||||
b.WriteString(`}}}`)
|
|
||||||
|
|
||||||
if _, err := fmt.Fprintf(c.Writer, "event: response.failed\ndata: %s\n\n", b.String()); err != nil {
|
if _, err := fmt.Fprintf(c.Writer, "event: response.failed\ndata: %s\n\n", payload); err != nil {
|
||||||
_ = c.Error(err)
|
_ = c.Error(err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -186,7 +186,7 @@ func TestInboundIsResponses_CoversAllRoutes(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"/v1/responses", true},
|
{"/v1/responses", true},
|
||||||
{"/v1/responses/compact", true},
|
{"/v1/responses/compact", true},
|
||||||
{"/responses", true}, // <-- 用户 16 实际走这条
|
{"/responses", true}, // <-- 用户 16 实际走这条
|
||||||
{"/responses/compact", true},
|
{"/responses/compact", true},
|
||||||
{"/backend-api/codex/responses", true},
|
{"/backend-api/codex/responses", true},
|
||||||
{"/backend-api/codex/responses/compact", true},
|
{"/backend-api/codex/responses/compact", true},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user