@@ -6,13 +6,31 @@ import {
66 createProviderHttpError ,
77 extractProviderErrorDetail ,
88 extractProviderRequestId ,
9+ formatProviderErrorPayload ,
910 ProviderHttpError ,
1011 readProviderBinaryResponse ,
1112 readProviderJsonResponse ,
1213 readProviderTextResponse ,
1314 readResponseTextLimited ,
1415} from "./provider-http-errors.js" ;
1516
17+ function hasLoneSurrogate ( s : string ) : boolean {
18+ for ( let i = 0 ; i < s . length ; i += 1 ) {
19+ const c = s . charCodeAt ( i ) ;
20+ if ( c >= 0xd800 && c <= 0xdbff ) {
21+ if ( i + 1 >= s . length || s . charCodeAt ( i + 1 ) < 0xdc00 || s . charCodeAt ( i + 1 ) > 0xdfff ) {
22+ return true ;
23+ }
24+ }
25+ if ( c >= 0xdc00 && c <= 0xdfff ) {
26+ if ( i === 0 || s . charCodeAt ( i - 1 ) < 0xd800 || s . charCodeAt ( i - 1 ) > 0xdbff ) {
27+ return true ;
28+ }
29+ }
30+ }
31+ return false ;
32+ }
33+
1634function createStreamingBinaryResponse ( params : {
1735 chunkCount : number ;
1836 chunkSize : number ;
@@ -136,6 +154,21 @@ describe("provider error utils", () => {
136154 ) ;
137155 } ) ;
138156
157+ it ( "does not split UTF-16 surrogate pairs when truncating provider error details" , async ( ) => {
158+ const message = "a" . repeat ( 218 ) + "😀" + "suffix" ;
159+ const response = new Response (
160+ JSON . stringify ( {
161+ error : { message, code : "utf16_test" } ,
162+ } ) ,
163+ { status : 400 } ,
164+ ) ;
165+
166+ await expect ( assertOkOrThrowProviderError ( response , "Provider API error" ) ) . rejects . toThrow ( ) ;
167+ const detail = formatProviderErrorPayload ( { error : { message, code : "utf16_test" } } ) ;
168+ expect ( detail ) . toContain ( "utf16_test" ) ;
169+ expect ( hasLoneSurrogate ( detail ?? "" ) ) . toBe ( false ) ;
170+ } ) ;
171+
139172 it ( "keeps HTTP status metadata when error body reads fail" , async ( ) => {
140173 const response = {
141174 ok : false ,
0 commit comments