2
2
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
3
* SPDX-License-Identifier: Apache-2.0
4
4
*/
5
- import {
6
- ChatMessage ,
7
- Tool ,
8
- ToolResult ,
9
- ToolResultStatus ,
10
- UserInputMessage ,
11
- UserInputMessageContext ,
12
- } from '@amzn/codewhisperer-streaming'
5
+ import { ChatMessage , Tool , ToolResult , ToolResultStatus , ToolUse } from '@amzn/codewhisperer-streaming'
13
6
import { randomUUID } from '../../shared/crypto'
14
7
import { getLogger } from '../../shared/logger/logger'
15
8
import { tools } from '../constants'
@@ -105,168 +98,99 @@ export class ChatHistoryManager {
105
98
* message is set without tool results, then the user message will have cancelled tool results.
106
99
*/
107
100
public fixHistory ( newUserMessage : ChatMessage ) : ChatMessage {
108
- // Trim the conversation history if it exceeds the maximum length
109
- if ( this . history . length > MaxConversationHistoryLength ) {
110
- // Find the second oldest user message without tool results
111
- let indexToTrim : number | undefined
101
+ this . trimConversationHistory ( )
102
+ this . ensureLastMessageFromAssistant ( )
103
+ return this . handleToolUses ( newUserMessage )
104
+ }
112
105
113
- for ( let i = 1 ; i < this . history . length ; i ++ ) {
114
- const message = this . history [ i ]
115
- if ( message . userInputMessage ) {
116
- const userMessage = message . userInputMessage
117
- const ctx = userMessage . userInputMessageContext
118
- const hasNoToolResults = ctx && ( ! ctx . toolResults || ctx . toolResults . length === 0 )
119
- if ( hasNoToolResults && userMessage . content !== '' ) {
120
- indexToTrim = i
121
- break
122
- }
123
- }
124
- }
125
- if ( indexToTrim !== undefined ) {
126
- this . logger . debug ( `Removing the first ${ indexToTrim } elements in the history` )
127
- this . history . splice ( 0 , indexToTrim )
128
- } else {
129
- this . logger . debug ( 'No valid starting user message found in the history, clearing' )
130
- this . history = [ ]
131
- }
106
+ private trimConversationHistory ( ) : void {
107
+ if ( this . history . length <= MaxConversationHistoryLength ) {
108
+ return
132
109
}
133
110
134
- // Ensure the last message is from the assistant
135
- if ( this . history . length > 0 && this . history [ this . history . length - 1 ] . userInputMessage !== undefined ) {
136
- this . logger . debug ( 'Last message in history is from the user, dropping' )
137
- this . history . pop ( )
111
+ const indexToTrim = this . findIndexToTrim ( )
112
+ if ( indexToTrim !== undefined ) {
113
+ this . logger . debug ( `Removing the first ${ indexToTrim } elements in the history` )
114
+ this . history . splice ( 0 , indexToTrim )
115
+ } else {
116
+ this . logger . debug ( 'No valid starting user message found in the history, clearing' )
117
+ this . history = [ ]
138
118
}
119
+ }
139
120
140
- // If the last message from the assistant contains tool uses, ensure the next user message contains tool results
141
-
142
- const lastHistoryMessage = this . history [ this . history . length - 1 ]
143
-
144
- if (
145
- lastHistoryMessage &&
146
- ( lastHistoryMessage . assistantResponseMessage ||
147
- lastHistoryMessage . assistantResponseMessage !== undefined ) &&
148
- newUserMessage
149
- ) {
150
- const toolUses = lastHistoryMessage . assistantResponseMessage . toolUses
151
-
152
- if ( toolUses && toolUses . length > 0 ) {
153
- if ( newUserMessage . userInputMessage ) {
154
- if ( newUserMessage . userInputMessage . userInputMessageContext ) {
155
- const ctx = newUserMessage . userInputMessage . userInputMessageContext
156
-
157
- if ( ! ctx . toolResults || ctx . toolResults . length === 0 ) {
158
- ctx . toolResults = toolUses . map ( ( toolUse ) => ( {
159
- toolUseId : toolUse . toolUseId ,
160
- content : [
161
- {
162
- type : 'Text' ,
163
- text : 'Tool use was cancelled by the user' ,
164
- } ,
165
- ] ,
166
- status : ToolResultStatus . ERROR ,
167
- } ) )
168
- }
169
- } else {
170
- const toolResults = toolUses . map ( ( toolUse ) => ( {
171
- toolUseId : toolUse . toolUseId ,
172
- content : [
173
- {
174
- type : 'Text' ,
175
- text : 'Tool use was cancelled by the user' ,
176
- } ,
177
- ] ,
178
- status : ToolResultStatus . ERROR ,
179
- } ) )
180
-
181
- newUserMessage . userInputMessage . userInputMessageContext = {
182
- shellState : undefined ,
183
- envState : undefined ,
184
- toolResults : toolResults ,
185
- tools : this . tools . length === 0 ? undefined : [ ...this . tools ] ,
186
- }
187
-
188
- return newUserMessage
189
- }
190
- }
121
+ private findIndexToTrim ( ) : number | undefined {
122
+ for ( let i = 1 ; i < this . history . length ; i ++ ) {
123
+ const message = this . history [ i ]
124
+ if ( this . isValidUserMessageWithoutToolResults ( message ) ) {
125
+ return i
191
126
}
192
127
}
193
-
194
- // Always return the message to fix the TypeScript error
195
- return newUserMessage
128
+ return undefined
196
129
}
197
130
198
- /**
199
- * Adds tool results to the conversation.
200
- */
201
- addToolResults ( toolResults : ToolResult [ ] ) : void {
202
- const userInputMessageContext : UserInputMessageContext = {
203
- shellState : undefined ,
204
- envState : undefined ,
205
- toolResults : toolResults ,
206
- tools : this . tools . length === 0 ? undefined : [ ...this . tools ] ,
207
- }
208
-
209
- const msg : UserInputMessage = {
210
- content : '' ,
211
- userInputMessageContext : userInputMessageContext ,
131
+ private isValidUserMessageWithoutToolResults ( message : ChatMessage ) : boolean {
132
+ if ( ! message . userInputMessage ) {
133
+ return false
212
134
}
135
+ const ctx = message . userInputMessage . userInputMessageContext
136
+ return Boolean (
137
+ ctx && ( ! ctx . toolResults || ctx . toolResults . length === 0 ) && message . userInputMessage . content !== ''
138
+ )
139
+ }
213
140
214
- if ( this . lastUserMessage ?. userInputMessage ) {
215
- this . lastUserMessage . userInputMessage = msg
141
+ private ensureLastMessageFromAssistant ( ) : void {
142
+ if ( this . history . length > 0 && this . history [ this . history . length - 1 ] . userInputMessage !== undefined ) {
143
+ this . logger . debug ( 'Last message in history is from the user, dropping' )
144
+ this . history . pop ( )
216
145
}
217
146
}
218
147
219
- /**
220
- * Checks if the latest message in history is an Assistant Message.
221
- * If it is and doesn't have toolUse, it will be removed.
222
- * If it has toolUse, an assistantResponse message with cancelled tool status will be added.
223
- */
224
- public checkLatestAssistantMessage ( ) : void {
225
- if ( this . history . length === 0 ) {
226
- return
148
+ private handleToolUses ( newUserMessage : ChatMessage ) : ChatMessage {
149
+ const lastHistoryMessage = this . history [ this . history . length - 1 ]
150
+ if ( ! lastHistoryMessage || ! lastHistoryMessage . assistantResponseMessage || ! newUserMessage ) {
151
+ return newUserMessage
227
152
}
228
153
229
- const lastMessage = this . history [ this . history . length - 1 ]
230
-
231
- if ( lastMessage . assistantResponseMessage ) {
232
- const toolUses = lastMessage . assistantResponseMessage . toolUses
154
+ const toolUses = lastHistoryMessage . assistantResponseMessage . toolUses
155
+ if ( ! toolUses || toolUses . length === 0 ) {
156
+ return newUserMessage
157
+ }
233
158
234
- if ( ! toolUses || toolUses . length === 0 ) {
235
- // If there are no tool uses, remove the assistant message
236
- this . logger . debug ( 'Removing assistant message without tool uses' )
237
- this . history . pop ( )
238
- } else {
239
- // If there are tool uses, add cancelled tool results
240
- const toolResults = toolUses . map ( ( toolUse ) => ( {
241
- toolUseId : toolUse . toolUseId ,
242
- content : [
243
- {
244
- type : 'Text' ,
245
- text : 'Tool use was cancelled by the user' ,
246
- } ,
247
- ] ,
248
- status : ToolResultStatus . ERROR ,
249
- } ) )
159
+ return this . addToolResultsToUserMessage ( newUserMessage , toolUses )
160
+ }
250
161
251
- // Create a new user message with cancelled tool results
252
- const userInputMessageContext : UserInputMessageContext = {
253
- shellState : undefined ,
254
- envState : undefined ,
255
- toolResults : toolResults ,
256
- tools : this . tools . length === 0 ? undefined : [ ...this . tools ] ,
257
- }
162
+ private addToolResultsToUserMessage ( newUserMessage : ChatMessage , toolUses : ToolUse [ ] ) : ChatMessage {
163
+ if ( ! newUserMessage . userInputMessage ) {
164
+ return newUserMessage
165
+ }
258
166
259
- const userMessage : ChatMessage = {
260
- userInputMessage : {
261
- content : '' ,
262
- userInputMessageContext : userInputMessageContext ,
263
- } ,
264
- }
167
+ const toolResults = this . createToolResults ( toolUses )
265
168
266
- this . history . push ( this . formatChatHistoryMessage ( userMessage ) )
267
- this . logger . debug ( 'Added user message with cancelled tool results' )
169
+ if ( newUserMessage . userInputMessage . userInputMessageContext ) {
170
+ newUserMessage . userInputMessage . userInputMessageContext . toolResults = toolResults
171
+ } else {
172
+ newUserMessage . userInputMessage . userInputMessageContext = {
173
+ shellState : undefined ,
174
+ envState : undefined ,
175
+ toolResults : toolResults ,
176
+ tools : this . tools . length === 0 ? undefined : [ ...this . tools ] ,
268
177
}
269
178
}
179
+
180
+ return newUserMessage
181
+ }
182
+
183
+ private createToolResults ( toolUses : ToolUse [ ] ) : ToolResult [ ] {
184
+ return toolUses . map ( ( toolUse ) => ( {
185
+ toolUseId : toolUse . toolUseId ,
186
+ content : [
187
+ {
188
+ type : 'Text' ,
189
+ text : 'Tool use was cancelled by the user' ,
190
+ } ,
191
+ ] ,
192
+ status : ToolResultStatus . ERROR ,
193
+ } ) )
270
194
}
271
195
272
196
private formatChatHistoryMessage ( message : ChatMessage ) : ChatMessage {
@@ -283,4 +207,18 @@ export class ChatHistoryManager {
283
207
}
284
208
return message
285
209
}
210
+
211
+ public clearRecentHistory ( ) : void {
212
+ if ( this . history . length === 0 ) {
213
+ return
214
+ }
215
+
216
+ const lastHistoryMessage = this . history [ this . history . length - 1 ]
217
+
218
+ if ( lastHistoryMessage . userInputMessage ?. userInputMessageContext ) {
219
+ this . history . pop ( )
220
+ } else if ( lastHistoryMessage . assistantResponseMessage ) {
221
+ this . history . splice ( - 2 )
222
+ }
223
+ }
286
224
}
0 commit comments