Skip to content

Commit 97636a1

Browse files
authored
Merge branch 'feature/cloudformation' into feature/cloudformation
2 parents 44672d9 + 4ad0d5c commit 97636a1

30 files changed

+1121
-1873
lines changed

LICENSE-THIRD-PARTY

Lines changed: 70 additions & 1714 deletions
Large diffs are not rendered by default.

package-lock.json

Lines changed: 323 additions & 58 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"date": "2025-11-15",
3+
"version": "1.104.0",
4+
"entries": []
5+
}

packages/amazonq/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.104.0 2025-11-15
2+
3+
- Miscellaneous non-user-facing changes
4+
15
## 1.103.0 2025-11-06
26

37
- **Feature** Q CodeTransformation: add more job metadata to history table

packages/amazonq/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "amazon-q-vscode",
33
"displayName": "Amazon Q",
44
"description": "The most capable generative AI–powered assistant for software development.",
5-
"version": "1.104.0-SNAPSHOT",
5+
"version": "1.105.0-SNAPSHOT",
66
"extensionKind": [
77
"workspace"
88
],

packages/core/src/awsService/sagemaker/commands.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,16 @@ export async function deeplinkConnect(
9696
wsUrl: string,
9797
token: string,
9898
domain: string,
99-
appType?: string
99+
appType?: string,
100+
isSMUS: boolean = false
100101
) {
101102
getLogger().debug(
102-
`sm:deeplinkConnect: connectionIdentifier: ${connectionIdentifier} session: ${session} wsUrl: ${wsUrl} token: ${token}`
103+
'sm:deeplinkConnect: connectionIdentifier: %s session: %s wsUrl: %s token: %s isSMUS: %s',
104+
connectionIdentifier,
105+
session,
106+
wsUrl,
107+
token,
108+
isSMUS
103109
)
104110

105111
if (isRemoteWorkspace()) {
@@ -112,7 +118,7 @@ export async function deeplinkConnect(
112118
connectionIdentifier,
113119
ctx.extensionContext,
114120
'sm_dl',
115-
false /* isSMUS */,
121+
isSMUS,
116122
undefined /* node */,
117123
session,
118124
wsUrl,
@@ -130,7 +136,10 @@ export async function deeplinkConnect(
130136
)
131137
} catch (err: any) {
132138
getLogger().error(
133-
`sm:OpenRemoteConnect: Unable to connect to target space with arn: ${connectionIdentifier} error: ${err}`
139+
'sm:OpenRemoteConnect: Unable to connect to target space with arn: %s error: %s isSMUS: %s',
140+
connectionIdentifier,
141+
err,
142+
isSMUS
134143
)
135144

136145
if (![RemoteSessionError.MissingExtension, RemoteSessionError.ExtensionVersionTooLow].includes(err.code)) {
@@ -292,7 +301,7 @@ async function handleRunningSpaceWithDisabledAccess(
292301

293302
const confirmed = await showConfirmationMessage({
294303
prompt,
295-
confirm: 'Restart and Connect',
304+
confirm: 'Restart Space and Connect',
296305
cancel: 'Cancel',
297306
type: 'warning',
298307
})
@@ -332,7 +341,8 @@ async function handleRunningSpaceWithDisabledAccess(
332341
await client.waitForAppInService(
333342
node.spaceApp.DomainId!,
334343
spaceName,
335-
node.spaceApp.SpaceSettingsSummary!.AppType!
344+
node.spaceApp.SpaceSettingsSummary!.AppType!,
345+
progress
336346
)
337347
await tryRemoteConnection(node, ctx, progress)
338348
} catch (err: any) {
@@ -376,7 +386,8 @@ async function handleStoppedSpace(
376386
await client.waitForAppInService(
377387
node.spaceApp.DomainId!,
378388
spaceName,
379-
node.spaceApp.SpaceSettingsSummary!.AppType!
389+
node.spaceApp.SpaceSettingsSummary!.AppType!,
390+
progress
380391
)
381392
await tryRemoteConnection(node, ctx, progress)
382393
}

packages/core/src/awsService/sagemaker/constants.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const InstanceTypeInsufficientMemoryMessage = (
3636
chosenInstanceType: string,
3737
recommendedInstanceType: string
3838
) => {
39-
return `Unable to create app for [${spaceName}] because instanceType [${chosenInstanceType}] is not supported for remote access enabled spaces. Use instanceType with at least 8 GiB memory. Would you like to start your space with instanceType [${recommendedInstanceType}]?`
39+
return `[${chosenInstanceType}] does not support remote access. Use an instanceType with at least 8 GiB memory. Would you like to start your space with instanceType [${recommendedInstanceType}]?`
4040
}
4141

4242
export const InstanceTypeNotSelectedMessage = (spaceName: string) => {
@@ -45,3 +45,11 @@ export const InstanceTypeNotSelectedMessage = (spaceName: string) => {
4545

4646
export const RemoteAccessRequiredMessage =
4747
'This space requires remote access to be enabled.\nWould you like to restart the space and connect?\nAny unsaved work will be lost.'
48+
49+
export const SmusDeeplinkSessionExpiredError = {
50+
title: 'Session Disconnected',
51+
message:
52+
'Your SageMaker Unified Studio session has been disconnected. Select a local (non-remote) VS Code window and use the SageMaker Unified Studio portal to connect again.',
53+
code: 'SMUS_SESSION_DISCONNECTED',
54+
shortMessage: 'Session disconnected, re-connect from SageMaker Unified Studio portal.',
55+
} as const

packages/core/src/awsService/sagemaker/credentialMapping.ts

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -90,41 +90,51 @@ export async function persistSmusProjectCreds(spaceArn: string, node: SagemakerU
9090
* @param session - SSM session ID.
9191
* @param wsUrl - SSM WebSocket URL.
9292
* @param token - Bearer token for the session.
93+
* @param appType - Application type (e.g., 'jupyterlab', 'codeeditor').
94+
* @param isSMUS - If true, skip refreshUrl construction (SMUS connections cannot refresh).
9395
*/
9496
export async function persistSSMConnection(
9597
spaceArn: string,
9698
domain: string,
9799
session?: string,
98100
wsUrl?: string,
99101
token?: string,
100-
appType?: string
102+
appType?: string,
103+
isSMUS?: boolean
101104
): Promise<void> {
102-
const { region } = parseArn(spaceArn)
103-
const endpoint = DevSettings.instance.get('endpoints', {})['sagemaker'] ?? ''
105+
let refreshUrl: string | undefined
104106

105-
let appSubDomain = 'jupyterlab'
106-
if (appType && appType.toLowerCase() === 'codeeditor') {
107-
appSubDomain = 'code-editor'
108-
}
107+
if (!isSMUS) {
108+
// Construct refreshUrl for SageMaker AI connections
109+
const { region } = parseArn(spaceArn)
110+
const endpoint = DevSettings.instance.get('endpoints', {})['sagemaker'] ?? ''
109111

110-
let envSubdomain: string
112+
let appSubDomain = 'jupyterlab'
113+
if (appType && appType.toLowerCase() === 'codeeditor') {
114+
appSubDomain = 'code-editor'
115+
}
111116

112-
if (endpoint.includes('beta')) {
113-
envSubdomain = 'devo'
114-
} else if (endpoint.includes('gamma')) {
115-
envSubdomain = 'loadtest'
116-
} else {
117-
envSubdomain = 'studio'
118-
}
117+
let envSubdomain: string
119118

120-
// Use the standard AWS domain for 'studio' (prod).
121-
// For non-prod environments, use the obfuscated domain 'asfiovnxocqpcry.com'.
122-
const baseDomain =
123-
envSubdomain === 'studio'
124-
? `studio.${region}.sagemaker.aws`
125-
: `${envSubdomain}.studio.${region}.asfiovnxocqpcry.com`
119+
if (endpoint.includes('beta')) {
120+
envSubdomain = 'devo'
121+
} else if (endpoint.includes('gamma')) {
122+
envSubdomain = 'loadtest'
123+
} else {
124+
envSubdomain = 'studio'
125+
}
126+
127+
// Use the standard AWS domain for 'studio' (prod).
128+
// For non-prod environments, use the obfuscated domain 'asfiovnxocqpcry.com'.
129+
const baseDomain =
130+
envSubdomain === 'studio'
131+
? `studio.${region}.sagemaker.aws`
132+
: `${envSubdomain}.studio.${region}.asfiovnxocqpcry.com`
133+
134+
refreshUrl = `https://studio-${domain}.${baseDomain}/${appSubDomain}`
135+
}
136+
// For SMUS connections, refreshUrl remains undefined
126137

127-
const refreshUrl = `https://studio-${domain}.${baseDomain}/${appSubDomain}`
128138
await setSpaceCredentials(spaceArn, refreshUrl, {
129139
sessionId: session ?? '-',
130140
url: wsUrl ?? '-',
@@ -179,12 +189,12 @@ export async function setSmusSpaceSsoProfile(spaceArn: string, projectId: string
179189
* Stores SSM connection information for a given space, typically from a deep link session.
180190
* This initializes the request as 'fresh' and includes a refresh URL if provided.
181191
* @param spaceArn - The arn of the SageMaker space.
182-
* @param refreshUrl - URL to use for refreshing session tokens.
192+
* @param refreshUrl - URL to use for refreshing session tokens (undefined for SMUS connections).
183193
* @param credentials - The session information used to initiate the connection.
184194
*/
185195
export async function setSpaceCredentials(
186196
spaceArn: string,
187-
refreshUrl: string,
197+
refreshUrl: string | undefined,
188198
credentials: SsmConnectionInfo
189199
): Promise<void> {
190200
const data = await loadMappings()

packages/core/src/awsService/sagemaker/detached-server/routes/getSessionAsync.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { IncomingMessage, ServerResponse } from 'http'
99
import url from 'url'
1010
import { SessionStore } from '../sessionStore'
1111
import { open, parseArn, readServerInfo } from '../utils'
12+
import { openErrorPage } from '../errorPage'
13+
import { SmusDeeplinkSessionExpiredError } from '../../constants'
1214

1315
export async function handleGetSessionAsync(req: IncomingMessage, res: ServerResponse): Promise<void> {
1416
const parsedUrl = url.parse(req.url || '', true)
@@ -46,8 +48,34 @@ export async function handleGetSessionAsync(req: IncomingMessage, res: ServerRes
4648
res.end()
4749
return
4850
} else if (status === 'not-started') {
49-
const serverInfo = await readServerInfo()
5051
const refreshUrl = await store.getRefreshUrl(connectionIdentifier)
52+
53+
// Check if this is a SMUS connection (no refreshUrl available)
54+
if (refreshUrl === undefined) {
55+
console.log(`SMUS session expired for connection: ${connectionIdentifier}`)
56+
57+
// Clean up the expired connection entry
58+
try {
59+
await store.cleanupExpiredConnection(connectionIdentifier)
60+
console.log(`Cleaned up expired connection: ${connectionIdentifier}`)
61+
} catch (cleanupErr) {
62+
console.error(`Failed to cleanup expired connection: ${cleanupErr}`)
63+
// Continue with error response even if cleanup fails
64+
}
65+
66+
await openErrorPage(SmusDeeplinkSessionExpiredError.title, SmusDeeplinkSessionExpiredError.message)
67+
res.writeHead(400, { 'Content-Type': 'application/json' })
68+
res.end(
69+
JSON.stringify({
70+
error: SmusDeeplinkSessionExpiredError.code,
71+
message: SmusDeeplinkSessionExpiredError.shortMessage,
72+
})
73+
)
74+
return
75+
}
76+
77+
// Continue with existing SageMaker AI refresh flow
78+
const serverInfo = await readServerInfo()
5179
const { spaceName } = parseArn(connectionIdentifier)
5280

5381
const url = `${refreshUrl}/${encodeURIComponent(spaceName)}?remote_access_token_refresh=true&reconnect_identifier=${encodeURIComponent(

packages/core/src/awsService/sagemaker/detached-server/sessionStore.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { readMapping, writeMapping } from './utils'
99
export type SessionStatus = 'pending' | 'fresh' | 'consumed' | 'not-started'
1010

1111
export class SessionStore {
12-
async getRefreshUrl(connectionId: string) {
12+
async getRefreshUrl(connectionId: string): Promise<string | undefined> {
1313
const mapping = await readMapping()
1414

1515
if (!mapping.deepLink) {
@@ -21,10 +21,6 @@ export class SessionStore {
2121
throw new Error(`No mapping found for connectionId: "${connectionId}"`)
2222
}
2323

24-
if (!entry.refreshUrl) {
25-
throw new Error(`No refreshUrl found for connectionId: "${connectionId}"`)
26-
}
27-
2824
return entry.refreshUrl
2925
}
3026

@@ -113,6 +109,20 @@ export class SessionStore {
113109
await writeMapping(mapping)
114110
}
115111

112+
async cleanupExpiredConnection(connectionId: string) {
113+
const mapping = await readMapping()
114+
115+
if (!mapping.deepLink) {
116+
throw new Error('No deepLink mapping found')
117+
}
118+
119+
// Remove the entire connection entry for the expired space
120+
if (mapping.deepLink[connectionId]) {
121+
delete mapping.deepLink[connectionId]
122+
await writeMapping(mapping)
123+
}
124+
}
125+
116126
async setSession(connectionId: string, requestId: string, ssmConnectionInfo: SsmConnectionInfo) {
117127
const mapping = await readMapping()
118128

0 commit comments

Comments
 (0)