askQuestionsTool.ts ×14

Frontier kind: Code frontier

unlabeled · c_fafc6290eb02

15 tests · 27181 LOC · 140 files · introduces 0 tests · 237 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
14 ranges237 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
2762 ranges27181 lines · 140 files · Browse complete extent
All tests (intent)
15 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 237 introduced LOC across 14 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/contrib/chat/common/tools/builtinTools/askQuestionsTool.ts 237 introduced LOC · 14 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- askQuestionsTool.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CancellationToken } from '../../../../../../base/common/cancellation.js';
7 > import { CancellationError } from '../../../../../../base/common/errors.js';
8 > import { MarkdownString } from '../../../../../../base/common/htmlContent.js';
9 > import { IJSONSchema, IJSONSchemaMap } from '../../../../../../base/common/jsonSchema.js';
10 > import { Disposable } from '../../../../../../base/common/lifecycle.js';
11 > import { hasKey } from '../../../../../../base/common/types.js';
12 > import { generateUuid } from '../../../../../../base/common/uuid.js';
13 > import { localize } from '../../../../../../nls.js';
14 > import { IChatQuestion, IChatQuestionAnswers, IChatQuestionAnswerValue, IChatMultiSelectAnswer, IChatService, IChatSingleSelectAnswer, IChatToolInvocation } from '../../chatService/chatService.js';
15 > import { ChatQuestionCarouselData } from '../../model/chatProgressTypes/chatQuestionCarouselData.js';
16 > import { IChatRequestModel } from '../../model/chatModel.js';
17 > import { ChatConfiguration, ChatPermissionLevel } from '../../constants.js';
18 > import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
19 > import { StopWatch } from '../../../../../../base/common/stopwatch.js';
20 > import { ILogService } from '../../../../../../platform/log/common/log.js';
21 > import { ITelemetryService } from '../../../../../../platform/telemetry/common/telemetry.js';
22 > import { CountTokensCallback, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolInvocationPreparationContext, IToolResult, ToolDataSource, ToolProgress } from '../languageModelToolsService.js';
23 > import { ThemeIcon } from '../../../../../../base/common/themables.js';
24 > import { Codicon } from '../../../../../../base/common/codicons.js';
25 > import { raceCancellation } from '../../../../../../base/common/async.js';
26 > import { URI } from '../../../../../../base/common/uri.js';
27 > import { TerminalToolId } from '../terminalToolIds.js';
28 >
29 > /**
30 > * Response returned to the model when the user is not available (autopilot mode).
31 > */
32 > export const AUTOPILOT_ASK_USER_RESPONSE =
33 > 'The user is not available to respond and will review your work later. Work autonomously and make good decisions.';
34 >
35 > // Use a distinct id to avoid clashing with extension-provided tools
36 > export const AskQuestionsToolId = 'vscode_askQuestions';
37 >
38 > // Soft limits are used in the schema to guide the model
39 > // Hard limits are more lenient and used to truncate if the model overshoots
40 > //
41 > // Example text at each limit:
42 > // - header soft (50 chars): "Which database engine do you want to use for this?"
43 > // - header hard (75 chars): "Which database engine and connection pooling strategy do you want to use here?"
44 > // - question soft (200 chars): "What testing framework would you like to use for this project? Consider factors like your team's familiarity, community support, and integration with your existing CI/CD pipeline when making a choice."
45 > const SoftLimits = {
46 > header: 50,
47 > question: 200
48 > } as const;
49 >
50 > const HardLimits = {
51 > header: 75,
52 > } as const;
53 >
54 function truncateToLimit(value: string | undefined, limit: number): string | undefined {
55 if (value === undefined) {
61 return value;
62 }
64 > export interface IQuestionOption {
65 > readonly label: string;
66 > readonly description?: string;
67 > readonly recommended?: boolean;
68 > }
69 >
70 > export interface IQuestion {
71 > readonly header: string;
72 > readonly question: string;
73 > readonly message?: string;
74 > readonly multiSelect?: boolean;
75 > readonly options?: IQuestionOption[];
76 > readonly allowFreeformInput?: boolean;
77 > }
78 >
79 > export interface IAskQuestionsParams {
80 > readonly questions: IQuestion[];
81 > }
82 >
83 > export interface IQuestionAnswer {
84 > readonly selected: string[];
85 > readonly freeText: string | null;
86 > readonly skipped: boolean;
87 > }
88 >
89 > export interface IAnswerResult {
90 > readonly answers: Record<string, IQuestionAnswer>;
91 > }
92 >
93 > export function createAskQuestionsToolData(): IToolData {
94 > const questionSchema: IJSONSchema & { properties: IJSONSchemaMap } = {
95 > type: 'object',
96 > properties: {
97 > header: {
98 > type: 'string',
99 > description: `Short identifier for the question. Must be unique so answers can be mapped back to the question. Maximum ${SoftLimits.header} characters.`,
100 > maxLength: SoftLimits.header
101 > },
102 > question: {
103 > type: 'string',
104 > description: `The question text to display to the user. Keep it concise, ideally one sentence. Maximum ${SoftLimits.question} characters.`,
105 > maxLength: SoftLimits.question
106 > },
107 > multiSelect: {
108 > type: 'boolean',
109 > description: 'Allow selecting multiple options when options are provided.'
110 > },
111 > allowFreeformInput: {
112 > type: 'boolean',
113 > description: 'Allow freeform text answers in addition to option selection. Defaults to true; set to false to restrict to predefined options only.'
114 > },
115 > message: {
116 > type: 'string',
117 > description: 'Optional markdown message to display below the question text, providing additional context or details.'
118 > },
119 > options: {
120 > type: 'array',
121 > description: 'Optional list of selectable answers. If omitted, the question is free text.',
122 > items: {
123 > type: 'object',
124 > properties: {
125 > label: {
126 > type: 'string',
127 > description: 'Display label and value for the option.'
128 > },
129 > description: {
130 > type: 'string',
131 > description: 'Optional secondary text shown with the option.'
132 > },
133 > recommended: {
134 > type: 'boolean',
135 > description: 'Mark this option as the recommended default.'
136 > }
137 > },
138 > required: ['label']
139 > }
140 > }
141 > },
142 > required: ['header', 'question']
143 > };
144 >
145 > const inputSchema: IJSONSchema & { properties: IJSONSchemaMap } = {
146 > type: 'object',
147 > properties: {
148 > questions: {
149 > type: 'array',
150 > description: 'List of questions to ask the user. Order is preserved.',
151 > items: questionSchema,
152 > minItems: 1
153 > }
154 > },
155 > required: ['questions']
156 > };
157 >
158 > return {
159 > id: AskQuestionsToolId,
160 > toolReferenceName: 'askQuestions',
161 > legacyToolReferenceFullNames: [AskQuestionsToolId, 'vscode/askQuestions'],
162 > canBeReferencedInPrompt: false,
163 > icon: ThemeIcon.fromId(Codicon.question.id),
164 > displayName: localize('tool.askQuestions.displayName', 'Ask Clarifying Questions'),
165 > userDescription: localize('tool.askQuestions.userDescription', 'Ask structured clarifying questions using single select, multi-select, or freeform inputs to collect task requirements before proceeding.'),
166 > modelDescription: 'Use this tool to ask the user a small number of clarifying questions before proceeding. Provide the questions array with concise headers and prompts. Use options for fixed choices, set multiSelect when multiple selections are allowed. Users can always provide a freeform text answer alongside options unless you set allowFreeformInput to false.',
167 > source: ToolDataSource.Internal,
168 > inputSchema
169 > };
170 > }
171 >
172 > export const AskQuestionsToolData: IToolData = createAskQuestionsToolData();
173 >
174 > export class AskQuestionsTool extends Disposable implements IToolImpl {
175 >
176 > constructor(
177 > @IChatService private readonly chatService: IChatService,
178 > @ITelemetryService private readonly telemetryService: ITelemetryService,
179 > @ILogService private readonly logService: ILogService,
180 > @IConfigurationService private readonly configService: IConfigurationService,
181 > ) {
182 > super();
183 > }
184 >
185 > async invoke(invocation: IToolInvocation, _countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise<IToolResult> {
186 const stopWatch = StopWatch.create(true);
187 const parameters = invocation.parameters as IAskQuestionsParams;
272 };
273 }
275 > async prepareToolInvocation(context: IToolInvocationPreparationContext, _token: CancellationToken): Promise<IPreparedToolInvocation | undefined> {
276 const parameters = context.parameters as IAskQuestionsParams;
277 const { questions } = parameters;
301 };
302 }
304 > private getRequest(chatSessionResource: URI | undefined, chatRequestId: string | undefined): { request: IChatRequestModel | undefined; sessionResource: URI | undefined } {
305 if (!chatSessionResource) {
306 return { request: undefined, sessionResource: undefined };
326 return { request, sessionResource: chatSessionResource };
327 }
329 > /**
330 > * Resolves the terminal execution ID for the request.
331 > * Prefer structured metadata and fall back to legacy message parsing for
332 > * old sessions that may not carry the metadata yet.
333 > * As a final fallback, search completed runInTerminal tool invocations in
334 > * the response for the terminal ID, but only when the tool output indicates
335 > * the terminal is still running and waiting for input (foreground/timeout
336 > * path where the model calls ask_questions from the same turn as
337 > * runInTerminal).
338 > */
339 > private extractTerminalId(request: IChatRequestModel): string | undefined {
340 if (request.terminalExecutionId) {
341 return request.terminalExecutionId;
375 return undefined;
376 }
378 > private toQuestionCarousel(questions: IQuestion[], resolveId?: string): { carousel: ChatQuestionCarouselData; idToHeaderMap: Map<string, string> } {
379 const idToHeaderMap = new Map<string, string>();
380 const carouselResolveId = resolveId ?? generateUuid();
385 };
386 }
388 > private toChatQuestion(question: IQuestion, idToHeaderMap: Map<string, string>, resolveId: string, index: number): IChatQuestion {
389 let type: IChatQuestion['type'];
390 if (!question.options || question.options.length === 0) {
427 };
428 }
430 > protected convertCarouselAnswers(questions: IQuestion[], carouselAnswers: IChatQuestionAnswers | undefined, idToHeaderMap: Map<string, string>): IAnswerResult {
431 const result: IAnswerResult = { answers: {} };
432
531 return result;
532 }
534 > private collectMetrics(questions: IQuestion[], result: IAnswerResult): { answeredCount: number; skippedCount: number; freeTextCount: number; recommendedAvailableCount: number; recommendedSelectedCount: number } {
535 const answers = Object.values(result.answers);
536 const answeredCount = answers.filter(a => !a.skipped).length;
545 return { answeredCount, skippedCount, freeTextCount, recommendedAvailableCount, recommendedSelectedCount };
546 }
548 > private createSkippedResult(questions: IQuestion[]): IToolResult {
549 const skippedAnswers: Record<string, IQuestionAnswer> = {};
550 for (const question of questions) {
555 };
556 }
558 > private createAutopilotResult(questions: IQuestion[]): IToolResult {
559 const answers: Record<string, IQuestionAnswer> = {};
560 for (const question of questions) {
572 };
573 }
575 > /**
576 > * Build carousel answer data keyed by carousel question IDs for rendering
577 > * the completed summary in the UI during autopilot mode.
578 > */
579 > private buildAutopilotCarouselAnswers(questions: IQuestion[], carousel: ChatQuestionCarouselData, idToHeaderMap: Map<string, string>): IChatQuestionAnswers {
580 const data: IChatQuestionAnswers = {};
581 // Build reverse map: original header -> internal carousel question ID
609 return data;
610 }
612 > private sendTelemetry(requestId: string | undefined, questionCount: number, answeredCount: number, skippedCount: number, freeTextCount: number, recommendedAvailableCount: number, recommendedSelectedCount: number, duration: number): void {
613 this.telemetryService.publicLog2<AskQuestionsToolInvokedEvent, AskQuestionsToolInvokedClassification>('askQuestionsToolInvoked', {
614 requestId,
622 });
623 }
625 >
626 > type AskQuestionsToolInvokedEvent = {
627 > requestId: string | undefined;
628 > questionCount: number;
629 > answeredCount: number;
630 > skippedCount: number;
631 > freeTextCount: number;
632 > recommendedAvailableCount: number;
633 > recommendedSelectedCount: number;
634 > duration: number;
635 > };
636 >
637 > type AskQuestionsToolInvokedClassification = {
638 > requestId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the current request turn.' };
639 > questionCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The total number of questions asked' };
640 > answeredCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of questions that were answered' };
641 > skippedCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of questions that were skipped' };
642 > freeTextCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of questions answered with free text input' };
643 > recommendedAvailableCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of questions that had a recommended option' };
644 > recommendedSelectedCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of questions where the user selected the recommended option' };
645 > duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The total time in milliseconds to complete all questions' };
646 > owner: 'digitarald';
647 > comment: 'Tracks usage of the AskQuestions tool for agent clarifications';
648 > };