@@ -330,9 +330,10 @@ async fn build_normal_response(
330330 let mut header_map = hyper:: header:: HeaderMap :: new ( ) ;
331331
332332 // Content-type precedence:
333- // 1. Explicit in http.response headers (highest priority)
334- // 2. Pipeline metadata content_type (from `to json`, etc.)
335- // 3. Auto-detection default
333+ // 1. Explicit in http.response headers
334+ // 2. Pipeline metadata (from `to json`, etc.)
335+ // 3. Inferred: record->json, binary->octet-stream, list/stream of records->ndjson, empty->None
336+ // 4. Default: text/html
336337 let content_type = http_meta
337338 . headers
338339 . get ( "content-type" )
@@ -342,12 +343,20 @@ async fn build_normal_response(
342343 crate :: response:: HeaderValue :: Multiple ( v) => v. first ( ) . cloned ( ) ,
343344 } )
344345 . or ( inferred_content_type)
345- . unwrap_or ( "text/html; charset=utf-8" . to_string ( ) ) ;
346+ . or_else ( || {
347+ if matches ! ( body, ResponseTransport :: Empty ) {
348+ None
349+ } else {
350+ Some ( "text/html; charset=utf-8" . to_string ( ) )
351+ }
352+ } ) ;
346353
347- header_map. insert (
348- hyper:: header:: CONTENT_TYPE ,
349- hyper:: header:: HeaderValue :: from_str ( & content_type) ?,
350- ) ;
354+ if let Some ( ref ct) = content_type {
355+ header_map. insert (
356+ hyper:: header:: CONTENT_TYPE ,
357+ hyper:: header:: HeaderValue :: from_str ( ct) ?,
358+ ) ;
359+ }
351360
352361 // Add compression headers if using brotli
353362 if use_brotli {
@@ -362,7 +371,7 @@ async fn build_normal_response(
362371 }
363372
364373 // Add SSE-required headers for event streams
365- let is_sse = content_type == "text/event-stream" ;
374+ let is_sse = content_type. as_deref ( ) == Some ( "text/event-stream" ) ;
366375 if is_sse {
367376 header_map. insert (
368377 hyper:: header:: CACHE_CONTROL ,
0 commit comments