diff --git a/sdk/api/handlers/openai/openai_responses_websocket.go b/sdk/api/handlers/openai/openai_responses_websocket.go index 6a444b45..d417d6b2 100644 --- a/sdk/api/handlers/openai/openai_responses_websocket.go +++ b/sdk/api/handlers/openai/openai_responses_websocket.go @@ -34,6 +34,8 @@ const ( wsTurnStateHeader = "x-codex-turn-state" wsRequestBodyKey = "REQUEST_BODY_OVERRIDE" wsPayloadLogMaxSize = 2048 + wsBodyLogMaxSize = 64 * 1024 + wsBodyLogTruncated = "\n[websocket log truncated]\n" ) var responsesWebsocketUpgrader = websocket.Upgrader{ @@ -825,18 +827,71 @@ func appendWebsocketEvent(builder *strings.Builder, eventType string, payload [] if builder == nil { return } + if builder.Len() >= wsBodyLogMaxSize { + return + } trimmedPayload := bytes.TrimSpace(payload) if len(trimmedPayload) == 0 { return } if builder.Len() > 0 { - builder.WriteString("\n") + if !appendWebsocketLogString(builder, "\n") { + return + } } - builder.WriteString("websocket.") - builder.WriteString(eventType) - builder.WriteString("\n") - builder.Write(trimmedPayload) - builder.WriteString("\n") + if !appendWebsocketLogString(builder, "websocket.") { + return + } + if !appendWebsocketLogString(builder, eventType) { + return + } + if !appendWebsocketLogString(builder, "\n") { + return + } + if !appendWebsocketLogBytes(builder, trimmedPayload, len(wsBodyLogTruncated)) { + appendWebsocketLogString(builder, wsBodyLogTruncated) + return + } + appendWebsocketLogString(builder, "\n") +} + +func appendWebsocketLogString(builder *strings.Builder, value string) bool { + if builder == nil { + return false + } + remaining := wsBodyLogMaxSize - builder.Len() + if remaining <= 0 { + return false + } + if len(value) <= remaining { + builder.WriteString(value) + return true + } + builder.WriteString(value[:remaining]) + return false +} + +func appendWebsocketLogBytes(builder *strings.Builder, value []byte, reserveForSuffix int) bool { + if builder == nil { + return false + } + remaining := wsBodyLogMaxSize - builder.Len() + if remaining <= 0 { + return false + } + if len(value) <= remaining { + builder.Write(value) + return true + } + limit := remaining - reserveForSuffix + if limit < 0 { + limit = 0 + } + if limit > len(value) { + limit = len(value) + } + builder.Write(value[:limit]) + return false } func websocketPayloadEventType(payload []byte) string { diff --git a/sdk/api/handlers/openai/openai_responses_websocket_test.go b/sdk/api/handlers/openai/openai_responses_websocket_test.go index d30c648d..c7348583 100644 --- a/sdk/api/handlers/openai/openai_responses_websocket_test.go +++ b/sdk/api/handlers/openai/openai_responses_websocket_test.go @@ -266,6 +266,34 @@ func TestAppendWebsocketEvent(t *testing.T) { } } + +func TestAppendWebsocketEventTruncatesAtLimit(t *testing.T) { + var builder strings.Builder + payload := bytes.Repeat([]byte("x"), wsBodyLogMaxSize) + + appendWebsocketEvent(&builder, "request", payload) + + got := builder.String() + if len(got) > wsBodyLogMaxSize { + t.Fatalf("body log len = %d, want <= %d", len(got), wsBodyLogMaxSize) + } + if !strings.Contains(got, wsBodyLogTruncated) { + t.Fatalf("expected truncation marker in body log") + } +} + +func TestAppendWebsocketEventNoGrowthAfterLimit(t *testing.T) { + var builder strings.Builder + appendWebsocketEvent(&builder, "request", bytes.Repeat([]byte("x"), wsBodyLogMaxSize)) + initial := builder.String() + + appendWebsocketEvent(&builder, "response", []byte(`{"type":"response.completed"}`)) + + if builder.String() != initial { + t.Fatalf("builder grew after reaching limit") + } +} + func TestSetWebsocketRequestBody(t *testing.T) { gin.SetMode(gin.TestMode) recorder := httptest.NewRecorder()