Skip to content

Commit c43d50f

Browse files
committed
fix(chat): prune history if it's too large
1 parent b6d4754 commit c43d50f

File tree

1 file changed

+77
-15
lines changed

1 file changed

+77
-15
lines changed

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

Lines changed: 77 additions & 15 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,81 @@ 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+
count += message.userInputMessage.userInputMessageContext?.editorState?.document?.text?.length ?? 0
152+
if (message.userInputMessage.userInputMessageContext?.editorState?.relevantDocuments) {
153+
for (const document of message.userInputMessage.userInputMessageContext.editorState
154+
.relevantDocuments) {
155+
count += document.text?.length ?? 0
156+
}
157+
}
158+
if (message.userInputMessage.userInputMessageContext?.additionalContext) {
159+
for (const document of message.userInputMessage.userInputMessageContext.additionalContext) {
160+
count += document.innerContext?.length ?? 0
161+
}
162+
}
163+
}
164+
165+
// Count characters in assistant messages
166+
if (message.assistantResponseMessage?.content) {
167+
count += message.assistantResponseMessage.content.length
168+
}
169+
170+
try {
171+
// Count characters in tool uses and results
172+
if (message.assistantResponseMessage?.toolUses) {
173+
for (const toolUse of message.assistantResponseMessage.toolUses) {
174+
count += JSON.stringify(toolUse).length
175+
}
176+
}
177+
178+
if (message.userInputMessage?.userInputMessageContext?.toolResults) {
179+
for (const toolResult of message.userInputMessage.userInputMessageContext.toolResults) {
180+
count += JSON.stringify(toolResult).length
181+
}
182+
}
183+
} catch (error: any) {
184+
this.logger.error(`Error calculating character count for tool uses/results: ${error.message}`)
185+
}
132186
}
187+
this.logger.debug(`Current history characters: ${count}`)
188+
return count
133189
}
134190

135191
private findIndexToTrim(): number | undefined {
136-
for (let i = 1; i < this.history.length; i++) {
192+
for (let i = 2; i < this.history.length; i++) {
137193
const message = this.history[i]
138194
if (this.isValidUserMessageWithoutToolResults(message)) {
139195
return i
@@ -162,6 +218,12 @@ export class ChatHistoryManager {
162218
private ensureCurrentMessageIsValid(newUserMessage: ChatMessage): void {
163219
const lastHistoryMessage = this.history[this.history.length - 1]
164220
if (!lastHistoryMessage) {
221+
if (newUserMessage.userInputMessage?.userInputMessageContext?.toolResults) {
222+
this.logger.error(
223+
'No history message found, but new user message has tool results. This is unexpected.'
224+
)
225+
newUserMessage.userInputMessage.userInputMessageContext.toolResults = undefined
226+
}
165227
return
166228
}
167229

0 commit comments

Comments
 (0)