Skip to content

Commit 0a41819

Browse files
committed
wip: control MynahUI from chat server
1 parent e200ef4 commit 0a41819

File tree

6 files changed

+119
-33
lines changed

6 files changed

+119
-33
lines changed

chat-client/src/client/mynahUi.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import {
5757
} from './utils'
5858
import { ChatHistory, ChatHistoryList } from './features/history'
5959
import { pairProgrammingModeOff, pairProgrammingModeOn, programmerModeCard } from './texts/pairProgramming'
60+
import { paidTierCard, upgradeQButton } from './texts/paidTier'
6061

6162
export interface InboundChatApi {
6263
addChatResponse(params: ChatResult, tabId: string, isPartialResult: boolean): void
@@ -171,8 +172,6 @@ export const createMynahUi = (
171172
): [MynahUI, InboundChatApi] => {
172173
let disclaimerCardActive = !disclaimerAcknowledged
173174
let programmingModeCardActive = !pairProgrammingCardAcknowledged
174-
const isFreeTierLimitReached = true
175-
let paidTierCardActive = isFreeTierLimitReached
176175
let contextCommandGroups: ContextCommandGroups | undefined
177176

178177
let chatEventHandlers: ChatEventHandler = {
@@ -265,12 +264,7 @@ export const createMynahUi = (
265264
// We check if tabMetadata.openTabKey exists - if it does and is set to true, we skip showing welcome messages
266265
// since this indicates we're loading a previous chat session rather than starting a new one.
267266
if (!tabStore?.tabMetadata || !tabStore.tabMetadata.openTabKey) {
268-
defaultTabConfig.chatItems = tabFactory.getChatItems(
269-
true,
270-
programmingModeCardActive,
271-
paidTierCardActive,
272-
[]
273-
)
267+
defaultTabConfig.chatItems = tabFactory.getChatItems(true, programmingModeCardActive, false, [])
274268
}
275269
mynahUi.updateStore(tabId, defaultTabConfig)
276270
messager.onTabAdd(tabId)
@@ -499,7 +493,7 @@ export const createMynahUi = (
499493
const mynahUiProps: MynahUIProps = {
500494
tabs: {},
501495
defaults: {
502-
store: tabFactory.createTab(false, paidTierCardActive),
496+
store: tabFactory.createTab(false, false),
503497
},
504498
config: {
505499
maxTabs: 10,
@@ -538,7 +532,7 @@ export const createMynahUi = (
538532
// This distinction helps maintain consistent tab behavior between fresh conversations and restored sessions.
539533
const createTabId = (openTab?: boolean) => {
540534
const tabId = mynahUi.updateStore('', {
541-
...tabFactory.createTab(disclaimerCardActive, paidTierCardActive),
535+
...tabFactory.createTab(disclaimerCardActive, false),
542536
tabMetadata: { openTabKey: openTab ? true : false },
543537
})
544538
if (tabId === undefined) {
@@ -818,6 +812,25 @@ export const createMynahUi = (
818812
}
819813

820814
const updateChat = (params: ChatUpdateParams) => {
815+
if (params.data?.placeholderText === 'upgrade-q') {
816+
const tabId = params.tabId !== 'xxx' ? params.tabId : getOrCreateTabId()!
817+
const upgradeQMode: 'paidtier' | 'freetier' | 'freetier-limit' = (params as any).upgradeQMode
818+
819+
// const chatItem: ChatItem = {
820+
// type: ChatItemType.DIRECTIVE,
821+
// contentHorizontalAlignment: 'center',
822+
// fullWidth: true,
823+
// body: `Upgrade Q: ${upgradeQMode}`,
824+
// }
825+
// mynahUi.addChatItem(tabId, chatItem)
826+
upgradeQButton.description = `Upgrade Q: ${upgradeQMode}`
827+
mynahUi.updateStore(tabId, {
828+
promptInputButtons: upgradeQMode === 'paidtier' ? [] : [upgradeQButton],
829+
chatItems: upgradeQMode === 'freetier-limit' ? [paidTierCard] : [],
830+
})
831+
return
832+
}
833+
821834
const isChatLoading = params.state?.inProgress
822835
mynahUi.updateStore(params.tabId, {
823836
loadingChat: isChatLoading,
@@ -1005,7 +1018,7 @@ ${params.message}`,
10051018
chatItems: tabFactory.getChatItems(
10061019
messages ? false : true,
10071020
programmingModeCardActive,
1008-
paidTierCardActive,
1021+
false,
10091022
messages
10101023
),
10111024
})

chat-client/src/client/tabs/tabFactory.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,17 @@ import {
55
MynahUIDataModel,
66
QuickActionCommandGroup,
77
TabBarMainAction,
8-
ChatItemButton,
98
} from '@aws/mynah-ui'
109
import { disclaimerCard } from '../texts/disclaimer'
1110
import { ChatMessage } from '@aws/language-server-runtimes-types'
1211
import { ChatHistory } from '../features/history'
1312
import { pairProgrammingPromptInput, programmerModeCard } from '../texts/pairProgramming'
14-
import { paidTierCard } from '../texts/paidTier'
13+
import { paidTierCard, upgradeQButton } from '../texts/paidTier'
1514

1615
export type DefaultTabData = MynahUIDataModel
1716

1817
export const ExportTabBarButtonId = 'export'
1918

20-
const upgradeToProButton: ChatItemButton = {
21-
flash: 'once',
22-
fillState: 'hover',
23-
position: 'outside',
24-
id: 'upgrade-q',
25-
// https://github.com/aws/mynah-ui/blob/main/src/components/icon/icons/q.svg
26-
// https://github.com/aws/mynah-ui/blob/main/src/components/icon/icons/rocket.svg
27-
// icon: MynahIcons.Q,
28-
description: 'Upgrade to Amazon Q Pro',
29-
text: 'Upgrade Q',
30-
status: 'info',
31-
}
32-
3319
export class TabFactory {
3420
private history: boolean = false
3521
private export: boolean = false
@@ -52,7 +38,7 @@ export class TabFactory {
5238
const tabData: MynahUIDataModel = {
5339
...this.getDefaultTabData(),
5440
...(disclaimerCardActive ? { promptInputStickyCard: disclaimerCard } : {}),
55-
promptInputButtons: showUpgradeButton ? [upgradeToProButton] : [],
41+
promptInputButtons: showUpgradeButton ? [upgradeQButton] : [],
5642
promptInputOptions: this.agenticMode ? [pairProgrammingPromptInput] : [],
5743
cancelButtonWhenLoading: this.agenticMode, // supported for agentic chat only
5844
}

chat-client/src/client/texts/paidTier.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChatItem, ChatItemFormItem, ChatItemType } from '@aws/mynah-ui'
1+
import { ChatItem, ChatItemButton, ChatItemFormItem, ChatItemType } from '@aws/mynah-ui'
22

33
export const paidTierCard: ChatItem = {
44
type: ChatItemType.ANSWER,
@@ -31,3 +31,16 @@ export const paidTierStep1: ChatItem = {
3131
type: ChatItemType.DIRECTIVE,
3232
body: 'You have upgraded to Amazon Q Pro',
3333
}
34+
35+
export const upgradeQButton: ChatItemButton = {
36+
flash: 'once',
37+
fillState: 'hover',
38+
position: 'outside',
39+
id: 'upgrade-q',
40+
// https://github.com/aws/mynah-ui/blob/main/src/components/icon/icons/q.svg
41+
// https://github.com/aws/mynah-ui/blob/main/src/components/icon/icons/rocket.svg
42+
// icon: MynahIcons.Q,
43+
description: 'Upgrade to Amazon Q Pro',
44+
text: 'Upgrade Q',
45+
status: 'info',
46+
}

server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
InlineChatResultParams,
3030
PromptInputOptionChangeParams,
3131
TextDocument,
32+
ChatUpdateParams,
3233
} from '@aws/language-server-runtimes/protocol'
3334
import {
3435
ApplyWorkspaceEditParams,
@@ -73,7 +74,14 @@ import { ChatSessionManagementService } from '../chat/chatSessionManagementServi
7374
import { ChatTelemetryController } from '../chat/telemetry/chatTelemetryController'
7475
import { QuickAction } from '../chat/quickActions'
7576
import { Metric } from '../../shared/telemetry/metric'
76-
import { getErrorMessage, getHttpStatusCode, getRequestID, isAwsError, isNullish, isObject } from '../../shared/utils'
77+
import {
78+
getErrorMessage,
79+
getHttpStatusCode,
80+
getRequestID,
81+
getSsoConnectionType,
82+
isFreeTierLimitError,
83+
isNullish,
84+
} from '../../shared/utils'
7785
import { HELP_MESSAGE, loadingMessage } from '../chat/constants'
7886
import { TelemetryService } from '../../shared/telemetry/telemetryService'
7987
import {
@@ -147,6 +155,7 @@ export class AgenticChatController implements ChatHandlers {
147155
#additionalContextProvider: AdditionalContextProvider
148156
#contextCommandsProvider: ContextCommandsProvider
149157
#stoppedToolUses = new Set<string>()
158+
#freeTierLimit = false
150159

151160
/**
152161
* Determines the appropriate message ID for a tool use based on tool type and name
@@ -239,7 +248,15 @@ export class AgenticChatController implements ChatHandlers {
239248
await this.#renderStoppedShellCommand(params.tabId, params.messageId)
240249
return { success: true }
241250
} else if (params.buttonId === 'upgrade-q') {
242-
this.#features.logging.warn('clicked upgrade-to-pro-button')
251+
const awsAccountId = (params as any).awsAccountId
252+
if (typeof awsAccountId !== 'string') {
253+
this.#log(`invalid awsAccountId: ${awsAccountId}`)
254+
return {
255+
success: false,
256+
failureReason: 'invalid awsAccountId',
257+
}
258+
}
259+
this.setUpgradeQMode(params.tabId, 'paidtier')
243260
return { success: true }
244261
} else {
245262
return {
@@ -1574,6 +1591,12 @@ export class AgenticChatController implements ChatHandlers {
15741591
metric.setDimension('cwsprChatResponseCode', getHttpStatusCode(err) ?? 0)
15751592
metric.setDimension('languageServerVersion', this.#features.runtime.serverInfo.version)
15761593

1594+
// TODO handle free tier limit exceeded
1595+
if (isFreeTierLimitError(err)) {
1596+
this.setUpgradeQMode(tabId, 'freetier-limit')
1597+
// throw new AmazonQFreeTierLimitError()
1598+
}
1599+
15771600
// use custom error message for unactionable errors (user-dependent errors like PromptCharacterLimit)
15781601
if (err.code && err.code in unactionableErrorCodes) {
15791602
const customErrMessage = unactionableErrorCodes[err.code as keyof typeof unactionableErrorCodes]
@@ -1604,7 +1627,7 @@ export class AgenticChatController implements ChatHandlers {
16041627
return createAuthFollowUpResult(authFollowType)
16051628
}
16061629

1607-
if (customerFacingErrorCodes.includes(err.code)) {
1630+
if (isFreeTierLimitError(err) || customerFacingErrorCodes.includes(err.code)) {
16081631
this.#features.logging.error(`${loggingUtils.formatErr(err)}`)
16091632
if (err.code === 'InputTooLong') {
16101633
// Clear the chat history in the database for this tab
@@ -1813,6 +1836,12 @@ export class AgenticChatController implements ChatHandlers {
18131836

18141837
async onReady() {
18151838
await this.#tabBarController.loadChats()
1839+
try {
1840+
this.setUpgradeQMode()
1841+
} catch (err) {
1842+
this.#log('Error initializing Free Tier state: ' + (err as Error).message)
1843+
}
1844+
18161845
try {
18171846
const localProjectContextController = await LocalProjectContextController.getInstance()
18181847
const contextItems = await localProjectContextController.getContextCommandItems()
@@ -1846,6 +1875,8 @@ export class AgenticChatController implements ChatHandlers {
18461875
this.#telemetryController.activeTabId = params.tabId
18471876

18481877
this.#chatSessionManagementService.createSession(params.tabId)
1878+
1879+
this.setUpgradeQMode(params.tabId)
18491880
}
18501881

18511882
onTabChange(params: TabChangeParams) {
@@ -1860,6 +1891,8 @@ export class AgenticChatController implements ChatHandlers {
18601891
name: ChatTelemetryEventName.EnterFocusConversation,
18611892
data: {},
18621893
})
1894+
1895+
this.setUpgradeQMode(params.tabId)
18631896
}
18641897

18651898
onTabRemove(params: TabRemoveParams) {
@@ -2004,6 +2037,43 @@ export class AgenticChatController implements ChatHandlers {
20042037
}
20052038
}
20062039

2040+
/**
2041+
* Enables or disables the "Upgrade Q" UI in the chat component.
2042+
*
2043+
* `mode` behavior:
2044+
* - 'freetier': always show "Upgrade Q" button.
2045+
* - 'freetier-limit': also show "Free Tier limit reached" card in chat.
2046+
* - This mode is "sticky" until 'paidtier' is passed to override it.
2047+
* - 'paidtier': don't show "Upgrade Q" button.
2048+
*/
2049+
async setUpgradeQMode(
2050+
tabId?: string,
2051+
mode?: 'paidtier' | 'freetier' | 'freetier-limit' /*, session: ChatSessionService*/
2052+
) {
2053+
if (mode === 'freetier-limit') {
2054+
this.#freeTierLimit = true // Sticky until 'paidtier' is sent.
2055+
} else if (mode === 'paidtier') {
2056+
this.#freeTierLimit = false
2057+
} else if (this.#freeTierLimit && (!mode || mode === 'freetier')) {
2058+
mode = 'freetier-limit'
2059+
} else if (!mode) {
2060+
const isFreeTierUser = getSsoConnectionType(this.#features.credentialsProvider) === 'builderId'
2061+
mode = isFreeTierUser ? 'freetier' : 'paidtier'
2062+
}
2063+
2064+
const o: ChatUpdateParams = {
2065+
tabId: tabId ?? 'xxx',
2066+
state: { inProgress: false },
2067+
data: {
2068+
// Special flag recognized by `chat-client/src/client/mynahUi.ts`.
2069+
placeholderText: 'upgrade-q',
2070+
messages: [],
2071+
},
2072+
}
2073+
;(o as any).upgradeQMode = mode
2074+
this.#features.chat.sendChatUpdate(o)
2075+
}
2076+
20072077
async #processGenerateAssistantResponseResponseWithTimeout(
20082078
response: GenerateAssistantResponseCommandOutput,
20092079
metric: Metric<AddMessageEvent>,

server/aws-lsp-codewhisperer/src/language-server/agenticChat/qAgenticChatServer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ export const QAgenticChatServer =
5151
chatController.updateConfiguration(updatedConfig)
5252
}
5353

54-
const isFreeTierUser = getSsoConnectionType(credentialsProvider)
55-
5654
lsp.onInitialized(async () => {
5755
// Get initialized service manager and inject it to chatSessionManagementService to pass it down
5856
amazonQServiceManager = AmazonQTokenServiceManager.getInstance()
@@ -153,6 +151,10 @@ export const QAgenticChatServer =
153151
return chatController.onPromptInputOptionChange(params)
154152
})
155153

154+
// ;(chat as any).onPromptInputButtonClick((params: any) => {
155+
// chatController.setUpgradeQMode(params.tabId, 'paidtier')
156+
// })
157+
156158
chat.onButtonClick(params => {
157159
return chatController.onButtonClick(params)
158160
})

server/aws-lsp-codewhisperer/src/language-server/chat/chatSessionService.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ type DeferredHandler = {
2424
export class ChatSessionService {
2525
public shareCodeWhispererContentWithAWS = false
2626
public pairProgrammingMode: boolean = true
27+
/** Decides whether the Chat UI should show "Upgrade Q" button/card. */
28+
public upgradeQMode: 'paidtier' | 'freetier' | 'freetier-limit' = 'paidtier'
2729
public contextListSent: boolean = false
2830
#abortController?: AbortController
2931
#conversationId?: string

0 commit comments

Comments
 (0)