Always populate ORItemParam.Summary (#9049)

* fix(openresponses): do not omit required fields summary and id

* fix(openresponses): ensure ORItemParam.Summary is never null

Normalize Summary to an empty slice at serialization chokepoints
(sendSSEEvent, bufferEvent, buildORResponse) so it always serializes
as [] instead of null.

Closes #9047
This commit is contained in:
Tv
2026-03-18 07:45:46 +00:00
committed by GitHub
parent 35d509d8e7
commit 8a0edd0809
2 changed files with 19 additions and 3 deletions

View File

@@ -1332,6 +1332,7 @@ func handleBackgroundStream(ctx context.Context, store *ResponseStore, responseI
// bufferEvent stores an SSE event in the response store for streaming resume
func bufferEvent(store *ResponseStore, responseID string, event *schema.ORStreamEvent) {
normalizeORStreamEvent(event)
if err := store.AppendEvent(responseID, event); err != nil {
xlog.Error("Failed to buffer event", "response_id", responseID, "error", err)
}
@@ -2605,6 +2606,7 @@ func handleOpenResponsesStream(c echo.Context, responseID string, createdAt int6
// sendSSEEvent sends a Server-Sent Event
func sendSSEEvent(c echo.Context, event *schema.ORStreamEvent) {
normalizeORStreamEvent(event)
data, err := json.Marshal(event)
if err != nil {
xlog.Error("Failed to marshal SSE event", "error", err)
@@ -2613,6 +2615,13 @@ func sendSSEEvent(c echo.Context, event *schema.ORStreamEvent) {
fmt.Fprintf(c.Response().Writer, "event: %s\ndata: %s\n\n", event.Type, string(data))
}
// normalizeORStreamEvent ensures required fields like Summary are never null.
func normalizeORStreamEvent(event *schema.ORStreamEvent) {
if event.Item != nil && event.Item.Summary == nil {
event.Item.Summary = []schema.ORContentPart{}
}
}
// getTopLogprobs returns the top_logprobs value, defaulting to 0 if nil
func getTopLogprobs(topLogprobs *int) int {
if topLogprobs != nil {
@@ -2693,6 +2702,13 @@ func buildORResponse(responseID string, createdAt int64, completedAt *int64, sta
outputItems = []schema.ORItemField{}
}
// Ensure Summary is never null on any output item
for i := range outputItems {
if outputItems[i].Summary == nil {
outputItems[i].Summary = []schema.ORContentPart{}
}
}
// Ensure tools is never null - always an array
tools := input.Tools
if tools == nil {

View File

@@ -86,7 +86,7 @@ type ORReasoningParam struct {
// ORItemParam represents an input/output item (discriminated union by type)
type ORItemParam struct {
Type string `json:"type"` // message|function_call|function_call_output|reasoning|item_reference
ID string `json:"id,omitempty"` // Present for all output items
ID string `json:"id"` // Present for all output items
Status string `json:"status,omitempty"` // in_progress|completed|incomplete
// Message fields
@@ -102,8 +102,8 @@ type ORItemParam struct {
Output interface{} `json:"output,omitempty"` // string or []ORContentPart
// Reasoning fields (for type == "reasoning")
Summary []ORContentPart `json:"summary,omitempty"` // Array of summary parts
EncryptedContent *string `json:"encrypted_content,omitempty"` // Provider-specific encrypted content
Summary []ORContentPart `json:"summary"` // Array of summary parts
EncryptedContent *string `json:"encrypted_content,omitempty"` // Provider-specific encrypted content
// Note: For item_reference type, use the ID field above to reference the item
// Note: For reasoning type, Content field (from message fields) contains the raw reasoning content