Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@
"ENABLE_CHAT": "true",
"ENABLE_CUSTOMIZATIONS": "true",
"ENABLE_AMAZON_Q_PROFILES": "false",
"ENABLE_AWS_Q_SECTION": "true"
"ENABLE_AWS_Q_SECTION": "true",
"ENABLE_AGENTIC_UI_MODE": "false"
// "HTTPS_PROXY": "http://127.0.0.1:8888",
// "AWS_CA_BUNDLE": "/path/to/cert.pem"
}
Expand All @@ -112,7 +113,8 @@
"ENABLE_CHAT": "true",
"ENABLE_CUSTOMIZATIONS": "true",
"ENABLE_AMAZON_Q_PROFILES": "false",
"ENABLE_AWS_Q_SECTION": "true"
"ENABLE_AWS_Q_SECTION": "true",
"ENABLE_AGENTIC_UI_MODE": "true"
// "HTTPS_PROXY": "http://127.0.0.1:8888",
// "AWS_CA_BUNDLE": "/path/to/cert.pem"
}
Expand Down
12 changes: 10 additions & 2 deletions chat-client/src/client/chat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ describe('Chat', () => {
postMessage: sandbox.stub(),
}

mynahUi = createChat(clientApi)
mynahUi = createChat(clientApi, {
agenticMode: true,
})
})

afterEach(() => {
Expand Down Expand Up @@ -392,7 +394,13 @@ describe('Chat', () => {
handleMessageReceive: handleMessageReceiveStub,
isSupportedTab: () => false,
}
mynahUi = createChat(clientApi, {}, clientAdapter as ChatClientAdapter)
mynahUi = createChat(
clientApi,
{
agenticMode: true,
},
clientAdapter as ChatClientAdapter
)

const tabId = '123'
const body = 'some response'
Expand Down
9 changes: 8 additions & 1 deletion chat-client/src/client/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const DEFAULT_TAB_DATA = {
type ChatClientConfig = Pick<MynahUIDataModel, 'quickActionCommands'> & {
disclaimerAcknowledged?: boolean
pairProgrammingAcknowledged?: boolean
agenticMode?: boolean
}

export const createChat = (
Expand All @@ -126,6 +127,7 @@ export const createChat = (
}

const featureConfig: Map<string, FeatureContext> = parseFeatureConfig(featureConfigSerialized)

/**
* Handles incoming messages from the IDE or other sources.
* Routes messages to appropriate handlers based on command type.
Expand Down Expand Up @@ -376,13 +378,18 @@ export const createChat = (
...(config?.quickActionCommands ? config.quickActionCommands : []),
])

if (config?.agenticMode) {
tabFactory.enableAgenticMode()
}

const [mynahUi, api] = createMynahUi(
messager,
tabFactory,
config?.disclaimerAcknowledged ?? false,
config?.pairProgrammingAcknowledged ?? false,
chatClientAdapter,
featureConfig
featureConfig,
!!config?.agenticMode
)

mynahApi = api
Expand Down
16 changes: 12 additions & 4 deletions chat-client/src/client/mynahUi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('MynahUI', () => {
createTabStub.returns({})
getChatItemsStub = sinon.stub(tabFactory, 'getChatItems')
getChatItemsStub.returns([])
const mynahUiResult = createMynahUi(messager, tabFactory, true, true)
const mynahUiResult = createMynahUi(messager, tabFactory, true, true, undefined, undefined, true)
mynahUi = mynahUiResult[0]
inboundChatApi = mynahUiResult[1]
getSelectedTabIdStub = sinon.stub(mynahUi, 'getSelectedTabId')
Expand All @@ -96,7 +96,7 @@ describe('MynahUI', () => {
const tabId = 'tab-1'
const prompt = { prompt: 'Test prompt', escapedPrompt: 'Test prompt' }

handleChatPrompt(mynahUi, tabId, prompt, messager)
handleChatPrompt(mynahUi, tabId, prompt, messager, undefined, undefined, true)

assert.notCalled(onQuickActionSpy)
assert.calledWith(onChatPromptSpy, { prompt, tabId, context: undefined })
Expand Down Expand Up @@ -125,7 +125,7 @@ describe('MynahUI', () => {
const tabId = 'tab-1'
const prompt = { prompt: 'Test prompt', escapedPrompt: 'Test prompt', command: '/help' }

handleChatPrompt(mynahUi, tabId, prompt, messager)
handleChatPrompt(mynahUi, tabId, prompt, messager, undefined, undefined, true)

assert.notCalled(onChatPromptSpy)
assert.calledWith(onQuickActionSpy, {
Expand Down Expand Up @@ -414,7 +414,15 @@ describe('withAdapter', () => {
telemetry: sinon.stub(),
} as OutboundChatApi)
const tabFactory = new TabFactory({})
const mynahUiResult = createMynahUi(messager as Messager, tabFactory, true, true, chatClientAdapter)
const mynahUiResult = createMynahUi(
messager as Messager,
tabFactory,
true,
true,
chatClientAdapter,
undefined,
true
)
mynahUi = mynahUiResult[0]
})

Expand Down
155 changes: 136 additions & 19 deletions chat-client/src/client/mynahUi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ const getTabPairProgrammingMode = (mynahUi: MynahUI, tabId: string) => {
return promptInputOptions.find(item => item.id === 'pair-programmer-mode')?.value === 'true'
}

const openTabKey = 'openTab'

export const handlePromptInputChange = (mynahUi: MynahUI, tabId: string, optionsValues: Record<string, string>) => {
const promptTypeValue = optionsValues['pair-programmer-mode']

Expand All @@ -102,7 +100,8 @@ export const handleChatPrompt = (
prompt: ChatPrompt,
messager: Messager,
triggerType?: TriggerType,
_eventId?: string
_eventId?: string,
agenticMode?: boolean
) => {
let userPrompt = prompt.escapedPrompt
messager.onStopChatResponse(tabId)
Expand Down Expand Up @@ -138,11 +137,18 @@ export const handleChatPrompt = (
})

// Set UI to loading state
mynahUi.updateStore(tabId, {
loadingChat: true,
cancelButtonWhenLoading: true,
promptInputDisabledState: false,
})
if (agenticMode) {
mynahUi.updateStore(tabId, {
loadingChat: true,
cancelButtonWhenLoading: true,
promptInputDisabledState: false,
})
} else {
mynahUi.updateStore(tabId, {
loadingChat: true,
promptInputDisabledState: true,
})
}

// Create initial empty response
mynahUi.addChatItem(tabId, {
Expand All @@ -156,7 +162,8 @@ export const createMynahUi = (
disclaimerAcknowledged: boolean,
pairProgrammingCardAcknowledged: boolean,
customChatClientAdapter?: ChatClientAdapter,
featureConfig?: Map<string, any>
featureConfig?: Map<string, any>,
agenticMode?: boolean
): [MynahUI, InboundChatApi] => {
let disclaimerCardActive = !disclaimerAcknowledged
let programmingModeCardActive = !pairProgrammingCardAcknowledged
Expand Down Expand Up @@ -199,7 +206,15 @@ export const createMynahUi = (
mynahUi.updateStore(tabId, { promptInputDisabledState: false })
} else {
const prompt = followUp.prompt ? followUp.prompt : followUp.pillText
handleChatPrompt(mynahUi, tabId, { prompt: prompt, escapedPrompt: prompt }, messager, 'click', eventId)
handleChatPrompt(
mynahUi,
tabId,
{ prompt: prompt, escapedPrompt: prompt },
messager,
'click',
eventId,
agenticMode
)

const payload: FollowUpClickParams = {
tabId,
Expand All @@ -210,7 +225,7 @@ export const createMynahUi = (
}
},
onChatPrompt(tabId, prompt, eventId) {
handleChatPrompt(mynahUi, tabId, prompt, messager, 'click', eventId)
handleChatPrompt(mynahUi, tabId, prompt, messager, 'click', eventId, agenticMode)
},
onReady: () => {
messager.onUiReady()
Expand Down Expand Up @@ -439,7 +454,9 @@ export const createMynahUi = (
throw new Error(`Unhandled tab bar button id: ${buttonId}`)
},
onPromptInputOptionChange: (tabId, optionsValues) => {
handlePromptInputChange(mynahUi, tabId, optionsValues)
if (agenticMode) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it's probably no needed here - this button for pair programming should never be shown for non agentic mode

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but I added it just in case something triggers it and messes up prompt with pair programming related stuff

handlePromptInputChange(mynahUi, tabId, optionsValues)
}
messager.onPromptInputOptionChange({ tabId, optionsValues })
},
onMessageDismiss: (tabId, messageId) => {
Expand Down Expand Up @@ -467,12 +484,17 @@ export const createMynahUi = (
},
config: {
maxTabs: 10,
texts: {
...uiComponentsTexts,
// Fallback to original texts in non-agentic chat mode
stopGenerating: agenticMode ? uiComponentsTexts.stopGenerating : 'Stop generating',
spinnerText: agenticMode ? uiComponentsTexts.spinnerText : 'Generating your answer...',
},
// RTS max user input is 600k, we need to leave around 500 chars to user to type the question
// beside, MynahUI will automatically crop it depending on the available chars left from the prompt field itself by using a 96 chars of threshold
// if we want to max user input as 599500, need to configure the maxUserInput as 599596
maxUserInput: 599596,
userInputLengthWarningThreshold: 550000,
texts: uiComponentsTexts,
},
}

Expand Down Expand Up @@ -555,6 +577,15 @@ export const createMynahUi = (
}

const addChatResponse = (chatResult: ChatResult, tabId: string, isPartialResult: boolean) => {
if (agenticMode) {
agenticAddChatResponse(chatResult, tabId, isPartialResult)
} else {
legacyAddChatResponse(chatResult, tabId, isPartialResult)
}
}

// addChatResponse handler to support Agentic chat UX changes for handling responses streaming.
const agenticAddChatResponse = (chatResult: ChatResult, tabId: string, isPartialResult: boolean) => {
const { type, ...chatResultWithoutType } = chatResult
let header = toMynahHeader(chatResult.header)
const fileList = toMynahFileList(chatResult.fileList)
Expand Down Expand Up @@ -620,7 +651,6 @@ export const createMynahUi = (
if (Object.keys(chatResult).length === 0) {
return
}

// If the response is auth follow-up show it as a system prompt
const followUpOptions = chatResult.followUp?.options
const isValidAuthFollowUp =
Expand All @@ -641,7 +671,6 @@ export const createMynahUi = (
// mynahUi.updateStore(tabId, { promptInputDisabledState: true })
return
}

const followUps = chatResult.followUp
? {
text: chatResult.followUp.text ?? 'Suggested follow up questions:',
Expand All @@ -668,11 +697,99 @@ export const createMynahUi = (
})
}

// addChatResponse handler to support extensions that haven't migrated to agentic chat yet
const legacyAddChatResponse = (chatResult: ChatResult, tabId: string, isPartialResult: boolean) => {
const { type, ...chatResultWithoutType } = chatResult
let header = undefined

if (chatResult.contextList !== undefined) {
header = {
fileList: {
fileTreeTitle: '',
filePaths: chatResult.contextList.filePaths?.map(file => file),
rootFolderTitle: 'Context',
flatList: true,
collapsed: true,
hideFileCount: true,
details: Object.fromEntries(
Object.entries(chatResult.contextList.details || {}).map(([filePath, fileDetails]) => [
filePath,
{
label:
fileDetails.lineRanges
?.map(range =>
range.first === -1 || range.second === -1
? ''
: `line ${range.first} - ${range.second}`
)
.join(', ') || '',
description: filePath,
clickable: true,
},
])
),
},
}
}

if (isPartialResult) {
// @ts-ignore - type for MynahUI differs from ChatResult types so we ignore it
mynahUi.updateLastChatAnswer(tabId, { ...chatResultWithoutType, header: header })
return
}

// If chat response from server is an empty object don't do anything
if (Object.keys(chatResult).length === 0) {
return
}
// If the response is auth follow-up show it as a system prompt
const followUpOptions = chatResult.followUp?.options
const isValidAuthFollowUp =
followUpOptions &&
followUpOptions.length > 0 &&
followUpOptions[0].type &&
isValidAuthFollowUpType(followUpOptions[0].type)
if (chatResult.body === '' && isValidAuthFollowUp) {
// @ts-ignore - type for MynahUI differs from ChatResult types so we ignore it
mynahUi.addChatItem(tabId, {
type: ChatItemType.SYSTEM_PROMPT,
...chatResultWithoutType,
})

// TODO, prompt should be disabled until user is authenticated
// Currently we don't have a mechanism to notify chat-client about auth changes
// mynahUi.updateStore(tabId, { promptInputDisabledState: true })
return
}
const followUps = chatResult.followUp
? {
text: chatResult.followUp.text ?? 'Suggested follow up questions:',
options: chatResult.followUp.options,
}
: {}

mynahUi.updateLastChatAnswer(tabId, {
header: header,
body: chatResult.body,
messageId: chatResult.messageId,
followUp: followUps,
relatedContent: chatResult.relatedContent,
canBeVoted: chatResult.canBeVoted,
})

mynahUi.endMessageStream(tabId, chatResult.messageId ?? '')

mynahUi.updateStore(tabId, {
loadingChat: false,
promptInputDisabledState: false,
})
}

const updateChat = (params: ChatUpdateParams) => {
const isChatLoading = params.state?.inProgress
mynahUi.updateStore(params.tabId, {
loadingChat: isChatLoading,
cancelButtonWhenLoading: true,
cancelButtonWhenLoading: agenticMode,
})
if (params.data?.messages.length) {
const { tabId } = params
Expand Down Expand Up @@ -707,7 +824,7 @@ export const createMynahUi = (
}))
mynahUi.updateStore(tabId, {
loadingChat: false,
cancelButtonWhenLoading: true,
cancelButtonWhenLoading: agenticMode,
chatItems: updatedItems,
promptInputDisabledState: false,
})
Expand Down Expand Up @@ -796,7 +913,7 @@ export const createMynahUi = (
].join('')
const chatPrompt: ChatPrompt = { prompt: body, escapedPrompt: body }

handleChatPrompt(mynahUi, tabId, chatPrompt, messager, params.triggerType)
handleChatPrompt(mynahUi, tabId, chatPrompt, messager, params.triggerType, undefined, agenticMode)
}

const showError = (params: ErrorParams) => {
Expand All @@ -811,7 +928,7 @@ ${params.message}`,

mynahUi.updateStore(tabId, {
loadingChat: false,
cancelButtonWhenLoading: true,
cancelButtonWhenLoading: agenticMode,
promptInputDisabledState: false,
})

Expand Down
Loading
Loading