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
5 changes: 3 additions & 2 deletions packages/amazonq/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import vscode, { env, version } from 'vscode'
import vscode, { version } from 'vscode'
import * as nls from 'vscode-nls'
import { LanguageClient, LanguageClientOptions, RequestType, State } from 'vscode-languageclient'
import { InlineCompletionManager } from '../app/inline/completion'
Expand Down Expand Up @@ -38,6 +38,7 @@ import {
getOptOutPreference,
isAmazonLinux2,
getClientId,
getClientName,
extensionVersion,
isSageMaker,
DevSettings,
Expand Down Expand Up @@ -163,7 +164,7 @@ export async function startLanguageServer(
initializationOptions: {
aws: {
clientInfo: {
name: env.appName,
name: getClientName(),
version: version,
extension: {
name: 'AmazonQ-For-VSCode',
Expand Down
23 changes: 18 additions & 5 deletions packages/core/src/shared/extensionUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,27 @@ function createCloud9Properties(company: string): IdeProperties {
}
}

function isSageMakerUnifiedStudio(): boolean {
/**
* export method - for testing purposes only
* @internal
*/
export function isSageMakerUnifiedStudio(): boolean {
if (serviceName === notInitialized) {
serviceName = process.env.SERVICE_NAME ?? ''
isSMUS = serviceName === sageMakerUnifiedStudio
}
return isSMUS
}

/**
* Reset cached SageMaker state - for testing purposes only
* @internal
*/
export function resetSageMakerState(): void {
serviceName = notInitialized
isSMUS = false
}

/**
* Decides if the current system is (the specified flavor of) Cloud9.
*/
Expand All @@ -177,17 +190,17 @@ export function isCloud9(flavor: 'classic' | 'codecatalyst' | 'any' = 'any'): bo
*/
export function isSageMaker(appName: 'SMAI' | 'SMUS' = 'SMAI'): boolean {
// Check for SageMaker-specific environment variables first
let hasSMEnvVars: boolean = false
if (hasSageMakerEnvVars()) {
getLogger().debug('SageMaker environment detected via environment variables')
return true
hasSMEnvVars = true
}

// Fall back to app name checks
switch (appName) {
case 'SMAI':
return vscode.env.appName === sageMakerAppname
return vscode.env.appName === sageMakerAppname && hasSMEnvVars
case 'SMUS':
return vscode.env.appName === sageMakerAppname && isSageMakerUnifiedStudio()
return vscode.env.appName === sageMakerAppname && isSageMakerUnifiedStudio() && hasSMEnvVars
default:
return false
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export { Prompter } from './ui/prompter'
export { VirtualFileSystem } from './virtualFilesystem'
export { VirtualMemoryFile } from './virtualMemoryFile'
export { AmazonqCreateUpload, Metric } from './telemetry/telemetry'
export { getClientId, getOperatingSystem, getOptOutPreference } from './telemetry/util'
export { getClientId, getClientName, getOperatingSystem, getOptOutPreference } from './telemetry/util'
export { extensionVersion } from './vscode/env'
export { cast } from './utilities/typeConstructors'
export * as workspaceUtils from './utilities/workspaceUtils'
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/shared/telemetry/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,3 +481,15 @@ export function withTelemetryContext(opts: TelemetryContextArgs) {
})
}
}

/**
* Used to identify the q client info and send the respective origin parameter from LSP to invoke Maestro service at CW API level
*
* Returns default value of vscode appName or AmazonQ-For-SMUS-CE in case of a sagemaker unified studio environment
*/
export function getClientName(): string {
if (isSageMaker('SMUS')) {
return 'AmazonQ-For-SMUS-CE'
}
return env.appName
}
142 changes: 142 additions & 0 deletions packages/core/src/test/shared/extensionUtilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import globals from '../../shared/extensionGlobals'
import { maybeShowMinVscodeWarning } from '../../shared/extensionStartup'
import { getTestWindow } from './vscode/window'
import { assertTelemetry } from '../testUtil'
import { isSageMaker } from '../../shared/extensionUtilities'
import { hasSageMakerEnvVars } from '../../shared/vscode/env'

describe('extensionUtilities', function () {
it('maybeShowMinVscodeWarning', async () => {
Expand Down Expand Up @@ -361,3 +363,143 @@ describe('UserActivity', function () {
return event.event
}
})

describe('isSageMaker', function () {
let sandbox: sinon.SinonSandbox
const env = require('../../shared/vscode/env')
const utils = require('../../shared/extensionUtilities')

beforeEach(function () {
sandbox = sinon.createSandbox()
utils.resetSageMakerState()
})

afterEach(function () {
sandbox.restore()
})

describe('SMAI detection', function () {
it('returns true when both app name and env vars match', function () {
sandbox.stub(vscode.env, 'appName').value('SageMaker Code Editor')
sandbox.stub(env, 'hasSageMakerEnvVars').returns(true)

assert.strictEqual(isSageMaker('SMAI'), true)
})

it('returns false when app name is different', function () {
sandbox.stub(vscode.env, 'appName').value('Visual Studio Code')
sandbox.stub(env, 'hasSageMakerEnvVars').returns(true)

assert.strictEqual(isSageMaker('SMAI'), false)
})

it('returns false when env vars are missing', function () {
sandbox.stub(vscode.env, 'appName').value('SageMaker Code Editor')
sandbox.stub(env, 'hasSageMakerEnvVars').returns(false)

assert.strictEqual(isSageMaker('SMAI'), false)
})

it('defaults to SMAI when no parameter provided', function () {
sandbox.stub(vscode.env, 'appName').value('SageMaker Code Editor')
sandbox.stub(env, 'hasSageMakerEnvVars').returns(true)

assert.strictEqual(isSageMaker(), true)
})
})

describe('SMUS detection', function () {
it('returns true when all conditions are met', function () {
sandbox.stub(vscode.env, 'appName').value('SageMaker Code Editor')
sandbox.stub(env, 'hasSageMakerEnvVars').returns(true)
sandbox.stub(process, 'env').value({ SERVICE_NAME: 'SageMakerUnifiedStudio' })
utils.resetSageMakerState()

assert.strictEqual(isSageMaker('SMUS'), true)
})

it('returns false when unified studio is missing', function () {
sandbox.stub(vscode.env, 'appName').value('SageMaker Code Editor')
sandbox.stub(env, 'hasSageMakerEnvVars').returns(true)
sandbox.stub(process, 'env').value({ SERVICE_NAME: 'SomeOtherService' })
utils.resetSageMakerState()

assert.strictEqual(isSageMaker('SMUS'), false)
})

it('returns false when env vars are missing', function () {
sandbox.stub(vscode.env, 'appName').value('SageMaker Code Editor')
sandbox.stub(env, 'hasSageMakerEnvVars').returns(false)
sandbox.stub(process, 'env').value({ SERVICE_NAME: 'SageMakerUnifiedStudio' })
utils.resetSageMakerState()

assert.strictEqual(isSageMaker('SMUS'), false)
})

it('returns false when app name is different', function () {
sandbox.stub(vscode.env, 'appName').value('Visual Studio Code')
sandbox.stub(env, 'hasSageMakerEnvVars').returns(true)
sandbox.stub(process, 'env').value({ SERVICE_NAME: 'SageMakerUnifiedStudio' })
utils.resetSageMakerState()

assert.strictEqual(isSageMaker('SMUS'), false)
})
})

it('returns false for invalid appName parameter', function () {
sandbox.stub(vscode.env, 'appName').value('SageMaker Code Editor')
sandbox.stub(env, 'hasSageMakerEnvVars').returns(true)

// @ts-ignore - Testing invalid input
assert.strictEqual(isSageMaker('INVALID'), false)
})
})

describe('hasSageMakerEnvVars', function () {
let sandbox: sinon.SinonSandbox

beforeEach(function () {
sandbox = sinon.createSandbox()
})

afterEach(function () {
sandbox.restore()
})

it('detects SageMaker environment variables', function () {
// Test SAGEMAKER_ prefix
sandbox.stub(process, 'env').value({ SAGEMAKER_APP_TYPE: 'JupyterServer' })
assert.strictEqual(hasSageMakerEnvVars(), true)

// Test SM_ prefix
sandbox.stub(process, 'env').value({ SM_APP_TYPE: 'CodeEditor' })
assert.strictEqual(hasSageMakerEnvVars(), true)

// Test SERVICE_NAME with correct value
sandbox.stub(process, 'env').value({ SERVICE_NAME: 'SageMakerUnifiedStudio' })
assert.strictEqual(hasSageMakerEnvVars(), true)

// Test STUDIO_LOGGING_DIR with correct path
sandbox.stub(process, 'env').value({ STUDIO_LOGGING_DIR: '/var/log/studio/app.log' })
assert.strictEqual(hasSageMakerEnvVars(), true)

// Test invalid SERVICE_NAME
sandbox.stub(process, 'env').value({ SERVICE_NAME: 'SomeOtherService' })
assert.strictEqual(hasSageMakerEnvVars(), false)

// Test invalid STUDIO_LOGGING_DIR
sandbox.stub(process, 'env').value({ STUDIO_LOGGING_DIR: '/var/log/other/app.log' })
assert.strictEqual(hasSageMakerEnvVars(), false)

// Test multiple env vars
sandbox.stub(process, 'env').value({
SAGEMAKER_APP_TYPE: 'JupyterServer',
SM_APP_TYPE: 'CodeEditor',
})
assert.strictEqual(hasSageMakerEnvVars(), true)

// Test no env vars
sandbox.stub(process, 'env').value({})
assert.strictEqual(hasSageMakerEnvVars(), false)
})
})
57 changes: 57 additions & 0 deletions packages/core/src/test/shared/telemetry/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import { randomUUID } from 'crypto'
import { isUuid } from '../../../shared/crypto'
import { MetricDatum } from '../../../shared/telemetry/clienttelemetry'
import { assertLogsContain } from '../../globalSetup.test'
import { getClientName } from '../../../shared/telemetry/util'
import * as extensionUtilities from '../../../shared/extensionUtilities'
import * as sinon from 'sinon'
import * as vscode from 'vscode'

describe('TelemetryConfig', function () {
const settingKey = 'aws.telemetry'
Expand Down Expand Up @@ -391,3 +395,56 @@ describe('validateMetricEvent', function () {
assertLogsContain('invalid Metric', false, 'warn')
})
})

describe('getClientName', function () {
let sandbox: sinon.SinonSandbox
let isSageMakerStub: sinon.SinonStub

beforeEach(function () {
sandbox = sinon.createSandbox()
isSageMakerStub = sandbox.stub(extensionUtilities, 'isSageMaker')
})

afterEach(function () {
sandbox.restore()
})

it('returns "AmazonQ-For-SMUS-CE" when in SMUS environment', function () {
isSageMakerStub.withArgs('SMUS').returns(true)
sandbox.stub(vscode.env, 'appName').value('SageMaker Code Editor')

const result = getClientName()

assert.strictEqual(result, 'AmazonQ-For-SMUS-CE')
assert.ok(isSageMakerStub.calledOnceWith('SMUS'))
})

it('returns vscode app name when not in SMUS environment', function () {
const mockAppName = 'Visual Studio Code'
isSageMakerStub.withArgs('SMUS').returns(false)
sandbox.stub(vscode.env, 'appName').value(mockAppName)

const result = getClientName()

assert.strictEqual(result, mockAppName)
assert.ok(isSageMakerStub.calledOnceWith('SMUS'))
})

it('handles undefined app name gracefully', function () {
isSageMakerStub.withArgs('SMUS').returns(false)
sandbox.stub(vscode.env, 'appName').value(undefined)

const result = getClientName()

assert.strictEqual(result, undefined)
})

it('prioritizes SMUS detection over app name', function () {
isSageMakerStub.withArgs('SMUS').returns(true)
sandbox.stub(vscode.env, 'appName').value('SageMaker Code Editor')

const result = getClientName()

assert.strictEqual(result, 'AmazonQ-For-SMUS-CE')
})
})
Loading