Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/askUserQuestionHandler.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { ChatParticipantToolToken, commands, LanguageModelTextPart } from 'vscode';6import { ILogService } from '../../../../platform/log/common/logService';7import { CancellationToken } from '../../../../util/vs/base/common/cancellation';8import { ToolName } from '../../../tools/common/toolNames';9import { IToolsService } from '../../../tools/common/toolsService';10import { IQuestion, IQuestionAnswer, IUserQuestionHandler, UserInputResponse } from '../../copilotcli/node/userInputHelpers';111213export interface IAskQuestionsParams {14readonly questions: IQuestion[];15}1617export interface IAnswerResult {18readonly answers: Record<string, IQuestionAnswer>;19}2021const NotifyQuestionCarouselAnswerCommandId = '_chat.notifyQuestionCarouselAnswer';2223function toCarouselAnswerValue(question: IQuestion, response: UserInputResponse): string | { selectedValue?: string; freeformValue?: string } | { selectedValues: string[]; freeformValue?: string } | undefined {24if (!response.answer) {25return undefined;26}2728if (!question.options || question.options.length === 0) {29return response.answer;30}3132if (question.multiSelect) {33const selectedValues = question.options.some(option => option.label === response.answer)34? [response.answer]35: response.answer.split(',').map(value => value.trim()).filter(Boolean);36return response.wasFreeform37? { selectedValues, freeformValue: response.answer }38: { selectedValues };39}4041return response.wasFreeform42? { freeformValue: response.answer }43: { selectedValue: response.answer };44}4546export class UserQuestionHandler implements IUserQuestionHandler {47declare _serviceBrand: undefined;48constructor(49@ILogService protected readonly _logService: ILogService,50@IToolsService private readonly _toolsService: IToolsService,51) {52}53async askUserQuestion(question: IQuestion, toolInvocationToken: ChatParticipantToolToken, token: CancellationToken, toolCallId?: string): Promise<IQuestionAnswer | undefined> {54const input: IAskQuestionsParams = { questions: [question] };55const result = await this._toolsService.invokeTool(ToolName.CoreAskQuestions, {56input,57toolInvocationToken,58chatStreamToolCallId: toolCallId,59}, token);606162// Parse the result63const firstPart = result?.content.at(0);64if (!(firstPart instanceof LanguageModelTextPart) || !firstPart.value) {65return undefined;66}6768const carouselAnswers = JSON.parse(firstPart.value) as IAnswerResult;6970// Log all available keys in carouselAnswers for debugging71this._logService.trace(`[AskQuestionsTool] Question & answers ${question.question}, Answers object: ${JSON.stringify(carouselAnswers)}`);7273const answer = carouselAnswers.answers[question.question] ?? carouselAnswers.answers[question.header];74if (answer === undefined) {75return undefined;76} else if (answer.freeText) {77return answer;78} else if (answer.selected.length) {79return answer;80}81return undefined;82}8384async notifyQuestionCarouselAnswer(toolCallId: string, question: IQuestion, response: UserInputResponse): Promise<void> {85const answerValue = toCarouselAnswerValue(question, response);86await commands.executeCommand(NotifyQuestionCarouselAnswerCommandId, toolCallId, answerValue === undefined ? undefined : {87[`${toolCallId}:0`]: answerValue,88});89}90}919293