Skip to content

Commit 2ac19b7

Browse files
francescoopiccoliFrancesco Piccoli
andauthored
feat: add iam support in q chat server and q agentic server (#945)
* feat: add StreamingClientServiceIAM, refactor StreamingClientService and QServiceManager * refactor: qChat and qAgenticChat to support both iam and token auth * fix: features object parameter in other servers after changes to qServiceManager * fix: tests after refactor and renaming * feat: export QChatServerIAMProxy and fix naming of other servers after refactor * docs: testing and debugging with IAM auth * feat: enable chat for CodeWhisperer Server IAM launch config * fix: test/aws-lsp-codewhisperer.js assertions names * refactor: restore runtimes-defined features type in controllers & original QServiceManagerFeatures * refactor: remove unneeded functions and check in iam service manager * chore: merge branch 'main' into frapicc/iam-support-q-chat-server * chore: revert merge from main * chore: remove profileArn from agenticChatController * chore: remove unused parameters for qConfigurationServer * feat: add QChatServerIAM to iam-webworker servers in app/aws-lsp-codewhisperer-runtimes * test: add some tests for IAM q chat server * fix: revert changes to agentic chat (remove iam support) * fix: test * test: avoid running seperate tests for q chat server iam / token * chore: renam q chat server test class --------- Co-authored-by: Francesco Piccoli <[email protected]>
1 parent 4793504 commit 2ac19b7

29 files changed

+419
-179
lines changed

.vscode/launch.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@
128128
"env": {
129129
"LSP_SERVER": "${workspaceFolder}/app/aws-lsp-codewhisperer-runtimes/out/iam-standalone.js",
130130
"ENABLE_INLINE_COMPLETION": "true",
131-
"ENABLE_IAM_PROVIDER": "true"
131+
"ENABLE_IAM_PROVIDER": "true",
132+
"ENABLE_CHAT": "true"
132133
// "HTTPS_PROXY": "http://127.0.0.1:8888",
133134
}
134135
// "preLaunchTask": "compile"

CONTRIBUTING.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,22 @@ and be able to debug it all.
211211
A new window will open.
212212
213213
### With CodeWhisperer Server in VSCode
214-
215-
1. In the `Run & Debug` menu, run `"CodeWhisperer Server"`
214+
#### With token credentials
215+
1. In the `Run & Debug` menu, run `"CodeWhisperer Server Token"`
216216
2. Set breakpoints in `src` where needed
217217
3. Check the logs in `"AWS Documents Language Server"` output window.
218218
219219
> **NOTE**: If you see "Recommendation failure: Error: Authorization failed, bearer token is not set" errors, make sure to authenticate using `"AWS LSP - Obtain bearer token and send to LSP server"` command.
220220
221221
> **NOTE**: The lsp client is activated by one of the `activationEvents` defined [here](https://github.com/aws/language-servers/blob/06fd81d1e936648ef43243865039f89c7ac142a7/client/vscode/package.json#L18-L22), the lsp client then starts the LSP server.
222222
223+
#### With IAM credentials
224+
1. In the `Run & Debug` menu, run `"CodeWhisperer Server IAM"`
225+
2. Set breakpoints in `src` where needed
226+
3. Check the logs in `"AWS Documents Language Server"` output window.
227+
228+
> **NOTE**: To authenticate use ['AWS LSP - Choose credentials profile, resolve, and send to LSP Server'](https://github.com/aws/language-servers/blob/694bbb85580cc79313d65ad77b224875f74280c2/client/vscode/package.json#L32-L33) command, giving as input your iam credential profile name, for more info see [here](https://github.com/aws/language-servers/blob/694bbb85580cc79313d65ad77b224875f74280c2/client/vscode/README.md?plain=1#L11).
229+
223230
### With Other Clients
224231
Using other clients can also be done with the bundle created from this package.
225232

app/aws-lsp-codewhisperer-runtimes/src/agent-standalone.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { RuntimeProps } from '@aws/language-server-runtimes/runtimes/runtime'
33
import {
44
CodeWhispererSecurityScanServerTokenProxy,
55
CodeWhispererServerTokenProxy,
6-
QAgenticChatServerProxy,
6+
QAgenticChatServerTokenProxy,
77
QConfigurationServerTokenProxy,
88
QNetTransformServerTokenProxy,
99
} from '@aws/lsp-codewhisperer'
@@ -22,7 +22,7 @@ const props: RuntimeProps = {
2222
CodeWhispererSecurityScanServerTokenProxy,
2323
QConfigurationServerTokenProxy,
2424
QNetTransformServerTokenProxy,
25-
QAgenticChatServerProxy,
25+
QAgenticChatServerTokenProxy,
2626
IdentityServer.create,
2727
FsToolsServer,
2828
],
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { standalone } from '@aws/language-server-runtimes/runtimes'
22
import { RuntimeProps } from '@aws/language-server-runtimes/runtimes/runtime'
3-
import { CodeWhispererServerIAM } from '@aws/lsp-codewhisperer'
3+
import { CodeWhispererServerIAM, QChatServerIAMProxy } from '@aws/lsp-codewhisperer'
44

55
const props: RuntimeProps = {
66
version: '0.1.0',
7-
servers: [CodeWhispererServerIAM],
7+
servers: [CodeWhispererServerIAM, QChatServerIAMProxy],
88
name: 'AWS CodeWhisperer',
99
}
1010
standalone(props)

app/aws-lsp-codewhisperer-runtimes/src/iam-webworker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { webworker } from '@aws/language-server-runtimes/runtimes/webworker'
22
import { RuntimeProps } from '@aws/language-server-runtimes/runtimes/runtime'
33
import { CodeWhispererServerIAM } from '@aws/lsp-codewhisperer/out/language-server/inline-completion/codeWhispererServer'
4+
import { QChatServerIAM } from '@aws/lsp-codewhisperer/out/language-server/chat/qChatServer'
45

56
const props: RuntimeProps = {
67
version: '1.0.0',
7-
servers: [CodeWhispererServerIAM],
8+
servers: [CodeWhispererServerIAM, QChatServerIAM],
89
name: 'AWS CodeWhisperer',
910
}
1011

app/aws-lsp-codewhisperer-runtimes/src/token-standalone.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { RuntimeProps } from '@aws/language-server-runtimes/runtimes/runtime'
33
import {
44
CodeWhispererSecurityScanServerTokenProxy,
55
CodeWhispererServerTokenProxy,
6-
QChatServerProxy,
6+
QChatServerTokenProxy,
77
QConfigurationServerTokenProxy,
88
QNetTransformServerTokenProxy,
99
QLocalProjectContextServerTokenProxy,
@@ -22,7 +22,7 @@ const props: RuntimeProps = {
2222
CodeWhispererSecurityScanServerTokenProxy,
2323
QConfigurationServerTokenProxy,
2424
QNetTransformServerTokenProxy,
25-
QChatServerProxy,
25+
QChatServerTokenProxy,
2626
IdentityServer.create,
2727
QLocalProjectContextServerTokenProxy,
2828
],

client/vscode/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This extension contains functioning code that shows an example of how extensions
88

99
The intent behind this concept code is to show how an extension could "push" credentials to the server whenever they are resolved (eg: when a user configures credentials for use with the server, or when the credentials have been refreshed). To simulate these events, the following commands can be run from the extension:
1010

11-
- awslsp.selectProfile (sigv4) - used to simulate when credentials are pushed to the server. When you run this command, you will be prompted to enter a profile name. This profile's credentials will be resolved from your shared credentials file, then pushed to the server. (Only basic access key - secret key credentials type is supported in this example).
11+
- awslsp.selectProfile (sigv4) - used to simulate when credentials are pushed to the server. When you run this command, you will be prompted to enter a profile name. This profile's credentials will be resolved from your shared credentials file, then pushed to the server. (Only basic access key - secret key credentials type is supported in this example). From the minimal vscode client launched from "Run and Debug" with [CodeWhisperer Server IAM](https://github.com/aws/language-servers/blob/694bbb85580cc79313d65ad77b224875f74280c2/.vscode/launch.json#L100) configuration, the command is accessible via the command palette as ['AWS LSP - Choose credentials profile, resolve, and send to LSP Server'](https://github.com/aws/language-servers/blob/694bbb85580cc79313d65ad77b224875f74280c2/client/vscode/package.json#L32-L33).
1212
- awslsp.clearProfile (sigv4), awslsp.clearBearerToken (token) - used to simulate when credentials are removed from the server
1313
- awslsp.resolveBearerToken (token) - used to simulate when bearer tokens are pushed to the server. When you run this command, you will be taken through an SSO login flow, similar to what the AWS Toolkit for VS Code does. Once logged in, the bearer token will be pushed to the server.
1414

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import {
77
ChatTriggerType,
88
GenerateAssistantResponseCommandInput,
99
GenerateAssistantResponseCommandOutput,
10-
SendMessageCommandInput,
11-
SendMessageCommandOutput,
10+
SendMessageCommandInput as SendMessageCommandInputCodeWhispererStreaming,
1211
ToolResult,
1312
ToolResultContentBlock,
1413
ToolUse,
@@ -64,6 +63,7 @@ import { AmazonQTokenServiceManager } from '../../shared/amazonQServiceManager/A
6463
import { AmazonQWorkspaceConfig } from '../../shared/amazonQServiceManager/configurationUtils'
6564
import { TabBarController } from './tabBarController'
6665
import { ChatDatabase } from './tools/chatDb/chatDb'
66+
import { SendMessageCommandInput, SendMessageCommandOutput } from '../../shared/streamingClientService'
6767
import {
6868
AgenticChatEventParser,
6969
ChatResultWithMetadata as AgenticChatResultWithMetadata,
@@ -512,21 +512,19 @@ export class AgenticChatController implements ChatHandlers {
512512
let requestInput: SendMessageCommandInput
513513

514514
try {
515-
const profileArn = AmazonQTokenServiceManager.getInstance(this.#features).getActiveProfileArn()
516515
requestInput = this.#triggerContext.getChatParamsFromTrigger(
517516
params,
518517
triggerContext,
519518
ChatTriggerType.INLINE_CHAT,
520-
this.#customizationArn,
521-
profileArn
519+
this.#customizationArn
522520
)
523521

524522
if (!this.#amazonQServiceManager) {
525523
throw new Error('amazonQServiceManager is not initialized')
526524
}
527525

528526
const client = this.#amazonQServiceManager.getStreamingClient()
529-
response = await client.sendMessage(requestInput)
527+
response = await client.sendMessage(requestInput as SendMessageCommandInputCodeWhispererStreaming)
530528
this.#log('Response for inline chat', JSON.stringify(response.$metadata), JSON.stringify(response))
531529
} catch (err) {
532530
if (err instanceof AmazonQServicePendingSigninError || err instanceof AmazonQServicePendingProfileError) {

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

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ export const QAgenticChatServer =
2020
(): Server => features => {
2121
const { chat, credentialsProvider, telemetry, logging, lsp, runtime, agent } = features
2222

23+
// AmazonQTokenServiceManager and TelemetryService are initialized in `onInitialized` handler to make sure Language Server connection is started
2324
let amazonQServiceManager: AmazonQTokenServiceManager
25+
let telemetryService: TelemetryService
26+
2427
let chatController: AgenticChatController
2528
let chatSessionManagementService: ChatSessionManagementService
26-
let telemetryService: TelemetryService;
2729

2830
lsp.addInitializer((params: InitializeParams) => {
2931
return {
@@ -52,29 +54,32 @@ export const QAgenticChatServer =
5254
lsp.onInitialized(async () => {
5355
// Initialize service manager and inject it to chatSessionManagementService to pass it down
5456
amazonQServiceManager = AmazonQTokenServiceManager.getInstance(features)
55-
chatSessionManagementService = ChatSessionManagementService
56-
.getInstance()
57-
.withAmazonQServiceManager(amazonQServiceManager)
58-
59-
telemetryService = new TelemetryService(
60-
amazonQServiceManager,
61-
credentialsProvider,
62-
telemetry,
63-
logging,
64-
)
57+
chatSessionManagementService =
58+
ChatSessionManagementService.getInstance().withAmazonQServiceManager(amazonQServiceManager)
6559

66-
const clientParams = safeGet(lsp.getClientInitializeParams(), new AmazonQServiceInitializationError(
67-
'TelemetryService initialized before LSP connection was initialized.'))
60+
telemetryService = new TelemetryService(amazonQServiceManager, credentialsProvider, telemetry, logging)
61+
62+
const clientParams = safeGet(
63+
lsp.getClientInitializeParams(),
64+
new AmazonQServiceInitializationError(
65+
'TelemetryService initialized before LSP connection was initialized.'
66+
)
67+
)
6868

6969
telemetryService.updateUserContext(makeUserContextObject(clientParams, runtime.platform, 'CHAT'))
7070

71-
chatController = new AgenticChatController(chatSessionManagementService, features, telemetryService, amazonQServiceManager)
71+
chatController = new AgenticChatController(
72+
chatSessionManagementService,
73+
features,
74+
telemetryService,
75+
amazonQServiceManager
76+
)
7277

7378
/*
74-
Calling handleDidChangeConfiguration once to ensure we get configuration atleast once at start up
75-
76-
TODO: TODO: consider refactoring such responsibilities to common service manager config/initialisation server
77-
*/
79+
Calling handleDidChangeConfiguration once to ensure we get configuration atleast once at start up
80+
81+
TODO: TODO: consider refactoring such responsibilities to common service manager config/initialisation server
82+
*/
7883
await amazonQServiceManager.handleDidChangeConfiguration()
7984
await amazonQServiceManager.addDidChangeConfigurationListener(updateConfigurationHandler)
8085
})
@@ -134,7 +139,7 @@ export const QAgenticChatServer =
134139
}
135140
}
136141
}
137-
} as const, async (input) =>
142+
} as const, async (input) =>
138143
(await features.workspace.getAllTextDocuments()).map(td => td.uri).filter(uri => input.filter === undefined || uri.includes(input.filter))
139144
)
140145

@@ -156,7 +161,7 @@ If a file is not open, use the \`fsRead\` tool to read from disk. Use this tool
156161
},
157162
required: ['paths']
158163
}
159-
} as const, async (input) =>
164+
} as const, async (input) =>
160165
input.paths.reduce(async (acc, path) => {
161166
const doc = await features.workspace.getTextDocument(path)
162167
if (doc) {

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChatTriggerType, SendMessageCommandInput, SendMessageCommandOutput } from '@amzn/codewhisperer-streaming'
1+
import { ChatTriggerType } from '@amzn/codewhisperer-streaming'
22
import {
33
ApplyWorkspaceEditParams,
44
ErrorCodes,
@@ -47,7 +47,8 @@ import {
4747
} from '../../shared/amazonQServiceManager/errors'
4848
import { TelemetryService } from '../../shared/telemetry/telemetryService'
4949
import { AmazonQWorkspaceConfig } from '../../shared/amazonQServiceManager/configurationUtils'
50-
import { AmazonQTokenServiceManager } from '../../shared/amazonQServiceManager/AmazonQTokenServiceManager'
50+
import { AmazonQBaseServiceManager } from '../../shared/amazonQServiceManager/BaseAmazonQServiceManager'
51+
import { SendMessageCommandInput, SendMessageCommandOutput } from '../../shared/streamingClientService'
5152

5253
type ChatHandlers = Omit<
5354
LspHandlers<Chat>,
@@ -71,13 +72,13 @@ export class ChatController implements ChatHandlers {
7172
#triggerContext: QChatTriggerContext
7273
#customizationArn?: string
7374
#telemetryService: TelemetryService
74-
#amazonQServiceManager: AmazonQTokenServiceManager
75+
#amazonQServiceManager: AmazonQBaseServiceManager
7576

7677
constructor(
7778
chatSessionManagementService: ChatSessionManagementService,
7879
features: Features,
7980
telemetryService: TelemetryService,
80-
amazonQServiceManager: AmazonQTokenServiceManager
81+
amazonQServiceManager: AmazonQBaseServiceManager
8182
) {
8283
this.#features = features
8384
this.#chatSessionManagementService = chatSessionManagementService

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { ChatResponseStream, Reference, SupplementaryWebLink } from '@amzn/codewhisperer-streaming'
1+
import {
2+
ChatResponseStream as ChatResponseStreamCodeWhispererStreaming,
3+
Reference,
4+
SupplementaryWebLink,
5+
} from '@amzn/codewhisperer-streaming'
6+
import { ChatResponseStream as ChatResponseStreamQDeveloperStreaming } from '@amzn/amazon-q-developer-streaming-client'
27
import {
38
ChatItemAction,
49
ChatResult,
@@ -8,6 +13,7 @@ import {
813
import { Result } from '../types'
914
import { AddMessageEvent } from '../../shared/telemetry/types'
1015
import { Metric } from '../../shared/telemetry/metric'
16+
export type ChatResponseStream = ChatResponseStreamCodeWhispererStreaming | ChatResponseStreamQDeveloperStreaming
1117

1218
export type ChatResultWithMetadata = {
1319
chatResult: ChatResult

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { Result } from '../types'
22
import { ChatSessionService } from './chatSessionService'
3-
import { AmazonQTokenServiceManager } from '../../shared/amazonQServiceManager/AmazonQTokenServiceManager'
4-
3+
import { AmazonQBaseServiceManager } from '../../shared/amazonQServiceManager/BaseAmazonQServiceManager'
54
export class ChatSessionManagementService {
65
static #instance?: ChatSessionManagementService
76
#sessionByTab: Map<string, ChatSessionService> = new Map<string, any>()
8-
#amazonQServiceManager?: AmazonQTokenServiceManager
7+
#amazonQServiceManager?: AmazonQBaseServiceManager
98

109
public static getInstance() {
1110
if (!ChatSessionManagementService.#instance) {
@@ -21,7 +20,7 @@ export class ChatSessionManagementService {
2120

2221
private constructor() {}
2322

24-
public withAmazonQServiceManager(amazonQServiceManager: AmazonQTokenServiceManager) {
23+
public withAmazonQServiceManager(amazonQServiceManager: AmazonQBaseServiceManager) {
2524
this.#amazonQServiceManager = amazonQServiceManager
2625

2726
return this

0 commit comments

Comments
 (0)