Skip to content
Open
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
4 changes: 4 additions & 0 deletions app/aws-lsp-codewhisperer-runtimes/src/agent-standalone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
CodeWhispererServer,
QAgenticChatServerProxy,
QConfigurationServerTokenProxy,
TransformConfigurationServerTokenProxy,
AtxNetTransformServerTokenProxy,
QLocalProjectContextServerProxy,
QNetTransformServerTokenProxy,
WorkspaceContextServerTokenProxy,
Expand All @@ -28,6 +30,8 @@ const props = {
CodeWhispererServer,
CodeWhispererSecurityScanServerTokenProxy,
QConfigurationServerTokenProxy,
TransformConfigurationServerTokenProxy,
AtxNetTransformServerTokenProxy,
QNetTransformServerTokenProxy,
QAgenticChatServerProxy,
IdentityServer.create,
Expand Down
4 changes: 4 additions & 0 deletions app/aws-lsp-codewhisperer-runtimes/src/token-standalone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
CodeWhispererServerTokenProxy,
QChatServerTokenProxy,
QConfigurationServerTokenProxy,
TransformConfigurationServerTokenProxy,
AtxNetTransformServerTokenProxy,
QNetTransformServerTokenProxy,
QLocalProjectContextServerProxy,
WorkspaceContextServerTokenProxy,
Expand All @@ -20,6 +22,8 @@ const props = createTokenRuntimeProps(VERSION, [
CodeWhispererServerTokenProxy,
CodeWhispererSecurityScanServerTokenProxy,
QConfigurationServerTokenProxy,
TransformConfigurationServerTokenProxy,
AtxNetTransformServerTokenProxy,
QNetTransformServerTokenProxy,
QChatServerTokenProxy,
IdentityServer.create,
Expand Down
Binary file not shown.
900 changes: 899 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion server/aws-lsp-codewhisperer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"postinstall": "node ./script/install_transitive_dep.js"
},
"dependencies": {
"@amazon/elastic-gumby-frontend-client": "file:../../core/atx-fes-client/amazon-elastic-gumby-frontend-client-1.0.0.tgz",
"@amzn/amazon-q-developer-streaming-client": "file:../../core/q-developer-streaming-client/amzn-amazon-q-developer-streaming-client-1.0.0.tgz",
"@amzn/codewhisperer": "file:../../core/codewhisperer/amzn-codewhisperer-1.0.0.tgz",
"@amzn/codewhisperer-runtime": "file:../../core/codewhisperer-runtime/amzn-codewhisperer-runtime-1.0.0.tgz",
Expand Down Expand Up @@ -106,6 +107,7 @@
"@amzn/codewhisperer",
"@amzn/codewhisperer-runtime",
"@amzn/codewhisperer-streaming",
"@amzn/amazon-q-developer-streaming-client"
"@amzn/amazon-q-developer-streaming-client",
"@amazon/elastic-gumby-frontend-client"
]
}
1 change: 1 addition & 0 deletions server/aws-lsp-codewhisperer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from './language-server/chat/qChatServer'
export * from './language-server/agenticChat/qAgenticChatServer'
export * from './shared/proxy-server'
export * from './language-server/netTransform/netTransformServer'
export * from './language-server/netTransform/atxNetTransformServer'
export { AmazonQServiceServerIAM, AmazonQServiceServerToken } from './shared/amazonQServer'
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
import {
CancellationToken,
CredentialsProvider,
GetConfigurationFromServerParams,
InitializeParams,
Logging,
LSPErrorCodes,
ResponseError,
Server,
BearerCredentials,
} from '@aws/language-server-runtimes/server-interface'
import { AmazonQDeveloperProfile } from '../../shared/amazonQServiceManager/qDeveloperProfiles'
import { ElasticGumbyFrontendClient, ListAvailableProfilesCommand } from '@amazon/elastic-gumby-frontend-client'
import {
DEFAULT_ATX_FES_ENDPOINT_URL,
DEFAULT_ATX_FES_REGION,
ATX_FES_REGION_ENV_VAR,
ATX_FES_ENDPOINT_URL_ENV_VAR,
ATX_FES_ENDPOINTS,
} from '../../shared/constants'
import { getBearerTokenFromProvider } from '../../shared/utils'

// Transform Configuration Sections
export const TRANSFORM_PROFILES_CONFIGURATION_SECTION = 'aws.transformProfiles'

/**
* Transform Configuration Server - standalone server for ATX FES profile management
* Completely separate from qConfigurationServer to maintain clean RTS/ATX FES separation
*/
export class TransformConfigurationServer {
private atxClient: ElasticGumbyFrontendClient | null = null

constructor(
private readonly logging: Logging,
private readonly credentialsProvider: CredentialsProvider
) {
this.logging.log('TransformConfigurationServer: Constructor called - server created')
}

/**
* Initialize as standalone LSP server
*/
async initialize(params: InitializeParams): Promise<any> {
this.logging.log('TransformConfigurationServer: Initialize called')
return {
capabilities: {},
awsServerCapabilities: {
configurationProvider: {
sections: [TRANSFORM_PROFILES_CONFIGURATION_SECTION],
},
},
}
}

/**
* Handle configuration requests for Transform profiles
*/
async getConfiguration(params: GetConfigurationFromServerParams, token: CancellationToken): Promise<any> {
this.logging.log(`TransformConfigurationServer: Configuration requested for section: ${params.section}`)

switch (params.section) {
case TRANSFORM_PROFILES_CONFIGURATION_SECTION:
const profiles = await this.listAvailableProfiles(token)
return profiles
default:
throw new ResponseError(
LSPErrorCodes.RequestFailed,
`TransformConfigurationServer: Unsupported configuration section: ${params.section}`
)
}
}

/**
* Initialize ATX FES client with bearer token authentication
*/
private async initializeAtxClient(): Promise<boolean> {
try {
if (!this.credentialsProvider?.hasCredentials('bearer')) {
return false
}

const credentials = (await this.credentialsProvider.getCredentials('bearer')) as BearerCredentials
if (!credentials?.token) {
return false
}

const region = await this.getClientRegion()
const endpoint = this.getEndpointForRegion(region)

this.logging.log(
`TransformConfigurationServer: Initializing ATX client with region: ${region}, endpoint: ${endpoint}`
)

this.atxClient = new ElasticGumbyFrontendClient({
region: region,
endpoint: endpoint,
})

return true
} catch (error) {
const region = await this.getClientRegion()
const endpoint = this.getEndpointForRegion(region)
this.logging.warn(
`TransformConfigurationServer: Failed to initialize ATX client with region: ${region}, endpoint: ${endpoint}. Error: ${error}`
)
return false
}
}

/**
* Get region for ATX FES client - supports dynamic region selection
*/
private async getClientRegion(): Promise<string> {
// Check environment variable first
const envRegion = process.env[ATX_FES_REGION_ENV_VAR]
if (envRegion) {
return envRegion
}

// Try to get region from profile
const profileRegion = await this.getRegionFromProfile()
if (profileRegion) {
return profileRegion
}

// Fall back to default
return DEFAULT_ATX_FES_REGION
}

private async getRegionFromProfile(): Promise<string | undefined> {
try {
if (!this.credentialsProvider?.hasCredentials('bearer')) {
return undefined
}

const tempClient = new ElasticGumbyFrontendClient({
region: DEFAULT_ATX_FES_REGION,
endpoint: DEFAULT_ATX_FES_ENDPOINT_URL,
})

const command = new ListAvailableProfilesCommand({ maxResults: 100 })
const response = await tempClient.send(command)
const profiles = response.profiles || []

const activeProfile = profiles.find((p: any) => p.arn)
if (activeProfile?.arn) {
const arnParts = activeProfile.arn.split(':')
if (arnParts.length >= 4) {
return arnParts[3]
}
}

return undefined
} catch (error) {
return undefined
}
}

/**
* Get endpoint URL for the specified region
*/
private getEndpointForRegion(region: string): string {
return (
process.env[ATX_FES_ENDPOINT_URL_ENV_VAR] || ATX_FES_ENDPOINTS.get(region) || DEFAULT_ATX_FES_ENDPOINT_URL
)
}

/**
* Add bearer token authentication to ATX FES command
*/
private async addBearerTokenToCommand(command: any): Promise<void> {
const credentials = (await this.credentialsProvider.getCredentials('bearer')) as BearerCredentials
if (!credentials?.token) {
throw new Error('No bearer token available for ATX FES authentication')
}

command.middlewareStack?.add(
(next: any) => async (args: any) => {
args.request.headers = {
...args.request.headers,
Authorization: `Bearer ${credentials.token}`,
}
return next(args)
},
{ step: 'build', priority: 'high' }
)
}

/**
* List available Transform profiles using ATX FES ListAvailableProfiles API
* Uses multi-region discovery similar to RTS approach
*/
async listAvailableProfiles(token: CancellationToken): Promise<AmazonQDeveloperProfile[]> {
try {
const allProfiles: AmazonQDeveloperProfile[] = []

for (const [region, endpoint] of ATX_FES_ENDPOINTS) {
try {
if (token?.isCancellationRequested) {
throw new ResponseError(LSPErrorCodes.RequestCancelled, 'Request cancelled')
}

const profiles = await this.listAvailableProfilesForRegion(region, endpoint)
allProfiles.push(...profiles)
this.logging.log(
`TransformConfigurationServer: Found ${profiles.length} profiles in region ${region}`
)
} catch (error) {
this.logging.debug(`TransformConfigurationServer: No profiles in region ${region}: ${error}`)
}
}

this.logging.log(
`TransformConfigurationServer: Total ${allProfiles.length} Transform profiles found across all regions`
)
return allProfiles
} catch (error) {
this.logging.warn(`TransformConfigurationServer: ListAvailableProfiles failed: ${error}`)
return []
}
}

/**
* List available profiles for a specific region (similar to RTS listAvailableCustomizationsForProfileAndRegion)
*/
private async listAvailableProfilesForRegion(region: string, endpoint: string): Promise<AmazonQDeveloperProfile[]> {
this.logging.log(`TransformConfigurationServer: Querying region: ${region}, endpoint: ${endpoint}`)

// Create region-specific client (similar to RTS approach)
const regionClient = new ElasticGumbyFrontendClient({
region: region,
endpoint: endpoint,
})

const command = new ListAvailableProfilesCommand({
maxResults: 100,
})

await this.addBearerTokenToCommand(command)
const response = await regionClient.send(command)

// Convert ATX FES profiles to AmazonQDeveloperProfile format
const transformProfiles: AmazonQDeveloperProfile[] = (response.profiles || []).map((profile: any) => {
const convertedProfile = {
arn: profile.arn || '',
name: profile.profileName || profile.applicationUrl || 'Unnamed Transform Profile',
applicationUrl: (profile.applicationUrl || '').replace(/\/$/, ''), // Strip trailing slash
identityDetails: {
region: region,
accountId: profile.accountId || '',
},
}

return convertedProfile
})

return transformProfiles
}
}

/**
* Transform Configuration Server Token - creates standalone Transform configuration server
*/
export const TransformConfigurationServerToken = (): Server => {
return ({ credentialsProvider, lsp, logging }) => {
let transformConfigurationServer: TransformConfigurationServer

lsp.addInitializer(async params => {
transformConfigurationServer = new TransformConfigurationServer(logging, credentialsProvider)
return transformConfigurationServer.initialize(params)
})

lsp.extensions.onGetConfigurationFromServer(
async (params: GetConfigurationFromServerParams, token: CancellationToken) => {
logging.log('TransformConfigurationServer: onGetConfigurationFromServer handler called')
return transformConfigurationServer.getConfiguration(params, token)
}
)

return () => {}
}
}
Loading
Loading