@@ -3,8 +3,8 @@ import { encodedWindowEnd, READ_WINDOW_CHARACTERS } from '@/lib/sim-search/live/
33/**
44 * Characters of document text one batched read call returns. With about a thousand characters of
55 * metadata per document, it keeps the call under the 50,000-character size at which MCP clients
6- * such as Claude Code save a tool result to a file rather than show it. More than five long
7- * documents exceed it, since none is cut below its standard page .
6+ * such as Claude Code save a tool result to a file rather than show it. A call whose standard
7+ * pages alone exceed it returns those pages, as a read without sharing would .
88 */
99const READ_CALL_CHARACTERS = 40_000
1010
@@ -18,34 +18,43 @@ const encodedLength = (text: string) => JSON.stringify(text).length - 2
1818
1919const isWindowedRead = ( document : object ) : document is WindowedRead => 'chunks' in document
2020
21+ /**
22+ * A read's text as JSON encodes it, and the part of that it always keeps: its standard page, or
23+ * all of it when it is not a single chunk this budget can shorten.
24+ */
25+ function encodedSize ( read : object ) : { length : number ; kept : number } {
26+ if ( ! isWindowedRead ( read ) ) return { length : 0 , kept : 0 }
27+ const length = read . chunks . reduce ( ( total , chunk ) => total + encodedLength ( chunk . content ) , 0 )
28+ const [ chunk ] = read . chunks
29+ if ( ! chunk || read . chunks . length !== 1 ) return { length, kept : length }
30+ return { length, kept : encodedLength ( chunk . content . slice ( 0 , READ_WINDOW_CHARACTERS ) ) }
31+ }
32+
2133/**
2234 * Shares one call's text budget across the documents it read; an entry without text, such as a
23- * failed read, passes through. Documents shorter than an even share keep all their text and leave
24- * the rest to the longer ones, which are cut to an equal share and continue through `next`. No
25- * document is cut below the standard page .
35+ * failed read, passes through. Every document keeps its standard page, and the budget those pages
36+ * leave is shared as further text: documents needing less than an even share keep all theirs and
37+ * leave the rest to the longer ones, which are cut to an equal share and continue through `next` .
2638 */
2739export function shareReadBudget < T extends object > ( reads : readonly T [ ] ) : T [ ] {
28- const lengths = reads . map ( ( read ) =>
29- isWindowedRead ( read )
30- ? read . chunks . reduce ( ( total , chunk ) => total + encodedLength ( chunk . content ) , 0 )
31- : 0
32- )
33- if ( lengths . reduce ( ( total , length ) => total + length , 0 ) <= READ_CALL_CHARACTERS )
40+ const sizes = reads . map ( encodedSize )
41+ if ( sizes . reduce ( ( total , size ) => total + size . length , 0 ) <= READ_CALL_CHARACTERS )
3442 return [ ...reads ]
35- let remaining = READ_CALL_CHARACTERS
43+ const extras = sizes . map ( ( size ) => size . length - size . kept )
44+ let remaining = READ_CALL_CHARACTERS - sizes . reduce ( ( total , size ) => total + size . kept , 0 )
3645 let left = reads . length
3746 let cap = 0
38- for ( const length of [ ...lengths ] . sort ( ( a , b ) => a - b ) ) {
39- const share = Math . floor ( remaining / left )
40- if ( length > share ) {
47+ for ( const extra of [ ...extras ] . sort ( ( a , b ) => a - b ) ) {
48+ const share = Math . max ( 0 , Math . floor ( remaining / left ) )
49+ if ( extra > share ) {
4150 cap = share
4251 break
4352 }
44- remaining -= length
53+ remaining -= extra
4554 left --
4655 }
4756 return reads . map ( ( read , index ) =>
48- isWindowedRead ( read ) && lengths [ index ] > cap ? cut ( read , cap ) : read
57+ isWindowedRead ( read ) && extras [ index ] > cap ? cut ( read , sizes [ index ] . kept + cap ) : read
4958 )
5059}
5160
0 commit comments