diff --git a/utils/index.d.ts b/utils/index.d.ts index b4357affb4..cea1ca2e8b 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) @@ -1413,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; @@ -2383,6 +2395,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 { + const metadata = { __injectedContext: additionalContext }; + return vscode.commands.executeCommand(commandId, ...args, metadata); +} 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..98688d08af 100644 --- a/utils/src/registerCommand.ts +++ b/utils/src/registerCommand.ts @@ -42,7 +42,18 @@ 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) { + 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 { await setTelemetryProperties(context, args); } catch (e: unknown) { @@ -52,8 +63,8 @@ export function registerCommand(commandId: string, callback: (context: types.IAc context.telemetry.properties.telemetryError = error.message; } } - - return callback(context, ...args); + const finalContext = injectedContext ? { ...context, ...injectedContext } : context; + return callback(finalContext, ...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 + })); + } }