@@ -75,6 +75,7 @@ import { loadLiveSearchPolicies } from '@/lib/sim-search/live/policy-store'
7575import { LIVE_SEARCH_PROVIDER_IDS } from '@/lib/sim-search/live/provider-catalog'
7676import { liveSearchGuidance } from '@/lib/sim-search/live/providers'
7777import { LiveReadError } from '@/lib/sim-search/live/read-error'
78+ import { encodedWindowEnd , READ_WINDOW_CHARACTERS } from '@/lib/sim-search/live/read-window'
7879import {
7980 encodeLiveReference ,
8081 includesLiveDocument ,
@@ -287,44 +288,25 @@ export const LIVE_NOT_STARTED_MESSAGE =
287288export const LIVE_OVER_ACCOUNT_LIMIT_MESSAGE = `Not searched: one search covers at most ${ MAX_ACCOUNTS } accounts. Search this source or account alone.`
288289/** Previews center on the query's longest matching term, like indexed passages. */
289290const PREVIEW_CHARACTERS = 1800
290- /** Characters one read returns at the default limit, the page budget indexed document reads use. */
291- const READ_WINDOW_CHARACTERS = 8000
292- /** The read limit {@link READ_WINDOW_CHARACTERS} is sized for, which is the default limit. */
293- const READ_WINDOW_LIMIT = 3
294291/**
295- * Characters the documents of one scaled read call share. Through four documents it keeps the
296- * call's result, with about a thousand characters of metadata per document, under the
297- * 50,000-character size at which MCP clients such as Claude Code save a tool result to a file
298- * rather than show it. From five documents the standard page per document is the larger, so
299- * those calls read exactly what they read without scaling.
292+ * Characters a scaled read returns for one document. Two documents at this size, with about a
293+ * thousand characters of metadata each, stay under the 50,000-character size at which MCP clients
294+ * such as Claude Code save a tool result to a file rather than show it; a call of more documents
295+ * shares that budget, as the MCP server's batched read sizes it.
300296 */
301- const SCALED_READ_CALL_CHARACTERS = 40_000
297+ const SCALED_READ_DOCUMENT_CHARACTERS = 20_000
302298
303299/**
304- * Where one read's window ends. A live document is one chunk, so a caller that scales the window
305- * gets a longer one when it asks for more chunks than the default, bounded by an even share of the
306- * call's budget across the documents it reads. The share counts the window as the result encodes
307- * it, since JSON escapes quotes, backslashes and line breaks, so a longer window is shortened until
308- * its encoded text fits. The window is never shorter than the standard page, which is also what a
309- * read at or below the default limit, or from any other caller, returns.
300+ * Where one read's window ends: the standard page, or for a caller that scales its window, up to
301+ * {@link SCALED_READ_DOCUMENT_CHARACTERS} as the result encodes it. A live document is one chunk,
302+ * so its window is sized by characters, not by the chunk limit. The window is never shorter than
303+ * the standard page.
310304 */
311- function readWindowEnd (
312- content : string ,
313- start : number ,
314- { limit, scaledWindow } : Pick < LiveReadInput , 'limit' | 'scaledWindow' >
315- ) : number {
305+ function readWindowEnd ( content : string , start : number , scaledWindow : boolean | undefined ) : number {
316306 const standardEnd = Math . min ( content . length , start + READ_WINDOW_CHARACTERS )
317- if ( ! scaledWindow || limit <= READ_WINDOW_LIMIT ) return standardEnd
318- const scaled = Math . floor ( ( READ_WINDOW_CHARACTERS * limit ) / READ_WINDOW_LIMIT )
319- const share = Math . floor ( SCALED_READ_CALL_CHARACTERS / Math . max ( 1 , scaledWindow . documentsInCall ) )
320- const budget = Math . min ( scaled , share )
321- let end = Math . min ( content . length , start + budget )
322- for ( let attempt = 0 ; attempt < 3 && end > standardEnd ; attempt ++ ) {
323- const encoded = JSON . stringify ( content . slice ( start , end ) ) . length - 2
324- if ( encoded <= budget ) return end
325- end = start + Math . floor ( ( ( end - start ) * budget ) / encoded )
326- }
327- return standardEnd
307+ return scaledWindow
308+ ? encodedWindowEnd ( content , start , SCALED_READ_DOCUMENT_CHARACTERS , standardEnd )
309+ : standardEnd
328310}
329311
330312/**
@@ -1017,11 +999,8 @@ export type LiveReadInput = ResourceOwner & {
1017999 documentId : string
10181000 filters ?: WorkspaceSearchFilters
10191001 limit : number
1020- /**
1021- * Lengthens the window for a limit above the default, sharing one call's budget across the
1022- * documents it reads, as {@link readWindowEnd} sizes it.
1023- */
1024- scaledWindow ?: { documentsInCall : number }
1002+ /** Lengthens the window past the standard page, as {@link readWindowEnd} sizes it. */
1003+ scaledWindow ?: boolean
10251004 startChunkIndex ?: number
10261005 startOffset ?: number
10271006 resultSecretRegistry : ResolvedSecretTraceRegistry
@@ -1167,7 +1146,7 @@ export const readLiveDocument = defineAuthorizedKnowledgeUseCase({
11671146 'validation' ,
11681147 'This document changed. Read again from the beginning'
11691148 )
1170- const end = readWindowEnd ( content , start , input )
1149+ const end = readWindowEnd ( content , start , input . scaledWindow )
11711150 return {
11721151 documentId : input . documentId ,
11731152 knowledgeBaseId : '' ,
0 commit comments