From ec2baf5617c9179a23d469eddb5090b74a8e7d1d Mon Sep 17 00:00:00 2001 From: Megan Mott Date: Tue, 2 Dec 2025 14:53:22 -0800 Subject: [PATCH 1/2] add re run changes --- utils/index.d.ts | 15 +++++++++ utils/src/copilot/copilot.ts | 33 +++++++++++++++---- utils/src/executeCommandWithAddedContext.ts | 12 +++++++ utils/src/index.ts | 2 ++ .../src/pickTreeItem/runGenericPromptStep.ts | 17 ++++++++++ utils/src/registerCommand.ts | 10 +++++- utils/src/userInput/CopilotUserInput.ts | 21 +++++++++--- 7 files changed, 99 insertions(+), 11 deletions(-) create mode 100644 utils/src/executeCommandWithAddedContext.ts create mode 100644 utils/src/pickTreeItem/runGenericPromptStep.ts diff --git a/utils/index.d.ts b/utils/index.d.ts index b4357affb4..26be426f3c 100644 --- a/utils/index.d.ts +++ b/utils/index.d.ts @@ -707,6 +707,14 @@ export declare function callWithTelemetryAndErrorHandlingSync(callbackId: str */ export declare function callWithMaskHandling(callback: () => Promise, valueToMask: string): Promise; +/** + * A wrapper for the VS Code executeCommand command + * Used to pass in additional context when executing a command + * @param commandId Identifier of the command to execute + * @param additionalContext Full context including addtional properties + * @param args Parameters passed to the command function + */ +export declare function executeCommandWithAddedContext(commandId: string, additionalContext: Partial, ...args: unknown[]): Thenable; /** * Add an extension-wide value to mask for all commands * This will apply to telemetry and "Report Issue", but _not_ VS Code UI (i.e. the error notification or output channel) @@ -2383,6 +2391,13 @@ export declare class QuickPickAzureResourceStep extends GenericQuickPickStep(context: PickExperienceContext, wizardOptions?: IWizardOptions, startingNode?: unknown): Promise; //#endregion +/** + * Creates and runs a generic prompt step. Use runQuickPickWizard to run quick pick steps + * @param context The action context + * @param wizardOptions The options used to construct the wizard + */ +export declare function runGenericPromptStep(context: PickExperienceContext, wizardOptions: IWizardOptions): Promise; + /** * Registers a namespace for common random utility functions */ diff --git a/utils/src/copilot/copilot.ts b/utils/src/copilot/copilot.ts index 4f15967260..e6a6118928 100644 --- a/utils/src/copilot/copilot.ts +++ b/utils/src/copilot/copilot.ts @@ -6,9 +6,9 @@ import * as vscode from "vscode"; import { InvalidCopilotResponseError } from "../errors"; const languageModelPreference: { vendor: "copilot", family: string }[] = [ - // not yet seen/available - // { vendor: "copilot", family: "gpt-4-turbo-preview" }, - // seen/available + { vendor: "copilot", family: "gpt-5-mini" }, + { vendor: "copilot", family: "claude-sonnet-4" }, + { vendor: "copilot", family: "claude-sonnet-4.5" }, { vendor: "copilot", family: "gpt-4o" }, { vendor: "copilot", family: "gpt-4-turbo" }, { vendor: "copilot", family: "gpt-4" }, @@ -23,12 +23,21 @@ async function selectMostPreferredLm(): Promise(commandId: string, additionalContext: Partial, ...args: unknown[]): Thenable { + // Special metadata arg signaling context injection + const metadata = { injectedContext: additionalContext }; + return vscode.commands.executeCommand(commandId, metadata, ...args); +} diff --git a/utils/src/index.ts b/utils/src/index.ts index 249f0611c6..983a7cb739 100644 --- a/utils/src/index.ts +++ b/utils/src/index.ts @@ -16,6 +16,7 @@ export * from './dev/TestOutputChannel'; export * from './dev/TestUserInput'; export * from './DialogResponses'; export * from './errors'; +export * from './executeCommandWithAddedContext'; export * from './extensionUserAgent'; export { registerUIExtensionVariables } from './extensionVariables'; export { addExtensionValueToMask, callWithMaskHandling, maskUserInfo, maskValue } from './masking'; @@ -31,6 +32,7 @@ export * from './pickTreeItem/GenericQuickPickStep'; export * from './pickTreeItem/quickPickAzureResource/QuickPickAzureResourceStep'; export * from './pickTreeItem/quickPickAzureResource/QuickPickAzureSubscriptionStep'; export * from './pickTreeItem/quickPickAzureResource/QuickPickGroupStep'; +export * from './pickTreeItem/runGenericPromptStep'; export * from './pickTreeItem/runQuickPickWizard'; export * from './registerCommand'; export * from './registerCommandWithTreeNodeUnwrapping'; diff --git a/utils/src/pickTreeItem/runGenericPromptStep.ts b/utils/src/pickTreeItem/runGenericPromptStep.ts new file mode 100644 index 0000000000..890bed7768 --- /dev/null +++ b/utils/src/pickTreeItem/runGenericPromptStep.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.md in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import * as types from '../../index'; +import { AzureWizard } from '../wizard/AzureWizard'; + +export async function runGenericPromptStep(context: types.PickExperienceContext, wizardOptions?: types.IWizardOptions): Promise { + const wizard = new AzureWizard(context, { + hideStepCount: true, + showLoadingPrompt: wizardOptions?.showLoadingPrompt ?? true, + ...wizardOptions + }); + + await wizard.prompt(); +} diff --git a/utils/src/registerCommand.ts b/utils/src/registerCommand.ts index 3f88713dab..bd9c6be174 100644 --- a/utils/src/registerCommand.ts +++ b/utils/src/registerCommand.ts @@ -42,7 +42,13 @@ export function registerCommand(commandId: string, callback: (context: types.IAc return await callWithTelemetryAndErrorHandling( telemetryId || commandId, async (context: types.IActionContext) => { + let injectedContext: Partial | undefined; if (args.length > 0) { + if (args[0] && typeof args[0] === "object" && "injectedContext" in args[0]) { + injectedContext = args[0].injectedContext as types.IActionContext + args.shift(); + } + try { await setTelemetryProperties(context, args); } catch (e: unknown) { @@ -52,7 +58,9 @@ export function registerCommand(commandId: string, callback: (context: types.IAc context.telemetry.properties.telemetryError = error.message; } } - + if (injectedContext) { + return callback({ ...context, ...injectedContext }, ...args); + } return callback(context, ...args); } ); diff --git a/utils/src/userInput/CopilotUserInput.ts b/utils/src/userInput/CopilotUserInput.ts index 432125c6a8..bf1dc6e291 100644 --- a/utils/src/userInput/CopilotUserInput.ts +++ b/utils/src/userInput/CopilotUserInput.ts @@ -91,14 +91,18 @@ export class CopilotUserInput implements types.IAzureUserInput { public async showQuickPick>(items: T[] | Thenable, options: vscodeTypes.QuickPickOptions): Promise { let primaryPrompt: string; const resolvedItems: T[] = await Promise.resolve(items); - const jsonItems: string[] = resolvedItems.map(item => JSON.stringify(item)); + + // Clean up items to only include label and description + const cleanedItems = this.cleanQuickPickItems(resolvedItems); + const jsonItems = cleanedItems.map(item => JSON.stringify(item)); try { if (options.canPickMany) { primaryPrompt = createPrimaryPromptToGetPickManyQuickPickInput(jsonItems, this._relevantContext); const response = await doCopilotInteraction(primaryPrompt); const jsonResponse: T[] = JSON.parse(response) as T[]; const picks = resolvedItems.filter(item => { - return jsonResponse.some(resp => JSON.stringify(resp) === JSON.stringify(item)); + return jsonResponse.some(resp => JSON.stringify(resp.label) === JSON.stringify(item.label) && + JSON.stringify(resp.description || '') === JSON.stringify(item.description || '')); }); if (!picks || picks.length === 0) { @@ -109,11 +113,12 @@ export class CopilotUserInput implements types.IAzureUserInput { return picks; } else { - primaryPrompt = createPrimaryPromptToGetSingleQuickPickInput(jsonItems, this._relevantContext); + primaryPrompt = createPrimaryPromptToGetSingleQuickPickInput(jsonItems, options.placeHolder, this._relevantContext); const response = await doCopilotInteraction(primaryPrompt); const jsonResponse: T = JSON.parse(response) as T; const pick = resolvedItems.find(item => { - return JSON.stringify(item) === JSON.stringify(jsonResponse); + return JSON.stringify(item.label) === JSON.stringify(jsonResponse.label) && + JSON.stringify(item.description || '') === JSON.stringify(jsonResponse.description || ''); }); if (!pick) { @@ -128,4 +133,12 @@ export class CopilotUserInput implements types.IAzureUserInput { throw new InvalidCopilotResponseError(); } } + + private cleanQuickPickItems>(items: T[]): { label: string, description: string, id: string | undefined }[] { + return items.map(item => ({ + label: item.label, + description: item.description || '', + id: (item.data && typeof item.data === 'object' && 'id' in item.data) ? String(item.data.id) : undefined + })); + } } From 61c73440da0deab7497b574a949705d4dee4f3f5 Mon Sep 17 00:00:00 2001 From: Megan Mott Date: Wed, 10 Dec 2025 15:03:49 -0800 Subject: [PATCH 2/2] add subscription to ActivityAttributes --- utils/index.d.ts | 4 ++++ utils/src/executeCommandWithAddedContext.ts | 5 ++--- utils/src/registerCommand.ts | 17 ++++++++++------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/utils/index.d.ts b/utils/index.d.ts index 26be426f3c..cea1ca2e8b 100644 --- a/utils/index.d.ts +++ b/utils/index.d.ts @@ -1421,6 +1421,10 @@ export interface ActivityAttributes { * Any Azure resource envelope related to the command or activity being run */ azureResource?: unknown; + /** + * Optional Azure subscription to be added + */ + subscription?: AzureSubscription; // For additional one-off properties that could be useful for Copilot [key: string]: unknown; diff --git a/utils/src/executeCommandWithAddedContext.ts b/utils/src/executeCommandWithAddedContext.ts index 480b375419..ad02512097 100644 --- a/utils/src/executeCommandWithAddedContext.ts +++ b/utils/src/executeCommandWithAddedContext.ts @@ -6,7 +6,6 @@ import * as vscode from "vscode"; import * as types from '../index'; export function executeCommandWithAddedContext(commandId: string, additionalContext: Partial, ...args: unknown[]): Thenable { - // Special metadata arg signaling context injection - const metadata = { injectedContext: additionalContext }; - return vscode.commands.executeCommand(commandId, metadata, ...args); + const metadata = { __injectedContext: additionalContext }; + return vscode.commands.executeCommand(commandId, ...args, metadata); } diff --git a/utils/src/registerCommand.ts b/utils/src/registerCommand.ts index bd9c6be174..98688d08af 100644 --- a/utils/src/registerCommand.ts +++ b/utils/src/registerCommand.ts @@ -44,9 +44,14 @@ export function registerCommand(commandId: string, callback: (context: types.IAc async (context: types.IActionContext) => { let injectedContext: Partial | undefined; if (args.length > 0) { - if (args[0] && typeof args[0] === "object" && "injectedContext" in args[0]) { - injectedContext = args[0].injectedContext as types.IActionContext - args.shift(); + const metadata = args[args.length - 1]; + + // Look for our metadata object at the end + if (metadata && typeof metadata === "object" && "__injectedContext" in metadata) { + injectedContext = metadata.__injectedContext as types.IActionContext; + + // remove only the metadata + args.pop(); } try { @@ -58,10 +63,8 @@ export function registerCommand(commandId: string, callback: (context: types.IAc context.telemetry.properties.telemetryError = error.message; } } - if (injectedContext) { - return callback({ ...context, ...injectedContext }, ...args); - } - return callback(context, ...args); + const finalContext = injectedContext ? { ...context, ...injectedContext } : context; + return callback(finalContext, ...args); } ); }));