Skip to content

Commit 97db5d8

Browse files
authored
feat: Implement dynamic model selection based on extension capabilities and improve error handling (#1737)
* fix: temporarily fix version showing error throttling without model selection * feat: get modelSelection from extention qcapabilities and handle error * chore: prettier fix * fix: fix the test related to modelId update * fix: simplify error handling and add util for model selection * fix: delete the test code * fix: leave fonstants and fix test * fix: give general error message for every existing users * fix: revert back to default model and check the error message based on enabled model selection * fix: delete unrelated comments and pass features.lsp rather than features
1 parent c9f8989 commit 97db5d8

File tree

10 files changed

+71
-12
lines changed

10 files changed

+71
-12
lines changed

chat-client/src/client/mynahUi.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ import {
6464
import { ChatHistory, ChatHistoryList } from './features/history'
6565
import { pairProgrammingModeOff, pairProgrammingModeOn, programmerModeCard } from './texts/pairProgramming'
6666
import { ContextRule, RulesList } from './features/rules'
67-
import { getModelSelectionChatItem, modelUnavailableBanner } from './texts/modelSelection'
67+
import { getModelSelectionChatItem, modelUnavailableBanner, modelThrottledBanner } from './texts/modelSelection'
6868
import {
6969
freeTierLimitSticky,
7070
upgradeSuccessSticky,
@@ -1016,6 +1016,13 @@ export const createMynahUi = (
10161016
return
10171017
}
10181018

1019+
if (updatedMessage.messageId === 'modelThrottled') {
1020+
mynahUi.updateStore(tabId, {
1021+
promptInputStickyCard: modelThrottledBanner,
1022+
})
1023+
return
1024+
}
1025+
10191026
const oldMessage = chatItems.find(ci => ci.messageId === updatedMessage.messageId)
10201027
if (!oldMessage) return
10211028

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,14 @@ export const modelUnavailableBanner: Partial<ChatItem> = {
5454
body: `The model you've selected is experiencing high load. Please switch to another model and try again.`,
5555
canBeDismissed: true,
5656
}
57+
58+
export const modelThrottledBanner: Partial<ChatItem> = {
59+
messageId: 'model-throttled-banner',
60+
header: {
61+
icon: 'warning',
62+
iconStatus: 'warning',
63+
body: '### Model Unavailable',
64+
},
65+
body: `I am experiencing high traffic, please try again shortly.`,
66+
canBeDismissed: true,
67+
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,6 @@ describe('AgenticChatController', () => {
354354
it('onTabAdd updates model ID in chat options and session', () => {
355355
const modelId = 'test-model-id'
356356
sinon.stub(ChatDatabase.prototype, 'getModelId').returns(modelId)
357-
358357
chatController.onTabAdd({ tabId: mockTabId })
359358

360359
sinon.assert.calledWithExactly(testFeatures.chat.chatOptionsUpdate, { modelId, tabId: mockTabId })

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import {
9393
getSsoConnectionType,
9494
isUsageLimitError,
9595
isNullish,
96+
enabledModelSelection,
9697
} from '../../shared/utils'
9798
import { HELP_MESSAGE, loadingMessage } from '../chat/constants'
9899
import { TelemetryService } from '../../shared/telemetry/telemetryService'
@@ -2352,6 +2353,19 @@ export class AgenticChatController implements ChatHandlers {
23522353
}
23532354
return emptyChatResult
23542355
}
2356+
if (err.message === `I am experiencing high traffic, please try again shortly.`) {
2357+
this.#features.chat.sendChatUpdate({
2358+
tabId: tabId,
2359+
data: { messages: [{ messageId: 'modelThrottled' }] },
2360+
})
2361+
const emptyChatResult: ChatResult = {
2362+
type: 'answer',
2363+
body: '',
2364+
messageId: errorMessageId,
2365+
buttons: [],
2366+
}
2367+
return emptyChatResult
2368+
}
23552369
return responseData
23562370
}
23572371
return new ResponseError<ChatResult>(LSPErrorCodes.RequestFailed, err.message, responseData)

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ import { ChatSessionManagementService } from '../chat/chatSessionManagementServi
1111
import { QAgenticChatServer } from './qAgenticChatServer'
1212
import { AmazonQTokenServiceManager } from '../../shared/amazonQServiceManager/AmazonQTokenServiceManager'
1313
import { AmazonQBaseServiceManager } from '../../shared/amazonQServiceManager/BaseAmazonQServiceManager'
14+
import { Features } from '../types'
1415

1516
describe('QAgenticChatServer', () => {
1617
const mockTabId = 'mockTabId'
1718
let disposeStub: sinon.SinonStub
18-
let withAmazonQServiceSpy: sinon.SinonSpy<[amazonQService: AmazonQBaseServiceManager], ChatSessionManagementService>
19+
let withAmazonQServiceSpy: sinon.SinonSpy<
20+
[serviceManager: AmazonQBaseServiceManager, lsp?: Features['lsp'] | undefined],
21+
ChatSessionManagementService
22+
>
1923
let testFeatures: TestFeatures
2024
let amazonQServiceManager: AmazonQTokenServiceManager
2125
let disposeServer: () => void
@@ -72,7 +76,7 @@ describe('QAgenticChatServer', () => {
7276
})
7377

7478
it('should initialize ChatSessionManagementService with AmazonQTokenServiceManager instance', () => {
75-
sinon.assert.calledOnceWithExactly(withAmazonQServiceSpy, amazonQServiceManager)
79+
sinon.assert.calledOnceWithExactly(withAmazonQServiceSpy, amazonQServiceManager, testFeatures.lsp)
7680
})
7781

7882
it('dispose should dispose all chat session services', () => {

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { AmazonQTokenServiceManager } from '../../shared/amazonQServiceManager/A
1313
import { AmazonQWorkspaceConfig } from '../../shared/amazonQServiceManager/configurationUtils'
1414
import { TabBarController } from './tabBarController'
1515
import { AmazonQServiceInitializationError } from '../../shared/amazonQServiceManager/errors'
16-
import { safeGet } from '../../shared/utils'
16+
import { enabledModelSelection, safeGet } from '../../shared/utils'
1717
import { enabledMCP } from './tools/mcp/mcpUtils'
1818

1919
export const QAgenticChatServer =
@@ -47,6 +47,7 @@ export const QAgenticChatServer =
4747
],
4848
},
4949
mcpServers: enabledMCP(params),
50+
// we should set it as true for current VSC and VS clients
5051
modelSelection: true,
5152
history: true,
5253
export: TabBarController.enableChatExport(params)
@@ -64,7 +65,7 @@ export const QAgenticChatServer =
6465
// Get initialized service manager and inject it to chatSessionManagementService to pass it down
6566
amazonQServiceManager = AmazonQTokenServiceManager.getInstance()
6667
chatSessionManagementService =
67-
ChatSessionManagementService.getInstance().withAmazonQServiceManager(amazonQServiceManager)
68+
ChatSessionManagementService.getInstance().withAmazonQServiceManager(amazonQServiceManager, features.lsp)
6869

6970
telemetryService = new TelemetryService(amazonQServiceManager, credentialsProvider, telemetry, logging)
7071

@@ -144,7 +145,7 @@ export const QAgenticChatServer =
144145
return chatController.onListConversations(params)
145146
})
146147

147-
chat.onListRules(params => {
148+
chat.onListRules(params => {
148149
return chatController.onListRules(params)
149150
})
150151

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { AmazonQBaseServiceManager } from '../../shared/amazonQServiceManager/BaseAmazonQServiceManager'
2-
import { Result } from '../types'
2+
import { Result, Features } from '../types'
33
import { ChatSessionService } from './chatSessionService'
44

55
export class ChatSessionManagementService {
66
static #instance?: ChatSessionManagementService
77
#sessionByTab: Map<string, ChatSessionService> = new Map<string, any>()
88
#serviceManager?: AmazonQBaseServiceManager
9+
#lsp?: Features['lsp']
910

1011
public static getInstance() {
1112
if (!ChatSessionManagementService.#instance) {
@@ -21,8 +22,9 @@ export class ChatSessionManagementService {
2122

2223
private constructor() {}
2324

24-
public withAmazonQServiceManager(serviceManager: AmazonQBaseServiceManager) {
25+
public withAmazonQServiceManager(serviceManager: AmazonQBaseServiceManager, lsp?: Features['lsp']) {
2526
this.#serviceManager = serviceManager
27+
this.#lsp = lsp
2628

2729
return this
2830
}
@@ -39,7 +41,7 @@ export class ChatSessionManagementService {
3941
}
4042
}
4143

42-
const newSession = new ChatSessionService(this.#serviceManager)
44+
const newSession = new ChatSessionService(this.#serviceManager, this.#lsp)
4345

4446
this.#sessionByTab.set(tabId, newSession)
4547

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import {
2121
import { AmazonQBaseServiceManager } from '../../shared/amazonQServiceManager/BaseAmazonQServiceManager'
2222
import { loggingUtils } from '@aws/lsp-core'
2323
import { Logging } from '@aws/language-server-runtimes/server-interface'
24+
import { Features } from '../types'
2425
import { getRequestID, isUsageLimitError } from '../../shared/utils'
26+
import { enabledModelSelection } from '../../shared/utils'
2527

2628
export type ChatSessionServiceConfig = CodeWhispererStreamingClientConfig
2729
type FileChange = { before?: string; after?: string }
@@ -35,6 +37,7 @@ export class ChatSessionService {
3537
public pairProgrammingMode: boolean = true
3638
public contextListSent: boolean = false
3739
public modelId: string | undefined
40+
#lsp?: Features['lsp']
3841
#abortController?: AbortController
3942
#currentPromptId?: string
4043
#conversationId?: string
@@ -121,8 +124,9 @@ export class ChatSessionService {
121124
this.#approvedPaths.add(normalizedPath)
122125
}
123126

124-
constructor(serviceManager?: AmazonQBaseServiceManager, logging?: Logging) {
127+
constructor(serviceManager?: AmazonQBaseServiceManager, lsp?: Features['lsp'], logging?: Logging) {
125128
this.#serviceManager = serviceManager
129+
this.#lsp = lsp
126130
this.#logging = logging
127131
}
128132

@@ -144,6 +148,10 @@ export class ChatSessionService {
144148
return response
145149
}
146150

151+
private isModelSelectionEnabled(): boolean {
152+
return enabledModelSelection(this.#lsp?.getClientInitializeParams())
153+
}
154+
147155
public async generateAssistantResponse(
148156
request: GenerateAssistantResponseCommandInput
149157
): Promise<GenerateAssistantResponseCommandOutput> {
@@ -208,8 +216,11 @@ export class ChatSessionService {
208216
error.message ===
209217
'Encountered unexpectedly high load when processing the request, please try again.'
210218
) {
211-
error.message = `The model you selected is temporarily unavailable. Please switch to a different model and try again.`
219+
error.message = this.isModelSelectionEnabled()
220+
? `The model you selected is temporarily unavailable. Please switch to a different model and try again.`
221+
: `I am experiencing high traffic, please try again shortly.`
212222
}
223+
213224
throw error
214225
}
215226
} else {

server/aws-lsp-codewhisperer/src/language-server/configuration/qConfigurationServer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface QClientCapabilities {
3939
developerProfiles?: boolean
4040
customizationsWithMetadata?: boolean
4141
mcp?: boolean
42+
modelSelection?: boolean
4243
}
4344

4445
type QConfigurationResponse =

server/aws-lsp-codewhisperer/src/shared/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import { ServiceException } from '@smithy/smithy-client'
2626
import * as ignoreWalk from 'ignore-walk'
2727
import { getAuthFollowUpType } from '../language-server/chat/utils'
2828
import ignore = require('ignore')
29+
import { InitializeParams } from '@aws/language-server-runtimes/server-interface'
30+
import { QClientCapabilities } from '../language-server/configuration/qConfigurationServer'
2931
export type SsoConnectionType = 'builderId' | 'identityCenter' | 'none'
3032

3133
export function isAwsError(error: unknown): error is AWSError {
@@ -316,6 +318,13 @@ export function getCompletionType(suggestion: Suggestion): CodewhispererCompleti
316318
return nonBlankLines > 1 ? 'Block' : 'Line'
317319
}
318320

321+
export function enabledModelSelection(params: InitializeParams | undefined): boolean {
322+
const qCapabilities = params?.initializationOptions?.aws?.awsClientCapabilities?.q as
323+
| QClientCapabilities
324+
| undefined
325+
return qCapabilities?.modelSelection || false
326+
}
327+
319328
export function parseJson(jsonString: string) {
320329
try {
321330
return JSON.parse(jsonString)

0 commit comments

Comments
 (0)