in server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts [365:482]
async onChatPrompt(params: ChatParams, token: CancellationToken): Promise<ChatResult | ResponseError<ChatResult>> {
// Phase 1: Initial Setup - This happens only once
const maybeDefaultResponse = getDefaultChatResponse(params.prompt.prompt)
if (maybeDefaultResponse) {
return maybeDefaultResponse
}
const sessionResult = this.#chatSessionManagementService.getSession(params.tabId)
const { data: session, success } = sessionResult
if (!success) {
return new ResponseError<ChatResult>(ErrorCodes.InternalError, sessionResult.error)
}
session.rejectAllDeferredToolExecutions(new ToolApprovalException('Command ignored: new prompt', false))
await this.#invalidateAllShellCommands(params.tabId, session)
const metric = new Metric<CombinedConversationEvent>({
cwsprChatConversationType: 'AgenticChat',
})
try {
const triggerContext = await this.#getTriggerContext(params, metric)
const isNewConversation = !session.conversationId
session.contextListSent = false
if (isNewConversation) {
// agentic chat does not support conversationId in API response,
// so we set it to random UUID per session, as other chat functionality
// depends on it
session.conversationId = uuid()
}
token.onCancellationRequested(async () => {
this.#log('cancellation requested')
await this.#getChatResultStream(params.partialResultToken).writeResultBlock({
type: 'directive',
messageId: 'stopped' + uuid(),
body: 'You stopped your current work, please provide additional examples or ask another question.',
})
this.#telemetryController.emitInteractWithAgenticChat('StopChat', params.tabId)
session.abortRequest()
void this.#invalidateAllShellCommands(params.tabId, session)
session.rejectAllDeferredToolExecutions(new CancellationError('user'))
})
const chatResultStream = this.#getChatResultStream(params.partialResultToken)
const additionalContext = await this.#additionalContextProvider.getAdditionalContext(
triggerContext,
(params.prompt as any).context
)
if (additionalContext.length) {
triggerContext.documentReference =
this.#additionalContextProvider.getFileListFromContext(additionalContext)
}
// Get the initial request input
const initialRequestInput = await this.#prepareRequestInput(
params,
session,
triggerContext,
additionalContext,
chatResultStream
)
// Start the agent loop
const finalResult = await this.#runAgentLoop(
initialRequestInput,
session,
metric,
chatResultStream,
params.tabId,
session.conversationId,
token,
triggerContext.documentReference
)
// Phase 5: Result Handling - This happens only once
return await this.#handleFinalResult(
finalResult,
session,
params,
metric,
triggerContext,
isNewConversation,
chatResultStream
)
} catch (err) {
// HACK: the chat-client needs to have a partial event with the associated messageId sent before it can accept the final result.
// Without this, the `thinking` indicator never goes away.
// Note: buttons being explicitly empty is required for this hack to work.
const errorMessageId = `error-message-id-${uuid()}`
await this.#sendProgressToClient(
{
type: 'answer',
body: '',
messageId: errorMessageId,
buttons: [],
},
params.partialResultToken
)
if (this.isUserAction(err, token)) {
/**
* when the session is aborted it generates an error.
* we need to resolve this error with an answer so the
* stream stops
*/
return {
type: 'answer',
body: '',
messageId: errorMessageId,
buttons: [],
}
}
return this.#handleRequestError(err, errorMessageId, params.tabId, metric)
}
}