Skip to content

Commit 90dfc71

Browse files
authored
fix(chat): Prune history if it's too large, ignore log window as active file (aws#6989)
## Problem ``` An error occurred while processing your request.This error is reported to the team automatically. We will attempt to fix it as soon as possible.Details: Input is too long. ``` - A bug where status code is not being populated in error message - A bug where output log window can be considered as active file which can cause waste of tokens - A bug where toolUse JSON.parse is not being handled correctly ## Solution - Prune history if it's too large - Fix status code bug in error message - Fix active file bug - Fix JSON.parse handling --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 8ff5e93 commit 90dfc71

File tree

5 files changed

+107
-31
lines changed

5 files changed

+107
-31
lines changed

packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): {
148148
},
149149
}
150150
}
151+
152+
if (document.relativeFilePath === 'amazonwebservices.amazon-q-vscode.Amazon Q Logs') {
153+
getLogger().debug('Active file is Amazon Q Logs, filter it out in the chat request')
154+
document = undefined
155+
cursorState = undefined
156+
}
151157
}
152158

153159
// service will throw validation exception if string is empty

packages/core/src/codewhispererChat/controllers/chat/controller.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,10 @@ export class ChatController {
698698
this.messenger.sendAsyncEventProgress(tabID, true, '')
699699
const session = this.sessionStorage.getSession(tabID)
700700
const toolUseWithError = session.toolUseWithError
701-
if (!toolUseWithError || !toolUseWithError.toolUse || !toolUseWithError.toolUse.input) {
701+
getLogger().debug(
702+
`processToolUseMessage: ${toolUseWithError?.toolUse.name}:${toolUseWithError?.toolUse.toolUseId} with error: ${toolUseWithError?.error}`
703+
)
704+
if (!toolUseWithError || !toolUseWithError.toolUse) {
702705
// Turn off AgentLoop flag if there's no tool use
703706
this.sessionStorage.setAgentLoopInProgress(tabID, false)
704707
return
@@ -780,7 +783,7 @@ export class ChatController {
780783
toolResults: toolResults,
781784
profile: AuthUtil.instance.regionProfileManager.activeRegionProfile,
782785
origin: Origin.IDE,
783-
context: session.context ?? [],
786+
context: [],
784787
relevantTextDocuments: [],
785788
additionalContents: [],
786789
documentReferences: [],
@@ -996,6 +999,7 @@ export class ChatController {
996999
private processException(e: any, tabID: string) {
9971000
let errorMessage = ''
9981001
let requestID = undefined
1002+
let statusCode = undefined
9991003
const defaultMessage = 'Failed to get response'
10001004
if (typeof e === 'string') {
10011005
errorMessage = e.toUpperCase()
@@ -1005,6 +1009,7 @@ export class ChatController {
10051009
} else if (e instanceof CodeWhispererStreamingServiceException) {
10061010
errorMessage = e.message
10071011
requestID = e.$metadata.requestId
1012+
statusCode = e.$metadata.httpStatusCode
10081013
} else if (e instanceof Error) {
10091014
errorMessage = e.message
10101015
}
@@ -1014,11 +1019,11 @@ export class ChatController {
10141019
this.sessionStorage.setAgentLoopInProgress(tabID, false)
10151020
}
10161021

1017-
this.messenger.sendErrorMessage(errorMessage, tabID, requestID)
1022+
this.messenger.sendErrorMessage(errorMessage, tabID, requestID, statusCode)
10181023
getLogger().error(`error: ${errorMessage} tabID: ${tabID} requestID: ${requestID}`)
10191024

10201025
this.sessionStorage.deleteSession(tabID)
1021-
this.chatHistoryStorage.getTabHistory(tabID).clearRecentHistory()
1026+
this.chatHistoryStorage.getTabHistory(tabID).clear()
10221027
}
10231028

10241029
private async processContextMenuCommand(command: EditorContextCommand) {

packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,9 @@ export class Messenger {
269269
}
270270

271271
if (cwChatEvent.toolUseEvent?.stop) {
272-
let toolError = undefined
272+
toolUse.toolUseId = cwChatEvent.toolUseEvent.toolUseId ?? ''
273+
toolUse.name = cwChatEvent.toolUseEvent.name ?? ''
273274
try {
274-
toolUse.toolUseId = cwChatEvent.toolUseEvent.toolUseId ?? ''
275-
toolUse.name = cwChatEvent.toolUseEvent.name ?? ''
276275
toolUse.input = JSON.parse(toolUseInput)
277276
const availableToolsNames = (session.pairProgrammingModeOn ? tools : noWriteTools).map(
278277
(item) => item.toolSpecification?.name
@@ -332,6 +331,10 @@ export class Messenger {
332331
) {
333332
session.setMessageIdToUpdateListDirectory(toolUse.toolUseId)
334333
}
334+
getLogger().debug(
335+
`SetToolUseWithError: ${toolUse.name}:${toolUse.toolUseId} with no error`
336+
)
337+
session.setToolUseWithError({ toolUse, error: undefined })
335338

336339
if (!validation.requiresAcceptance) {
337340
// Need separate id for read tool and safe bash command execution as 'run-shell-command' id is required to state in cwChatConnector.ts which will impact generic tool execution.
@@ -350,12 +353,19 @@ export class Messenger {
350353
}
351354
}
352355
} else {
353-
toolError = new Error('Tool not found')
356+
throw new Error('Tool not found')
354357
}
355358
} catch (error: any) {
356-
toolError = error
357-
} finally {
358-
session.setToolUseWithError({ toolUse, error: toolError })
359+
getLogger().error(
360+
`toolUseId: ${toolUse.toolUseId}, toolUseName: ${toolUse.name}, error: ${error}`
361+
)
362+
session.setToolUseWithError({ toolUse, error })
363+
// trigger processToolUseMessage to handle the error
364+
this.dispatcher.sendCustomFormActionMessage(
365+
new CustomFormActionMessage(tabID, {
366+
id: 'generic-tool-execution',
367+
})
368+
)
359369
}
360370
// TODO: Add a spinner component for fsWrite, previous implementation is causing lag in mynah UX.
361371
}
@@ -571,12 +581,17 @@ export class Messenger {
571581
)
572582
}
573583

574-
public sendErrorMessage(errorMessage: string | undefined, tabID: string, requestID: string | undefined) {
584+
public sendErrorMessage(
585+
errorMessage: string | undefined,
586+
tabID: string,
587+
requestID: string | undefined,
588+
statusCode?: number
589+
) {
575590
this.showChatExceptionMessage(
576591
{
577592
errorMessage: errorMessage,
578593
sessionID: undefined,
579-
statusCode: undefined,
594+
statusCode: statusCode?.toString(),
580595
},
581596
tabID,
582597
requestID

packages/core/src/codewhispererChat/storages/chatHistory.ts

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { ChatMessage, ToolResult, ToolResultStatus, ToolUse } from '@amzn/codewh
66
import { randomUUID } from '../../shared/crypto'
77
import { getLogger } from '../../shared/logger/logger'
88

9-
// Maximum number of messages to keep in history
10-
const MaxConversationHistoryLength = 100
9+
// Maximum number of characters to keep in history
10+
const MaxConversationHistoryCharacters = 600_000
1111

1212
/**
1313
* ChatHistoryManager handles the storage and manipulation of chat history
@@ -70,9 +70,6 @@ export class ChatHistoryManager {
7070
*/
7171
public appendUserMessage(newMessage: ChatMessage): void {
7272
this.lastUserMessage = newMessage
73-
if (!newMessage.userInputMessage?.content || newMessage.userInputMessage?.content.trim() === '') {
74-
this.logger.warn('input must not be empty when adding new messages')
75-
}
7673
this.history.push(this.formatChatHistoryMessage(this.lastUserMessage))
7774
}
7875

@@ -118,22 +115,69 @@ export class ChatHistoryManager {
118115
this.clearRecentHistory()
119116
}
120117

121-
if (this.history.length <= MaxConversationHistoryLength) {
122-
return
118+
// Check if we need to trim based on character count
119+
const totalCharacters = this.calculateHistoryCharacterCount()
120+
if (totalCharacters > MaxConversationHistoryCharacters) {
121+
this.logger.debug(
122+
`History size (${totalCharacters} chars) exceeds limit of ${MaxConversationHistoryCharacters} chars`
123+
)
124+
// Keep removing messages from the beginning until we're under the limit
125+
do {
126+
// Find the next valid user message to start from
127+
const indexToTrim = this.findIndexToTrim()
128+
if (indexToTrim !== undefined && indexToTrim > 0) {
129+
this.logger.debug(
130+
`Removing the first ${indexToTrim} elements in the history due to character count limit`
131+
)
132+
this.history.splice(0, indexToTrim)
133+
} else {
134+
// If we can't find a valid starting point, reset it
135+
this.logger.debug('Could not find a valid point to trim, reset history to reduce character count')
136+
this.history = []
137+
}
138+
} while (
139+
this.calculateHistoryCharacterCount() > MaxConversationHistoryCharacters &&
140+
this.history.length > 2
141+
)
123142
}
143+
}
124144

125-
const indexToTrim = this.findIndexToTrim()
126-
if (indexToTrim !== undefined) {
127-
this.logger.debug(`Removing the first ${indexToTrim} elements in the history`)
128-
this.history.splice(0, indexToTrim)
129-
} else {
130-
this.logger.debug('No valid starting user message found in the history, clearing')
131-
this.history = []
145+
private calculateHistoryCharacterCount(): number {
146+
let count = 0
147+
for (const message of this.history) {
148+
// Count characters in user messages
149+
if (message.userInputMessage?.content) {
150+
count += message.userInputMessage.content.length
151+
}
152+
153+
// Count characters in assistant messages
154+
if (message.assistantResponseMessage?.content) {
155+
count += message.assistantResponseMessage.content.length
156+
}
157+
158+
try {
159+
// Count characters in tool uses and results
160+
if (message.assistantResponseMessage?.toolUses) {
161+
for (const toolUse of message.assistantResponseMessage.toolUses) {
162+
count += JSON.stringify(toolUse).length
163+
}
164+
}
165+
166+
if (message.userInputMessage?.userInputMessageContext?.toolResults) {
167+
for (const toolResult of message.userInputMessage.userInputMessageContext.toolResults) {
168+
count += JSON.stringify(toolResult).length
169+
}
170+
}
171+
} catch (error: any) {
172+
this.logger.error(`Error calculating character count for tool uses/results: ${error.message}`)
173+
}
132174
}
175+
this.logger.debug(`Current history characters: ${count}`)
176+
return count
133177
}
134178

135179
private findIndexToTrim(): number | undefined {
136-
for (let i = 1; i < this.history.length; i++) {
180+
for (let i = 2; i < this.history.length; i++) {
137181
const message = this.history[i]
138182
if (this.isValidUserMessageWithoutToolResults(message)) {
139183
return i
@@ -162,6 +206,12 @@ export class ChatHistoryManager {
162206
private ensureCurrentMessageIsValid(newUserMessage: ChatMessage): void {
163207
const lastHistoryMessage = this.history[this.history.length - 1]
164208
if (!lastHistoryMessage) {
209+
if (newUserMessage.userInputMessage?.userInputMessageContext?.toolResults) {
210+
this.logger.debug('No history message found, but new user message has tool results.')
211+
newUserMessage.userInputMessage.userInputMessageContext.toolResults = undefined
212+
// tool results are empty, so content must not be empty
213+
newUserMessage.userInputMessage.content = 'Conversation history was too large, so it was cleared.'
214+
}
165215
return
166216
}
167217

@@ -198,8 +248,8 @@ export class ChatHistoryManager {
198248
userInputMessage: {
199249
...message.userInputMessage,
200250
userInputMessageContext: {
201-
...message.userInputMessage.userInputMessageContext,
202-
tools: undefined,
251+
// Only keep toolResults in history
252+
toolResults: message.userInputMessage.userInputMessageContext?.toolResults,
203253
},
204254
},
205255
}

packages/core/src/codewhispererChat/tools/chatStream.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class ChatStream extends Writable {
3333
) {
3434
super()
3535
this.logger.debug(
36-
`ChatStream created for tabID: ${tabID}, triggerID: ${triggerID}, session: ${session.readFiles}, emitEvent to mynahUI: ${emitEvent}`
36+
`ChatStream created for tabID: ${tabID}, triggerID: ${triggerID}, emitEvent to mynahUI: ${emitEvent}`
3737
)
3838
if (!emitEvent) {
3939
return

0 commit comments

Comments
 (0)