Skip to content

Commit ac7dbcc

Browse files
laileni-awsctlai95
andauthored
Merging Tai Agentic Tool exe Changes (#14)
* wip * wip * wip --------- Co-authored-by: Tai Lai <[email protected]>
1 parent bd37885 commit ac7dbcc

File tree

16 files changed

+1973
-127
lines changed

16 files changed

+1973
-127
lines changed

packages/core/src/codewhisperer/client/user-service-2.json

Lines changed: 682 additions & 35 deletions
Large diffs are not rendered by default.

packages/core/src/codewhispererChat/clients/chat/v0/chat.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
*/
55

66
import { SendMessageCommandOutput, SendMessageRequest } from '@amzn/amazon-q-developer-streaming-client'
7-
import { GenerateAssistantResponseCommandOutput, GenerateAssistantResponseRequest } from '@amzn/codewhisperer-streaming'
7+
import {
8+
ChatMessage,
9+
GenerateAssistantResponseCommandOutput,
10+
GenerateAssistantResponseRequest,
11+
ToolUse,
12+
} from '@amzn/codewhisperer-streaming'
813
import * as vscode from 'vscode'
914
import { ToolkitError } from '../../../../shared/errors'
1015
import { createCodeWhispererChatStreamingClient } from '../../../../shared/clients/codewhispererChatClient'
@@ -13,6 +18,8 @@ import { UserWrittenCodeTracker } from '../../../../codewhisperer/tracker/userWr
1318

1419
export class ChatSession {
1520
private sessionId?: string
21+
private _toolUse: ToolUse | undefined
22+
private _chatHistory: ChatMessage[] = []
1623

1724
contexts: Map<string, { first: number; second: number }[]> = new Map()
1825
// TODO: doesn't handle the edge case when two files share the same relativePath string but from different root
@@ -21,6 +28,12 @@ export class ChatSession {
2128
public get sessionIdentifier(): string | undefined {
2229
return this.sessionId
2330
}
31+
public get toolUse(): ToolUse | undefined {
32+
return this._toolUse
33+
}
34+
public get chatHistory(): ChatMessage[] {
35+
return this._chatHistory
36+
}
2437

2538
public tokenSource!: vscode.CancellationTokenSource
2639

@@ -35,6 +48,31 @@ export class ChatSession {
3548
public setSessionID(id?: string) {
3649
this.sessionId = id
3750
}
51+
public setToolUse(toolUse: ToolUse | undefined) {
52+
this._toolUse = toolUse
53+
}
54+
public pushToChatHistory(message: ChatMessage | undefined) {
55+
if (message === undefined) {
56+
return
57+
}
58+
this._chatHistory.push(this.formatChatHistoryMessage(message))
59+
}
60+
61+
private formatChatHistoryMessage(message: ChatMessage): ChatMessage {
62+
if (message.userInputMessage !== undefined) {
63+
return {
64+
userInputMessage: {
65+
...message.userInputMessage,
66+
userInputMessageContext: {
67+
...message.userInputMessage.userInputMessageContext,
68+
tools: undefined,
69+
},
70+
},
71+
}
72+
}
73+
return message
74+
}
75+
3876
async chatIam(chatRequest: SendMessageRequest): Promise<SendMessageCommandOutput> {
3977
const client = await createQDeveloperStreamingClient()
4078

@@ -60,11 +98,13 @@ export class ChatSession {
6098
async chatSso(chatRequest: GenerateAssistantResponseRequest): Promise<GenerateAssistantResponseCommandOutput> {
6199
const client = await createCodeWhispererChatStreamingClient()
62100

63-
if (this.sessionId !== undefined && chatRequest.conversationState !== undefined) {
101+
if (this.sessionId !== '' && this.sessionId !== undefined && chatRequest.conversationState !== undefined) {
64102
chatRequest.conversationState.conversationId = this.sessionId
65103
}
66104

67105
const response = await client.generateAssistantResponse(chatRequest)
106+
// eslint-disable-next-line aws-toolkits/no-console-log
107+
console.log(response.$metadata.requestId)
68108
if (!response.generateAssistantResponseResponse) {
69109
throw new ToolkitError(
70110
`Empty chat response. Session id: ${this.sessionId} Request ID: ${response.$metadata.requestId}`

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

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@
44
*/
55

66
import {
7+
ChatMessage,
78
ConversationState,
89
CursorState,
910
DocumentSymbol,
1011
RelevantTextDocument,
1112
SymbolType,
1213
TextDocument,
14+
Tool,
1315
} from '@amzn/codewhisperer-streaming'
1416
import { ChatTriggerType, TriggerPayload } from '../model'
1517
import { undefinedIfEmpty } from '../../../../shared/utilities/textUtilities'
18+
import toolsJson from '../../../tools/tool_index.json'
19+
import { tryGetCurrentWorkingDirectory } from '../../../../shared/utilities/workspaceUtils'
1620

1721
const fqnNameSizeDownLimit = 1
1822
const fqnNameSizeUpLimit = 256
@@ -126,3 +130,114 @@ export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { c
126130
},
127131
}
128132
}
133+
134+
export function triggerPayloadToAgenticChatRequest(
135+
triggerPayload: TriggerPayload,
136+
chatHistory?: ChatMessage[]
137+
): { conversationState: ConversationState } {
138+
let document: TextDocument | undefined = undefined
139+
let cursorState: CursorState | undefined = undefined
140+
141+
if (triggerPayload.filePath !== undefined && triggerPayload.filePath !== '') {
142+
const documentSymbolFqns: DocumentSymbol[] = []
143+
if (triggerPayload.codeQuery?.fullyQualifiedNames?.used) {
144+
for (const fqn of triggerPayload.codeQuery.fullyQualifiedNames.used) {
145+
const elem = {
146+
name: fqn.symbol?.join('.') ?? '',
147+
type: SymbolType.USAGE,
148+
source: fqn.source?.join('.'),
149+
}
150+
151+
if (
152+
elem.name.length >= fqnNameSizeDownLimit &&
153+
elem.name.length < fqnNameSizeUpLimit &&
154+
(elem.source === undefined ||
155+
(elem.source.length >= fqnNameSizeDownLimit && elem.source.length < fqnNameSizeUpLimit))
156+
) {
157+
documentSymbolFqns.push(elem)
158+
}
159+
}
160+
}
161+
162+
let programmingLanguage
163+
if (
164+
triggerPayload.fileLanguage !== undefined &&
165+
triggerPayload.fileLanguage !== '' &&
166+
supportedLanguagesList.includes(triggerPayload.fileLanguage)
167+
) {
168+
programmingLanguage = { languageName: triggerPayload.fileLanguage }
169+
}
170+
171+
document = {
172+
relativeFilePath: triggerPayload.filePath ? triggerPayload.filePath.substring(0, filePathSizeLimit) : '',
173+
text: triggerPayload.fileText,
174+
programmingLanguage: programmingLanguage,
175+
documentSymbols: documentSymbolFqns,
176+
}
177+
178+
if (triggerPayload.codeSelection?.start) {
179+
cursorState = {
180+
range: {
181+
start: {
182+
line: triggerPayload.codeSelection.start.line,
183+
character: triggerPayload.codeSelection.start.character,
184+
},
185+
end: {
186+
line: triggerPayload.codeSelection.end.line,
187+
character: triggerPayload.codeSelection.end.character,
188+
},
189+
},
190+
}
191+
}
192+
}
193+
194+
const relevantDocuments: RelevantTextDocument[] = triggerPayload.relevantTextDocuments
195+
? triggerPayload.relevantTextDocuments
196+
: []
197+
const useRelevantDocuments = triggerPayload.useRelevantDocuments
198+
// service will throw validation exception if string is empty
199+
const customizationArn: string | undefined = undefinedIfEmpty(triggerPayload.customization.arn)
200+
const chatTriggerType = triggerPayload.trigger === ChatTriggerType.InlineChatMessage ? 'INLINE_CHAT' : 'MANUAL'
201+
202+
const operatingSystem = 'macos'
203+
const currentWorkingDirectory = tryGetCurrentWorkingDirectory()
204+
205+
const tools: Tool[] = Object.entries(toolsJson).map(([, toolSpec]) => ({
206+
toolSpecification: {
207+
...toolSpec,
208+
inputSchema: { json: toolSpec.input_schema },
209+
},
210+
}))
211+
return {
212+
conversationState: {
213+
currentMessage: {
214+
userInputMessage: {
215+
content: triggerPayload.message
216+
? triggerPayload.message.substring(0, customerMessageSizeLimit)
217+
: '',
218+
userInputMessageContext: {
219+
editorState: {
220+
document,
221+
cursorState,
222+
relevantDocuments,
223+
useRelevantDocuments,
224+
},
225+
envState: {
226+
operatingSystem,
227+
currentWorkingDirectory,
228+
environmentVariables: [],
229+
},
230+
additionalContext: triggerPayload.additionalContents,
231+
tools,
232+
toolResults: triggerPayload.toolResults,
233+
},
234+
userIntent: triggerPayload.userIntent,
235+
origin: 'IDE',
236+
},
237+
},
238+
chatTriggerType,
239+
customizationArn: customizationArn,
240+
history: chatHistory,
241+
},
242+
}
243+
}

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

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ import { UserIntentRecognizer } from './userIntent/userIntentRecognizer'
5050
import { CWCTelemetryHelper, recordTelemetryChatRunCommand } from './telemetryHelper'
5151
import { CodeWhispererTracker } from '../../../codewhisperer/tracker/codewhispererTracker'
5252
import { getLogger } from '../../../shared/logger/logger'
53-
import { triggerPayloadToChatRequest } from './chatRequest/converter'
53+
import { triggerPayloadToAgenticChatRequest, triggerPayloadToChatRequest } from './chatRequest/converter'
5454
import { AuthUtil } from '../../../codewhisperer/util/authUtil'
5555
import { openUrl } from '../../../shared/utilities/vsCodeUtils'
5656
import { randomUUID } from '../../../shared/crypto'
@@ -80,6 +80,8 @@ import {
8080
contextMaxLength,
8181
} from '../../constants'
8282
import { ChatSession } from '../../clients/chat/v0/chat'
83+
import { FsRead, FsReadParams } from '../../tools/fsRead'
84+
import { tryGetCurrentWorkingDirectory } from '../../../shared/utilities/workspaceUtils'
8385

8486
export interface ChatControllerMessagePublishers {
8587
readonly processPromptChatMessage: MessagePublisher<PromptMessage>
@@ -815,7 +817,7 @@ export class ChatController {
815817
private async processPromptMessageAsNewThread(message: PromptMessage) {
816818
this.editorContextExtractor
817819
.extractContextForTrigger('ChatMessage')
818-
.then((context) => {
820+
.then(async (context) => {
819821
const triggerID = randomUUID()
820822
this.triggerEventsStorage.addTriggerEvent({
821823
id: triggerID,
@@ -824,7 +826,7 @@ export class ChatController {
824826
type: 'chat_message',
825827
context,
826828
})
827-
return this.generateResponse(
829+
await this.generateResponse(
828830
{
829831
message: message.message,
830832
trigger: ChatTriggerType.ChatMessage,
@@ -839,8 +841,57 @@ export class ChatController {
839841
customization: getSelectedCustomization(),
840842
context: message.context,
841843
},
842-
triggerID
844+
triggerID,
845+
true
843846
)
847+
848+
const session = this.sessionStorage.getSession(message.tabID)
849+
while (true) {
850+
const toolUse = session.toolUse
851+
if (!toolUse || !toolUse.input) {
852+
break
853+
}
854+
session.setToolUse(undefined)
855+
856+
let result: any
857+
switch (toolUse.name) {
858+
case 'fs_read': {
859+
const fsRead = new FsRead(toolUse.input as unknown as FsReadParams)
860+
result = await fsRead.invoke({
861+
env: { currentDir: () => tryGetCurrentWorkingDirectory() },
862+
})
863+
break
864+
}
865+
default:
866+
break
867+
}
868+
869+
await this.generateResponse(
870+
{
871+
message: '',
872+
trigger: ChatTriggerType.ChatMessage,
873+
query: message.message,
874+
codeSelection: context?.focusAreaContext?.selectionInsideExtendedCodeBlock,
875+
fileText: context?.focusAreaContext?.extendedCodeBlock,
876+
fileLanguage: context?.activeFileContext?.fileLanguage,
877+
filePath: context?.activeFileContext?.filePath,
878+
matchPolicy: context?.activeFileContext?.matchPolicy,
879+
codeQuery: context?.focusAreaContext?.names,
880+
userIntent: this.userIntentRecognizer.getFromPromptChatMessage(message),
881+
customization: getSelectedCustomization(),
882+
context: message.context,
883+
toolResults: [
884+
{
885+
content: [{ text: result.output.content }],
886+
toolUseId: toolUse.toolUseId,
887+
status: 'success',
888+
},
889+
],
890+
},
891+
triggerID,
892+
true
893+
)
894+
}
844895
})
845896
.catch((e) => {
846897
this.processException(e, message.tabID)
@@ -1025,7 +1076,8 @@ export class ChatController {
10251076

10261077
private async generateResponse(
10271078
triggerPayload: TriggerPayload & { projectContextQueryLatencyMs?: number },
1028-
triggerID: string
1079+
triggerID: string,
1080+
agentic?: boolean
10291081
) {
10301082
const triggerEvent = this.triggerEventsStorage.getTriggerEvent(triggerID)
10311083
if (triggerEvent === undefined) {
@@ -1038,7 +1090,7 @@ export class ChatController {
10381090

10391091
if (triggerEvent.tabID === undefined) {
10401092
setTimeout(() => {
1041-
this.generateResponse(triggerPayload, triggerID).catch((e) => {
1093+
this.generateResponse(triggerPayload, triggerID, agentic).catch((e) => {
10421094
getLogger().error('generateResponse failed: %s', (e as Error).message)
10431095
})
10441096
}, 20)
@@ -1103,7 +1155,9 @@ export class ChatController {
11031155

11041156
triggerPayload.documentReferences = this.mergeRelevantTextDocuments(triggerPayload.relevantTextDocuments || [])
11051157

1106-
const request = triggerPayloadToChatRequest(triggerPayload)
1158+
const request = agentic
1159+
? triggerPayloadToAgenticChatRequest(triggerPayload, session.chatHistory)
1160+
: triggerPayloadToChatRequest(triggerPayload)
11071161

11081162
if (triggerPayload.documentReferences !== undefined) {
11091163
const relativePathsOfMergedRelevantDocuments = triggerPayload.documentReferences.map(
@@ -1157,6 +1211,7 @@ export class ChatController {
11571211
response.$metadata.requestId
11581212
} metadata: ${inspect(response.$metadata, { depth: 12 })}`
11591213
)
1214+
session.pushToChatHistory(request.conversationState.currentMessage)
11601215
await this.messenger.sendAIResponse(response, session, tabID, triggerID, triggerPayload)
11611216
} catch (e: any) {
11621217
this.telemetryHelper.recordMessageResponseError(triggerPayload, tabID, getHttpStatusCode(e) ?? 0)

0 commit comments

Comments
 (0)