Skip to content

Commit 14aa63c

Browse files
authored
Merge branch 'feature/stepfunctions-execution' into feature/stepfunctions-execution
2 parents f4a05fa + 8795c61 commit 14aa63c

File tree

6 files changed

+201
-27
lines changed

6 files changed

+201
-27
lines changed

packages/amazonq/src/app/inline/EditRendering/displayImage.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { AmazonQInlineCompletionItemProvider } from '../completion'
1717
import { vsCodeState } from 'aws-core-vscode/codewhisperer'
1818

1919
const autoRejectEditCursorDistance = 25
20+
const autoDiscardEditCursorDistance = 10
2021

2122
export class EditDecorationManager {
2223
private imageDecorationType: vscode.TextEditorDecorationType
@@ -312,6 +313,18 @@ export async function displaySvgDecoration(
312313
item: InlineCompletionItemWithReferences,
313314
inlineCompletionProvider?: AmazonQInlineCompletionItemProvider
314315
) {
316+
// Check if edit is too far from current cursor position
317+
const currentCursorLine = editor.selection.active.line
318+
if (Math.abs(startLine - currentCursorLine) >= autoDiscardEditCursorDistance) {
319+
// Emit DISCARD telemetry for edit suggestion that can't be shown because the suggestion is too far away
320+
const params = createDiscardTelemetryParams(session, item)
321+
languageClient.sendNotification('aws/logInlineCompletionSessionResults', params)
322+
getLogger('nextEditPrediction').debug(
323+
`Auto discarded edit suggestion for suggestion that is too far away: ${item.insertText as string}`
324+
)
325+
return
326+
}
327+
315328
const originalCode = editor.document.getText()
316329

317330
// Set edit state immediately to prevent race condition with completion requests
@@ -399,9 +412,6 @@ export async function displaySvgDecoration(
399412
const endPosition = getEndOfEditPosition(originalCode, newCode)
400413
editor.selection = new vscode.Selection(endPosition, endPosition)
401414

402-
// Move cursor to end of the actual changed content
403-
editor.selection = new vscode.Selection(endPosition, endPosition)
404-
405415
await decorationManager.clearDecorations(editor)
406416
documentChangeListener.dispose()
407417
cursorChangeListener.dispose()
@@ -420,19 +430,6 @@ export async function displaySvgDecoration(
420430
}
421431
languageClient.sendNotification('aws/logInlineCompletionSessionResults', params)
422432
session.triggerOnAcceptance = true
423-
// VS Code triggers suggestion on every keystroke, temporarily disable trigger on acceptance
424-
// if (inlineCompletionProvider && session.editsStreakPartialResultToken) {
425-
// await inlineCompletionProvider.provideInlineCompletionItems(
426-
// editor.document,
427-
// endPosition,
428-
// {
429-
// triggerKind: vscode.InlineCompletionTriggerKind.Automatic,
430-
// selectedCompletionInfo: undefined,
431-
// },
432-
// new vscode.CancellationTokenSource().token,
433-
// { emitTelemetry: false, showUi: false, editsStreakToken: session.editsStreakPartialResultToken }
434-
// )
435-
// }
436433
},
437434
async (isDiscard: boolean) => {
438435
// Handle reject

packages/amazonq/src/app/inline/completion.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ export class InlineCompletionManager implements Disposable {
213213

214214
export class AmazonQInlineCompletionItemProvider implements InlineCompletionItemProvider {
215215
private logger = getLogger()
216+
private pendingRequest: Promise<InlineCompletionItem[]> | undefined
217+
216218
constructor(
217219
private readonly languageClient: LanguageClient,
218220
private readonly recommendationService: RecommendationService,
@@ -300,6 +302,48 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
300302
options: JSON.stringify(getAllRecommendationsOptions),
301303
})
302304

305+
// If there's already a pending request, wait for it to complete instead of starting a new one
306+
// This prevents race conditions where multiple concurrent calls cause the later (empty) response
307+
// to override the earlier (valid) response
308+
if (this.pendingRequest) {
309+
getLogger().info('Reusing pending inline completion request to avoid race condition')
310+
try {
311+
const result = await this.pendingRequest
312+
// Check if THIS call's token was cancelled (not the original call's token)
313+
if (token.isCancellationRequested) {
314+
getLogger().info('Reused request completed but this call was cancelled')
315+
return []
316+
}
317+
return result
318+
} catch (e) {
319+
// If the pending request failed, continue with a new request
320+
getLogger().info('Pending request failed, starting new request: %O', e)
321+
}
322+
}
323+
324+
// Start a new request and track it
325+
this.pendingRequest = this._provideInlineCompletionItemsImpl(
326+
document,
327+
position,
328+
context,
329+
token,
330+
getAllRecommendationsOptions
331+
)
332+
333+
try {
334+
return await this.pendingRequest
335+
} finally {
336+
this.pendingRequest = undefined
337+
}
338+
}
339+
340+
private async _provideInlineCompletionItemsImpl(
341+
document: TextDocument,
342+
position: Position,
343+
context: InlineCompletionContext,
344+
token: CancellationToken,
345+
getAllRecommendationsOptions?: GetAllRecommendationsOptions
346+
): Promise<InlineCompletionItem[]> {
303347
if (vsCodeState.isCodeWhispererEditing) {
304348
getLogger().info('Q is editing, returning empty')
305349
return []

packages/amazonq/test/unit/app/inline/EditRendering/displayImage.test.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,89 @@ describe('EditDecorationManager', function () {
188188
})
189189
})
190190

191+
describe('displaySvgDecoration cursor distance auto-discard', function () {
192+
let sandbox: sinon.SinonSandbox
193+
let editorStub: sinon.SinonStubbedInstance<vscode.TextEditor>
194+
let languageClientStub: any
195+
let sessionStub: any
196+
let itemStub: any
197+
198+
beforeEach(function () {
199+
sandbox = sinon.createSandbox()
200+
const commonStubs = createCommonStubs(sandbox)
201+
editorStub = commonStubs.editorStub
202+
203+
languageClientStub = {
204+
sendNotification: sandbox.stub(),
205+
}
206+
207+
sessionStub = {
208+
sessionId: 'test-session',
209+
requestStartTime: Date.now(),
210+
firstCompletionDisplayLatency: 100,
211+
}
212+
213+
itemStub = {
214+
itemId: 'test-item',
215+
insertText: 'test content',
216+
}
217+
})
218+
219+
afterEach(function () {
220+
sandbox.restore()
221+
})
222+
223+
it('should send discard telemetry and return early when edit is 10+ lines away from cursor', async function () {
224+
// Set cursor at line 5
225+
editorStub.selection = {
226+
active: new vscode.Position(5, 0),
227+
} as any
228+
// Try to display edit at line 20 (15 lines away)
229+
await displaySvgDecoration(
230+
editorStub as unknown as vscode.TextEditor,
231+
vscode.Uri.parse('data:image/svg+xml;base64,test'),
232+
20,
233+
'new code',
234+
[],
235+
sessionStub,
236+
languageClientStub,
237+
itemStub
238+
)
239+
240+
// Verify discard telemetry was sent
241+
sinon.assert.calledOnce(languageClientStub.sendNotification)
242+
const call = languageClientStub.sendNotification.getCall(0)
243+
assert.strictEqual(call.args[0], 'aws/logInlineCompletionSessionResults')
244+
assert.strictEqual(call.args[1].sessionId, 'test-session')
245+
assert.strictEqual(call.args[1].completionSessionResult['test-item'].discarded, true)
246+
})
247+
248+
it('should proceed normally when edit is within 10 lines of cursor', async function () {
249+
// Set cursor at line 5
250+
editorStub.selection = {
251+
active: new vscode.Position(5, 0),
252+
} as any
253+
// Mock required dependencies for normal flow
254+
sandbox.stub(vscode.workspace, 'onDidChangeTextDocument').returns({ dispose: sandbox.stub() })
255+
sandbox.stub(vscode.window, 'onDidChangeTextEditorSelection').returns({ dispose: sandbox.stub() })
256+
257+
// Try to display edit at line 10 (5 lines away)
258+
await displaySvgDecoration(
259+
editorStub as unknown as vscode.TextEditor,
260+
vscode.Uri.parse('data:image/svg+xml;base64,test'),
261+
10,
262+
'new code',
263+
[],
264+
sessionStub,
265+
languageClientStub,
266+
itemStub
267+
)
268+
269+
// Verify no discard telemetry was sent (function should proceed normally)
270+
sinon.assert.notCalled(languageClientStub.sendNotification)
271+
})
272+
})
273+
191274
describe('displaySvgDecoration cursor distance auto-reject', function () {
192275
let sandbox: sinon.SinonSandbox
193276
let editorStub: sinon.SinonStubbedInstance<vscode.TextEditor>
@@ -253,6 +336,10 @@ describe('displaySvgDecoration cursor distance auto-reject', function () {
253336
})
254337

255338
it('should not reject when cursor moves less than 25 lines away', async function () {
339+
// Set cursor at line 50
340+
editorStub.selection = {
341+
active: new vscode.Position(50, 0),
342+
} as any
256343
const startLine = 50
257344
await setupDisplaySvgDecoration(startLine)
258345

@@ -262,6 +349,10 @@ describe('displaySvgDecoration cursor distance auto-reject', function () {
262349
})
263350

264351
it('should not reject when cursor moves exactly 25 lines away', async function () {
352+
// Set cursor at line 50
353+
editorStub.selection = {
354+
active: new vscode.Position(50, 0),
355+
} as any
265356
const startLine = 50
266357
await setupDisplaySvgDecoration(startLine)
267358

@@ -271,6 +362,10 @@ describe('displaySvgDecoration cursor distance auto-reject', function () {
271362
})
272363

273364
it('should reject when cursor moves more than 25 lines away', async function () {
365+
// Set cursor at line 50
366+
editorStub.selection = {
367+
active: new vscode.Position(50, 0),
368+
} as any
274369
const startLine = 50
275370
await setupDisplaySvgDecoration(startLine)
276371

@@ -280,6 +375,10 @@ describe('displaySvgDecoration cursor distance auto-reject', function () {
280375
})
281376

282377
it('should reject when cursor moves more than 25 lines before the edit', async function () {
378+
// Set cursor at line 50
379+
editorStub.selection = {
380+
active: new vscode.Position(50, 0),
381+
} as any
283382
const startLine = 50
284383
await setupDisplaySvgDecoration(startLine)
285384

@@ -289,6 +388,10 @@ describe('displaySvgDecoration cursor distance auto-reject', function () {
289388
})
290389

291390
it('should not reject when edit is near beginning of file and cursor cannot move far enough', async function () {
391+
// Set cursor at line 10
392+
editorStub.selection = {
393+
active: new vscode.Position(10, 0),
394+
} as any
292395
const startLine = 10
293396
await setupDisplaySvgDecoration(startLine)
294397

@@ -298,6 +401,10 @@ describe('displaySvgDecoration cursor distance auto-reject', function () {
298401
})
299402

300403
it('should not reject when edit suggestion is not active', async function () {
404+
// Set cursor at line 50
405+
editorStub.selection = {
406+
active: new vscode.Position(50, 0),
407+
} as any
301408
editSuggestionStateStub.returns(false)
302409

303410
const startLine = 50

packages/core/src/shared/clients/sagemaker.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ import { yes, no, continueText, cancel } from '../localizedText'
5252
import { AwsCredentialIdentity } from '@aws-sdk/types'
5353
import globals from '../extensionGlobals'
5454

55+
const appTypeSettingsMap: Record<string, string> = {
56+
[AppType.JupyterLab as string]: 'JupyterLabAppSettings',
57+
[AppType.CodeEditor as string]: 'CodeEditorAppSettings',
58+
} as const
59+
5560
export interface SagemakerSpaceApp extends SpaceDetails {
5661
App?: AppDetails
5762
DomainSpaceKey: string
@@ -136,13 +141,13 @@ export class SagemakerClient extends ClientWrapper<SageMakerClient> {
136141

137142
// Get app type
138143
const appType = spaceDetails.SpaceSettings?.AppType
139-
if (appType !== 'JupyterLab' && appType !== 'CodeEditor') {
144+
if (!appType || !(appType in appTypeSettingsMap)) {
140145
throw new ToolkitError(`Unsupported AppType "${appType}" for space "${spaceName}"`)
141146
}
142147

143148
// Get app resource spec
144149
const requestedResourceSpec =
145-
appType === 'JupyterLab'
150+
appType === AppType.JupyterLab
146151
? spaceDetails.SpaceSettings?.JupyterLabAppSettings?.DefaultResourceSpec
147152
: spaceDetails.SpaceSettings?.CodeEditorAppSettings?.DefaultResourceSpec
148153

@@ -181,16 +186,30 @@ export class SagemakerClient extends ClientWrapper<SageMakerClient> {
181186
instanceType = InstanceTypeMinimum
182187
}
183188

184-
// Get remote access flag
185-
if (!spaceDetails.SpaceSettings?.RemoteAccess || spaceDetails.SpaceSettings?.RemoteAccess === 'DISABLED') {
189+
// First, update the space if needed
190+
const needsRemoteAccess =
191+
!spaceDetails.SpaceSettings?.RemoteAccess || spaceDetails.SpaceSettings?.RemoteAccess === 'DISABLED'
192+
const instanceTypeChanged = requestedResourceSpec?.InstanceType !== instanceType
193+
194+
if (needsRemoteAccess || instanceTypeChanged) {
195+
const updateSpaceRequest: UpdateSpaceCommandInput = {
196+
DomainId: domainId,
197+
SpaceName: spaceName,
198+
SpaceSettings: {
199+
...(needsRemoteAccess && { RemoteAccess: 'ENABLED' }),
200+
...(instanceTypeChanged && {
201+
[appTypeSettingsMap[appType]]: {
202+
DefaultResourceSpec: {
203+
InstanceType: instanceType,
204+
},
205+
},
206+
}),
207+
},
208+
}
209+
186210
try {
187-
await this.updateSpace({
188-
DomainId: domainId,
189-
SpaceName: spaceName,
190-
SpaceSettings: {
191-
RemoteAccess: 'ENABLED',
192-
},
193-
})
211+
getLogger().debug('SagemakerClient: Updating space: domainId=%s, spaceName=%s', domainId, spaceName)
212+
await this.updateSpace(updateSpaceRequest)
194213
await this.waitForSpaceInService(spaceName, domainId)
195214
} catch (err) {
196215
throw this.handleStartSpaceError(err)
@@ -214,6 +233,7 @@ export class SagemakerClient extends ClientWrapper<SageMakerClient> {
214233
? { ...resourceSpec, EnvironmentArn: undefined, EnvironmentVersionArn: undefined }
215234
: resourceSpec
216235

236+
// Second, create the App
217237
const createAppRequest: CreateAppCommandInput = {
218238
DomainId: domainId,
219239
SpaceName: spaceName,
@@ -223,6 +243,7 @@ export class SagemakerClient extends ClientWrapper<SageMakerClient> {
223243
}
224244

225245
try {
246+
getLogger().debug('SagemakerClient: Creating app: domainId=%s, spaceName=%s', domainId, spaceName)
226247
await this.createApp(createAppRequest)
227248
} catch (err) {
228249
throw this.handleStartSpaceError(err)

packages/core/src/test/shared/clients/sagemakerClient.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ describe('SagemakerClient.startSpace', function () {
360360
getTestWindow().getFirstMessage().selectItem('Yes')
361361

362362
await promise
363+
sinon.assert.calledOnce(updateSpaceStub)
363364
sinon.assert.calledOnce(createAppStub)
364365
})
365366

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "The space is updated upon creation of a new app with the requested settings"
4+
}

0 commit comments

Comments
 (0)