package service import ( "context" "errors" "fmt" "strings" ) const ( // OpenAIUpstreamHTTP2StreamErrorCode is returned to OpenAI-compatible clients // when an upstream HTTP/2 response stream is reset after the request started. OpenAIUpstreamHTTP2StreamErrorCode = "upstream_http2_stream_error" OpenAIUpstreamStreamReadErrorCode = "upstream_stream_read_error" ) type openAIUpstreamStreamReadError struct { cause error clientCode string clientMessage string } func (e *openAIUpstreamStreamReadError) Error() string { return fmt.Sprintf("stream usage incomplete: %v", e.cause) } func (e *openAIUpstreamStreamReadError) Unwrap() error { return e.cause } func newOpenAIUpstreamStreamReadError(err error) error { code, message := classifyOpenAIUpstreamStreamReadError(err) return &openAIUpstreamStreamReadError{ cause: err, clientCode: code, clientMessage: message, } } // shouldClassifyOpenAIUpstreamStreamReadError excludes cancellation and // response-size enforcement from upstream retry. func shouldClassifyOpenAIUpstreamStreamReadError(err error, contexts ...context.Context) bool { if err == nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, ErrUpstreamResponseBodyTooLarge) { return false } for _, ctx := range contexts { if ctx != nil && ctx.Err() != nil { return false } } return true } // OpenAIUpstreamStreamReadErrorDetails returns the stable, sanitized client // classification attached to an upstream stream read failure. func OpenAIUpstreamStreamReadErrorDetails(err error) (code, message string, ok bool) { var streamErr *openAIUpstreamStreamReadError if !errors.As(err, &streamErr) || streamErr == nil { return "", "", false } return streamErr.clientCode, streamErr.clientMessage, true } func classifyOpenAIUpstreamStreamReadError(err error) (code, message string) { if err != nil { lower := strings.ToLower(err.Error()) // net/http's HTTP/2 stream error is unexported. Its stable text contains // "stream error: stream ID ..."; match only the transport signature and // never pass the original text to the client. if strings.Contains(lower, "stream error: stream id ") || (strings.Contains(lower, "http2:") && strings.Contains(lower, "stream")) { return OpenAIUpstreamHTTP2StreamErrorCode, "Upstream HTTP/2 stream failed" } } return OpenAIUpstreamStreamReadErrorCode, "Upstream response stream was interrupted" }