@@ -50,6 +50,7 @@ function mostlyText(buf) {
5050function redactPreview ( s ) {
5151 return String ( s )
5252 . replace ( / \b (?: d e v i n - s e s s i o n - t o k e n | s e s s i o n T o k e n | a p i [ _ - ] ? k e y | f i r e b a s e _ i d _ t o k e n | i d T o k e n | r e f r e s h T o k e n ) \b \s * [: = ] \s * [ " ' ] ? [ ^ " ' , \s ) ] + / gi, '<redacted-secret>' )
53+ . replace ( / \b [ A - Z 0 - 9 . _ % + - ] + @ [ A - Z 0 - 9 . - ] + \. [ A - Z ] { 2 , } \b / gi, '<redacted-email>' )
5354 . replace ( / \b [ A - Z a - z 0 - 9 _ - ] { 32 , } \b / g, '<redacted-token>' )
5455 . slice ( 0 , 240 ) ;
5556}
@@ -423,6 +424,115 @@ function summarizeReadWrapperField19(wrapperBuf) {
423424 } ;
424425}
425426
427+ function classifyErrorText ( text ) {
428+ const value = String ( text || '' ) . toLowerCase ( ) ;
429+ return {
430+ permissionDenied : / p e r m i s s i o n [ _ \s - ] ? d e n i e d | f o r b i d d e n | \b 4 0 3 \b | n o t a u t h o r i z e d / . test ( value ) ,
431+ failedPrecondition : / f a i l e d [ _ \s - ] ? p r e c o n d i t i o n | p r e c o n d i t i o n / . test ( value ) ,
432+ modelNotAvailable : / m o d e l [ _ \s - ] ? n o t [ _ \s - ] ? a v a i l a b l e | m o d e l n o t a v a i l a b l e / . test ( value ) ,
433+ internalError : / i n t e r n a l [ _ \s - ] ? e r r o r | a n i n t e r n a l e r r o r o c c u r r e d / . test ( value ) ,
434+ unauthenticated : / u n a u t h e n t i c a t e d | \b 4 0 1 \b | i n v a l i d a p i k e y | i n v a l i d t o k e n / . test ( value ) ,
435+ rateLimited : / r a t e [ _ \s - ] ? l i m i t | t o o m a n y r e q u e s t s | \b 4 2 9 \b / . test ( value ) ,
436+ quotaOrEntitlement : / q u o t a | c r e d i t | b i l l i n g | s u b s c r i p t i o n | e n t i t l e m e n t / . test ( value ) ,
437+ } ;
438+ }
439+
440+ function compactTrueFlags ( flags ) {
441+ return Object . fromEntries ( Object . entries ( flags || { } ) . filter ( ( [ , v ] ) => ! ! v ) ) ;
442+ }
443+
444+ function mergeErrorClassifications ( target , source ) {
445+ for ( const [ key , value ] of Object . entries ( source || { } ) ) {
446+ if ( value ) target [ key ] = true ;
447+ }
448+ return target ;
449+ }
450+
451+ function summarizeErrorString ( path , buf ) {
452+ const text = buf . toString ( 'utf8' ) ;
453+ const classifications = compactTrueFlags ( classifyErrorText ( text ) ) ;
454+ const out = {
455+ path,
456+ bytes : buf . length ,
457+ sha256 : shortHash ( buf ) ,
458+ classifications,
459+ } ;
460+ if ( process . env . WINDSURFAPI_PROTO_TRACE_ERROR_STRINGS === '1' ) {
461+ out . preview = redactPreview ( text ) ;
462+ }
463+ return out ;
464+ }
465+
466+ function collectErrorStrings ( buf , path , out , depth = 0 ) {
467+ if ( ! Buffer . isBuffer ( buf ) || out . length >= positiveIntEnv ( 'WINDSURFAPI_PROTO_TRACE_ERROR_STRING_LIMIT' , 8 ) ) return ;
468+ const maxDepth = positiveIntEnv ( 'WINDSURFAPI_PROTO_TRACE_ERROR_DEPTH' , 4 ) ;
469+ if ( depth < maxDepth && looksLikeMessage ( buf ) ) {
470+ const before = out . length ;
471+ try {
472+ const fields = parseFields ( buf ) ;
473+ for ( const f of fields ) {
474+ if ( out . length >= positiveIntEnv ( 'WINDSURFAPI_PROTO_TRACE_ERROR_STRING_LIMIT' , 8 ) ) return ;
475+ if ( f . wireType !== 2 || ! Buffer . isBuffer ( f . value ) ) continue ;
476+ collectErrorStrings ( f . value , `${ path } .${ f . field } ` , out , depth + 1 ) ;
477+ }
478+ if ( out . length > before ) return ;
479+ } catch { }
480+ }
481+ if ( mostlyText ( buf ) ) {
482+ out . push ( summarizeErrorString ( path , buf ) ) ;
483+ }
484+ }
485+
486+ function summarizeErrorSource ( field , payload ) {
487+ const source = {
488+ field,
489+ bytes : payload . length ,
490+ sha256 : shortHash ( payload ) ,
491+ fieldNumbers : [ ] ,
492+ varints : [ ] ,
493+ strings : [ ] ,
494+ classifications : { } ,
495+ } ;
496+ try {
497+ const fields = parseFields ( payload ) ;
498+ source . fieldNumbers = fields . map ( f => f . field ) ;
499+ source . varints = fields
500+ . filter ( f => f . wireType === 0 )
501+ . slice ( 0 , 8 )
502+ . map ( f => ( {
503+ field : f . field ,
504+ value : typeof f . value === 'bigint' ? f . value . toString ( ) : f . value ,
505+ } ) ) ;
506+ collectErrorStrings ( payload , String ( field ) , source . strings ) ;
507+ for ( const s of source . strings ) {
508+ mergeErrorClassifications ( source . classifications , s . classifications ) ;
509+ }
510+ } catch ( err ) {
511+ source . parseError = err . message ;
512+ }
513+ source . classifications = compactTrueFlags ( source . classifications ) ;
514+ return source ;
515+ }
516+
517+ function summarizeErrorStep ( fields ) {
518+ const sources = [ ] ;
519+ for ( const fieldNum of [ 24 , 31 ] ) {
520+ for ( const f of getAllFields ( fields , fieldNum ) ) {
521+ if ( f . wireType !== 2 || ! Buffer . isBuffer ( f . value ) ) continue ;
522+ sources . push ( summarizeErrorSource ( fieldNum , f . value ) ) ;
523+ }
524+ }
525+ if ( ! sources . length ) return null ;
526+ const classifications = { } ;
527+ for ( const source of sources ) {
528+ mergeErrorClassifications ( classifications , source . classifications ) ;
529+ }
530+ return {
531+ sources,
532+ classifications : compactTrueFlags ( classifications ) ,
533+ } ;
534+ }
535+
426536function summarizeTrajectoryStep ( stepBuf , index ) {
427537 const fields = parseFields ( stepBuf ) ;
428538 const oneofFields = [ ] ;
@@ -445,6 +555,7 @@ function summarizeTrajectoryStep(stepBuf, index) {
445555 } ) ) ;
446556 const type = numberField ( fields , 1 ) ;
447557 const wrapper19 = type === 14 ? getField ( fields , 19 , 2 ) : null ;
558+ const errorStep = summarizeErrorStep ( fields ) ;
448559 return {
449560 index,
450561 type,
@@ -453,6 +564,7 @@ function summarizeTrajectoryStep(stepBuf, index) {
453564 nativeOneofs : oneofFields ,
454565 messageFields : interestingFields ,
455566 ...( wrapper19 ? { readWrapperField19 : summarizeReadWrapperField19 ( wrapper19 . value ) } : { } ) ,
567+ ...( errorStep ? { errorStep } : { } ) ,
456568 } ;
457569}
458570
0 commit comments