@@ -11,7 +11,7 @@ import {
1111 SymbolType ,
1212 TextDocument ,
1313} from '@amzn/codewhisperer-streaming'
14- import { ChatTriggerType , TriggerPayload } from '../model'
14+ import { AdditionalContentEntryAddition , ChatTriggerType , RelevantTextDocumentAddition , TriggerPayload } from '../model'
1515import { undefinedIfEmpty } from '../../../../shared/utilities/textUtilities'
1616import { getLogger } from '../../../../shared/logger/logger'
1717
@@ -39,110 +39,47 @@ const filePathSizeLimit = 4_000
3939const customerMessageSizeLimit = 4_000
4040
4141export function triggerPayloadToChatRequest ( triggerPayload : TriggerPayload ) : { conversationState : ConversationState } {
42- // truncate
42+ // Flexible truncation logic
4343 let remainingPayloadSize = 100_000
44- // Type A context: Preserving userInput as much as possible
45- if ( triggerPayload . message !== undefined ) {
46- if ( triggerPayload . message . length <= remainingPayloadSize ) {
47- remainingPayloadSize -= triggerPayload . message . length
48- } else {
49- triggerPayload . message = triggerPayload . message . substring ( 0 , remainingPayloadSize )
50- remainingPayloadSize = 0
51- }
52- }
53- // TODO: send truncation telemetry if needed
54- getLogger ( ) . debug ( `current request user input size: ${ triggerPayload . message ?. length } ` )
55-
56- // Type B1(prompts) context: Preserving prompts as much as possible
57- let totalPromptSize = 0
58- if ( triggerPayload . additionalContents !== undefined ) {
59- for ( const additionalContent of triggerPayload . additionalContents ) {
60- if ( additionalContent . type === 'prompt' && additionalContent . innerContext !== undefined ) {
61- if ( additionalContent . innerContext . length <= remainingPayloadSize ) {
62- remainingPayloadSize -= additionalContent . innerContext . length
63- } else {
64- additionalContent . innerContext = additionalContent . innerContext . substring ( 0 , remainingPayloadSize )
65- remainingPayloadSize = 0
66- }
67- totalPromptSize += additionalContent . innerContext . length
68- }
69- }
70- }
44+ const userInputTruncationInfo = preserveContexts ( triggerPayload , remainingPayloadSize , ChatContextType . UserInput )
7145
72- getLogger ( ) . debug ( `current request total prompts size: ${ totalPromptSize } ` )
46+ // Type B1(prompts) context: Preserving @prompt as much as possible
47+ const userSpecificPromptsTruncationInfo = preserveContexts (
48+ triggerPayload ,
49+ userInputTruncationInfo . remainingPayloadSize ,
50+ ChatContextType . UserSpecificPrompts
51+ )
7352
7453 // Type C context: Preserving current file context as much as possible
75- // truncate the text to keep texts in the middle instead of blindly truncating the tail
76- if ( triggerPayload . fileText !== undefined ) {
77- if ( triggerPayload . fileText . length <= remainingPayloadSize ) {
78- remainingPayloadSize -= triggerPayload . fileText . length
79- } else {
80- // Calculate the middle point
81- const middle = Math . floor ( triggerPayload . fileText . length / 2 )
82- // Calculate how much text we can take from each side of the middle
83- const halfRemaining = Math . floor ( remainingPayloadSize / 2 )
84- // Get text from around the middle point
85- const startPos = middle - halfRemaining
86- const endPos = middle + halfRemaining
87-
88- triggerPayload . fileText = triggerPayload . fileText . substring ( startPos , endPos )
89- remainingPayloadSize = 0
90- }
91- }
92- getLogger ( ) . debug ( `current request file content size: ${ triggerPayload . fileText ?. length } ` )
54+ const currentFileTruncationInfo = preserveContexts (
55+ triggerPayload ,
56+ userSpecificPromptsTruncationInfo . remainingPayloadSize ,
57+ ChatContextType . CurrentFile
58+ )
9359
9460 // Type B1(rules) context: Preserving rules as much as possible
95- let totalRulesSize = 0
96- if ( triggerPayload . additionalContents !== undefined ) {
97- for ( const additionalContent of triggerPayload . additionalContents ) {
98- if ( additionalContent . type === 'rule' && additionalContent . innerContext !== undefined ) {
99- if ( additionalContent . innerContext . length <= remainingPayloadSize ) {
100- remainingPayloadSize -= additionalContent . innerContext . length
101- } else {
102- additionalContent . innerContext = additionalContent . innerContext . substring ( 0 , remainingPayloadSize )
103- remainingPayloadSize = 0
104- }
105- totalRulesSize += additionalContent . innerContext . length
106- }
107- }
108- }
109-
110- getLogger ( ) . debug ( `current request rules size: ${ totalRulesSize } ` )
61+ const userSpecificRulesTruncationInfo = preserveContexts (
62+ triggerPayload ,
63+ currentFileTruncationInfo . remainingPayloadSize ,
64+ ChatContextType . UserSpecificRules
65+ )
11166
11267 // Type B2(explicit @files) context: Preserving files as much as possible
113- if ( triggerPayload . additionalContents !== undefined ) {
114- for ( const additionalContent of triggerPayload . additionalContents ) {
115- if ( additionalContent . type === 'file' && additionalContent . innerContext !== undefined ) {
116- if ( additionalContent . innerContext . length <= remainingPayloadSize ) {
117- remainingPayloadSize -= additionalContent . innerContext . length
118- } else {
119- additionalContent . innerContext = additionalContent . innerContext . substring ( 0 , remainingPayloadSize )
120- remainingPayloadSize = 0
121- }
122- }
123- }
124- }
68+ const userSpecificFilesTruncationInfo = preserveContexts (
69+ triggerPayload ,
70+ userSpecificRulesTruncationInfo . remainingPayloadSize ,
71+ ChatContextType . UserSpecificFiles
72+ )
12573
12674 // Type B3 @workspace context: Preserving workspace as much as possible
127- let totalWorkspaceSize = 0
128- if ( triggerPayload . relevantTextDocuments !== undefined ) {
129- for ( const relevantDocument of triggerPayload . relevantTextDocuments ) {
130- if ( relevantDocument . text !== undefined ) {
131- if ( relevantDocument . text . length <= remainingPayloadSize ) {
132- // remainingPayloadSize -= relevantDocument.text.length
133- } else {
134- // relevantDocument.text = relevantDocument.text.substring(0, remainingPayloadSize)
135- // remainingPayloadSize = 0
136- }
137- totalWorkspaceSize += relevantDocument . text . length
138- }
139- }
140- }
141-
142- getLogger ( ) . debug ( `current request workspace size: ${ totalWorkspaceSize } ` )
75+ const workspaceTruncationInfo = preserveContexts (
76+ triggerPayload ,
77+ userSpecificFilesTruncationInfo . remainingPayloadSize ,
78+ ChatContextType . Workspace
79+ )
14380
14481 getLogger ( ) . debug (
145- `current request total payload size: ${ ( triggerPayload . message ?. length ?? 0 ) + totalPromptSize + ( triggerPayload . fileText ?. length ?? 0 ) + totalRulesSize + totalWorkspaceSize } `
82+ `current request total payload size: ${ userInputTruncationInfo . sizeAfter + userSpecificFilesTruncationInfo . sizeAfter + currentFileTruncationInfo . sizeAfter + userSpecificFilesTruncationInfo . sizeAfter + workspaceTruncationInfo . sizeAfter } `
14683 )
14784
14885 // Filter out empty innerContext from additionalContents
@@ -247,3 +184,151 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { c
247184 } ,
248185 }
249186}
187+
188+ function preserveContexts (
189+ triggerPayload : TriggerPayload ,
190+ remainingPayloadSize : number ,
191+ contextType : ChatContextType
192+ ) : FlexibleTruncationInfo {
193+ const typeToContextMap = new Map <
194+ ChatContextType ,
195+ string | AdditionalContentEntryAddition [ ] | RelevantTextDocumentAddition [ ] | undefined
196+ > ( [
197+ [ ChatContextType . UserInput , triggerPayload . message ] ,
198+ [ ChatContextType . CurrentFile , triggerPayload . fileText ] ,
199+ [ ChatContextType . UserSpecificPrompts , triggerPayload . additionalContents ] ,
200+ [ ChatContextType . UserSpecificRules , triggerPayload . additionalContents ] ,
201+ [ ChatContextType . UserSpecificFiles , triggerPayload . additionalContents ] ,
202+ [ ChatContextType . Workspace , triggerPayload . relevantTextDocuments ] ,
203+ ] )
204+
205+ let truncationInfo = {
206+ remainingPayloadSize : remainingPayloadSize ,
207+ sizeBefore : 0 ,
208+ sizeAfter : 0 ,
209+ textAfter : '' ,
210+ }
211+
212+ const contexts = typeToContextMap . get ( contextType )
213+ if ( ! contexts ) {
214+ getLogger ( ) . debug (
215+ `Current request context size: type: ${ contextType } , before: ${ truncationInfo . sizeBefore } , after: ${ truncationInfo . sizeAfter } `
216+ )
217+ return truncationInfo
218+ }
219+
220+ switch ( contextType ) {
221+ case ChatContextType . UserInput :
222+ truncationInfo = truncate ( contexts as string , truncationInfo )
223+ triggerPayload . message = truncationInfo . textAfter
224+ break
225+ case ChatContextType . CurrentFile :
226+ truncationInfo = truncate ( contexts as string , truncationInfo )
227+ triggerPayload . fileText = truncationInfo . textAfter
228+ break
229+ case ChatContextType . UserSpecificPrompts :
230+ truncationInfo = truncateUserSpecificContexts (
231+ contexts as AdditionalContentEntryAddition [ ] ,
232+ truncationInfo ,
233+ 'prompt'
234+ )
235+ break
236+ case ChatContextType . UserSpecificRules :
237+ truncationInfo = truncateUserSpecificContexts (
238+ contexts as AdditionalContentEntryAddition [ ] ,
239+ truncationInfo ,
240+ 'rule'
241+ )
242+ break
243+ case ChatContextType . UserSpecificFiles :
244+ truncationInfo = truncateUserSpecificContexts (
245+ contexts as AdditionalContentEntryAddition [ ] ,
246+ truncationInfo ,
247+ 'file'
248+ )
249+ break
250+ case ChatContextType . Workspace :
251+ truncationInfo = truncateWorkspaceContexts ( contexts as RelevantTextDocumentAddition [ ] , truncationInfo )
252+ break
253+ default :
254+ getLogger ( ) . warn ( `Unexpected context type: ${ contextType } ` )
255+ return truncationInfo
256+ }
257+
258+ getLogger ( ) . debug (
259+ `Current request context size: type: ${ contextType } , before: ${ truncationInfo . sizeBefore } , after: ${ truncationInfo . sizeAfter } `
260+ )
261+ return truncationInfo
262+ }
263+
264+ function truncateUserSpecificContexts (
265+ contexts : AdditionalContentEntryAddition [ ] ,
266+ truncationInfo : FlexibleTruncationInfo ,
267+ type : string
268+ ) : FlexibleTruncationInfo {
269+ for ( const context of contexts ) {
270+ if ( context . type !== type || ! context . innerContext ) {
271+ continue
272+ }
273+ truncationInfo = truncate ( context . innerContext , truncationInfo )
274+ context . innerContext = truncationInfo . textAfter
275+ }
276+ return truncationInfo
277+ }
278+
279+ function truncateWorkspaceContexts (
280+ contexts : RelevantTextDocumentAddition [ ] ,
281+ truncationInfo : FlexibleTruncationInfo
282+ ) : FlexibleTruncationInfo {
283+ for ( const context of contexts ) {
284+ if ( ! context . text ) {
285+ continue
286+ }
287+ truncationInfo = truncate ( context . text , truncationInfo )
288+ context . text = truncationInfo . textAfter
289+ }
290+ return truncationInfo
291+ }
292+
293+ function truncate (
294+ textBefore : string ,
295+ truncationInfo : FlexibleTruncationInfo ,
296+ isCurrentFile : Boolean = false
297+ ) : FlexibleTruncationInfo {
298+ const sizeBefore = truncationInfo . sizeBefore + textBefore . length
299+
300+ // for all other types of contexts, we simply truncate the tail,
301+ // for current file context, since it's expanded from the middle context, we truncate head and tail to preserve middle context
302+ const middle = Math . floor ( textBefore . length / 2 )
303+ const halfRemaining = Math . floor ( truncationInfo . remainingPayloadSize / 2 )
304+ const startPos = isCurrentFile ? middle - halfRemaining : 0
305+ const endPos = isCurrentFile
306+ ? middle + ( truncationInfo . remainingPayloadSize - halfRemaining )
307+ : Math . min ( textBefore . length , truncationInfo . remainingPayloadSize )
308+ const textAfter = textBefore . substring ( startPos , endPos )
309+
310+ const sizeAfter = truncationInfo . sizeAfter + textAfter . length
311+ const remainingPayloadSize = truncationInfo . remainingPayloadSize - textAfter . length
312+ return {
313+ remainingPayloadSize,
314+ sizeBefore,
315+ sizeAfter,
316+ textAfter,
317+ }
318+ }
319+
320+ type FlexibleTruncationInfo = {
321+ readonly remainingPayloadSize : number
322+ readonly sizeBefore : number
323+ readonly sizeAfter : number
324+ readonly textAfter : string
325+ }
326+
327+ export enum ChatContextType {
328+ UserInput = 'userInput' ,
329+ CurrentFile = 'currentFile' ,
330+ UserSpecificPrompts = 'userSpecificPrompts' ,
331+ UserSpecificRules = 'userSpecificRules' ,
332+ UserSpecificFiles = 'userSpecificFiles' ,
333+ Workspace = 'workspace' ,
334+ }
0 commit comments