@@ -77,6 +77,8 @@ export interface RequestLogContext {
7777 upstreamError ?: string ;
7878 /** HTTP status derived from a terminal `response.failed` SSE payload (429/401/503/etc.). */
7979 terminalHttpStatus ?: number ;
80+ /** Structured reason from `response.incomplete`; internal-only input to log classification. */
81+ terminalIncompleteReason ?: string ;
8082 affinity ?: "reused" | "new_bind" | "rebound" | "cleared" ;
8183 transportPhase ?: "pre_headers" | "mid_stream" | "terminal_sse" ;
8284 terminalSource ?: "upstream" | "synthetic" ;
@@ -532,7 +534,7 @@ export function inspectResponseLogSsePayload(logCtx: RequestLogContext, payload:
532534 * run it through redactSecretString so secrets never reach /api/logs. Pure; safe on any text.
533535 */
534536function captureUpstreamError ( logCtx : RequestLogContext , text : string | null ) : void {
535- if ( ! text || logCtx . upstreamError ) return ;
537+ if ( ! text ) return ;
536538 try {
537539 const json = JSON . parse ( text ) as {
538540 type ?: unknown ;
@@ -544,6 +546,14 @@ function captureUpstreamError(logCtx: RequestLogContext, text: string | null): v
544546 } ;
545547 } ;
546548 captureTerminalHttpStatus ( logCtx , json ) ;
549+ const reason = json ?. response ?. incomplete_details ?. reason ;
550+ if ( json . type === "response.incomplete"
551+ && logCtx . terminalIncompleteReason === undefined
552+ && typeof reason === "string"
553+ && reason . trim ( ) ) {
554+ logCtx . terminalIncompleteReason = reason . trim ( ) ;
555+ }
556+ if ( logCtx . upstreamError ) return ;
547557 const message = json ?. error ?. message
548558 ?? json ?. last_error ?. message
549559 ?? json ?. response ?. error ?. message ;
@@ -555,11 +565,11 @@ function captureUpstreamError(logCtx: RequestLogContext, text: string | null): v
555565 // the bridge on a stall-timeout or adapter EOF (response.incomplete). Maps the raw reason to a
556566 // reader-facing label so a generic 502 in /api/logs explains WHY the turn ended, not just the
557567 // mapped HTTP code.
558- const reason = json ?. response ?. incomplete_details ?. reason ;
559568 if ( typeof reason === "string" && reason . trim ( ) ) {
560569 logCtx . upstreamError = redactSecretString ( incompleteReasonLabel ( reason . trim ( ) ) ) . slice ( 0 , 500 ) ;
561570 }
562571 } catch {
572+ if ( logCtx . upstreamError ) return ;
563573 const trimmed = text . trim ( ) ;
564574 if ( trimmed ) {
565575 logCtx . upstreamError = redactSecretString ( trimmed ) . slice ( 0 , 500 ) ;
@@ -570,6 +580,8 @@ function captureUpstreamError(logCtx: RequestLogContext, text: string | null): v
570580/** Map a raw `incomplete_details.reason` (emitted by the bridge) to a reader-facing label. */
571581function incompleteReasonLabel ( reason : string ) : string {
572582 switch ( reason ) {
583+ case "max_output_tokens" :
584+ return `Output reached the requested token limit (${ reason } )` ;
573585 case "upstream_stall_timeout" :
574586 return `Upstream stalled: no data for the stall-timeout window (${ reason } )` ;
575587 case "adapter_eof" :
@@ -614,6 +626,21 @@ export function httpStatusForRequestLogTerminal(
614626 status : ResponsesTerminalStatus ,
615627 logCtx ?: RequestLogContext ,
616628) : number {
629+ /**
630+ * [Decision Log]
631+ * - 목적과 의도: Keep request logs aligned with the successful HTTP/SSE contract.
632+ * - 기존 구현 및 제약 조건: All incomplete terminals were recorded as 502 even when the
633+ * client-requested output limit was reached normally.
634+ * - 검토한 주요 대안: Treat every incomplete as success, or infer the reason from display text.
635+ * - 선택한 방식: Only structured max_output_tokens incompletes map to 200.
636+ * - 다른 대안 대신 이 방식을 선택한 이유: Stall, EOF, and unknown incompletes must remain
637+ * visible failures, and display text is not a stable classification contract.
638+ * - 장점, 단점 및 영향: Logs stop reporting false upstream errors while retaining the
639+ * incomplete terminal detail; native callers without a structured reason keep old behavior.
640+ */
641+ if ( status === "incomplete" && logCtx ?. terminalIncompleteReason === "max_output_tokens" ) {
642+ return 200 ;
643+ }
617644 if ( status === "failed" && logCtx ?. terminalHttpStatus !== undefined ) {
618645 return logCtx . terminalHttpStatus ;
619646 }
0 commit comments