@@ -806,39 +806,96 @@ export function isSkippableMicrosoftGraphFolderError(
806806 */
807807export const CONNECTOR_TEXT_DOCUMENT_MAX_BYTES = 12 * 1024 * 1024
808808
809- const TRUNCATION_NOTICE = '[Truncated: the indexed text reached the size limit]'
809+ const TRAILING_TRUNCATION_NOTICE = '[Truncated: the indexed text reached the size limit]'
810+ const LEADING_TRUNCATION_NOTICE = '[Truncated: earlier text was left out to fit the size limit]'
810811
811812/**
812- * Accumulates newline-joined text under a byte ceiling. A record is appended
813- * whole or not at all, so a truncated document never ends mid-message, and the
814- * output carries a notice when something was left out.
813+ * Which end of the stream survives when it does not fit: `first` keeps what
814+ * was pushed first and refuses the rest (a mail thread, whose root message is
815+ * the context), `last` keeps what was pushed last and lets older records go
816+ * (a chat transcript, whose newest messages are the ones people search for).
817+ */
818+ export type BoundedLinesKeep = 'first' | 'last'
819+
820+ interface BoundedRecord {
821+ lines : string [ ]
822+ bytes : number
823+ }
824+
825+ function byteSize ( lines : readonly string [ ] ) : number {
826+ let size = 0
827+ for ( const line of lines ) size += Buffer . byteLength ( line , 'utf8' ) + 1
828+ return size
829+ }
830+
831+ /**
832+ * Accumulates newline-joined text under a byte ceiling. A record is kept
833+ * whole or not at all, so a truncated document never ends mid-message, and
834+ * the output carries a notice where something was left out.
815835 */
816836export class BoundedLines {
817- private readonly lines : string [ ] = [ ]
818- private bytes = 0
837+ private readonly pinned : string [ ] = [ ]
838+ private readonly records : BoundedRecord [ ] = [ ]
839+ private pinnedBytes = 0
840+ private recordBytes = 0
819841 private truncated = false
820842
821- constructor ( private readonly maxBytes = CONNECTOR_TEXT_DOCUMENT_MAX_BYTES ) { }
843+ constructor (
844+ private readonly maxBytes = CONNECTOR_TEXT_DOCUMENT_MAX_BYTES ,
845+ private readonly keep : BoundedLinesKeep = 'first'
846+ ) { }
847+
848+ /** Lines that open the document and are never let go, such as its header; counted against the ceiling. */
849+ pin ( ...lines : string [ ] ) : void {
850+ this . pinned . push ( ...lines )
851+ this . pinnedBytes += byteSize ( lines )
852+ }
853+
854+ /** Records currently kept. */
855+ get count ( ) : number {
856+ return this . records . length
857+ }
822858
823859 /**
824- * Appends the lines together when they fit; otherwise marks the document
825- * truncated, appends nothing, and returns false so the caller stops.
860+ * Appends the lines as one record and returns whether it was kept. Keeping
861+ * the first, a record that does not fit is refused, and so is every later
862+ * one, so a caller can stop. Keeping the last, a record is refused only when
863+ * it cannot fit on its own, and appending it lets the oldest records go
864+ * until the rest fits, so a caller carries on.
826865 */
827866 push ( ...lines : string [ ] ) : boolean {
828- if ( this . truncated ) return false
829- let size = 0
830- for ( const line of lines ) size += Buffer . byteLength ( line , 'utf8' ) + 1
831- if ( this . bytes + size > this . maxBytes ) {
867+ const bytes = byteSize ( lines )
868+ if ( this . keep === 'first' ) {
869+ if ( this . truncated ) return false
870+ if ( this . pinnedBytes + this . recordBytes + bytes > this . maxBytes ) {
871+ this . truncated = true
872+ return false
873+ }
874+ this . records . push ( { lines, bytes } )
875+ this . recordBytes += bytes
876+ return true
877+ }
878+ if ( this . pinnedBytes + bytes > this . maxBytes ) {
832879 this . truncated = true
833880 return false
834881 }
835- this . lines . push ( ...lines )
836- this . bytes += size
882+ this . records . push ( { lines, bytes } )
883+ this . recordBytes += bytes
884+ while ( this . pinnedBytes + this . recordBytes > this . maxBytes ) {
885+ const oldest = this . records . shift ( )
886+ if ( ! oldest ) break
887+ this . recordBytes -= oldest . bytes
888+ this . truncated = true
889+ }
837890 return true
838891 }
839892
840- /** Joins the accepted lines, ending with the truncation notice when a push was refused . */
893+ /** Joins the kept lines, with the truncation notice where records were left out . */
841894 join ( ) : string {
842- return this . truncated ? [ ...this . lines , TRUNCATION_NOTICE ] . join ( '\n' ) : this . lines . join ( '\n' )
895+ const body = this . records . flatMap ( ( record ) => record . lines )
896+ if ( ! this . truncated ) return [ ...this . pinned , ...body ] . join ( '\n' )
897+ return this . keep === 'first'
898+ ? [ ...this . pinned , ...body , TRAILING_TRUNCATION_NOTICE ] . join ( '\n' )
899+ : [ ...this . pinned , LEADING_TRUNCATION_NOTICE , ...body ] . join ( '\n' )
843900 }
844901}
0 commit comments