Path: blob/main/extensions/copilot/src/extension/mcp/vscode-node/mcpToolCallingTools.tsx
13401 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 * as vscode from 'vscode';6import { JsonSchema } from '../../../platform/configuration/common/jsonSchema';7import { CancellationError } from '../../../util/vs/base/common/errors';89export class McpPickRef {10public _inner?: { type: 'pick'; value: vscode.QuickPick<vscode.QuickPickItem> } | { type: 'input'; value: vscode.InputBox };11private _isDisposed = false;1213public readonly picks: {14id: string;15title: string;16choice: string;17}[] = [];1819constructor(private _inputBarrier: Promise<void>) {20this._inputBarrier.then(() => {2122if (!this._inner && !this._isDisposed) {23this.getPick().show();24this.reset(); // mark as "thinking"25}26});27}2829public async pick() {30await this._inputBarrier;31const pick = this.getPick();32pick.busy = false;33return pick;34}3536public async input() {37await this._inputBarrier;38const input = this.getInput();39input.busy = false;40return input;41}4243public reset() {44if (!this._inner) {45return;46}4748if (this._inner.type === 'pick') {49this._inner.value.items = [];50} else {51this._inner.value.value = '';52}5354this._inner.value.title = '🤔';55this._inner.value.placeholder = 'Thinking...';56this._inner.value.busy = true;57}5859public dispose() {60this._inner?.value.dispose();61this._isDisposed = true;62}6364private getInput() {65if (this._inner?.type !== 'input') {66this._inner?.value.dispose();6768const input = vscode.window.createInputBox();69input.ignoreFocusOut = true;70this._inner = { type: 'input', value: input };71}7273return this._inner.value;74}7576private getPick() {77if (this._inner?.type !== 'pick') {78this._inner?.value.dispose();7980const pick = vscode.window.createQuickPick();81pick.ignoreFocusOut = true;82this._inner = { type: 'pick', value: pick };83}8485return this._inner.value;86}87}8889interface IQuickInputToolArgs {90id: string;91title: string;92placeholder?: string;93value?: string;94}9596export class QuickInputTool {97public static readonly ID = 'getInput';98public static readonly description = 'Prompts the user for a short string input.';99public static readonly schema: JsonSchema = {100type: 'object',101properties: {102id: {103type: 'string',104description: 'An alphanumeric identifier for the input.',105},106title: {107type: 'string',108description: 'The title of the input box.',109},110placeholder: {111type: 'string',112description: 'The placeholder text for the input box.',113},114value: {115type: 'string',116description: 'The default value of the input box.',117},118},119required: ['title', 'id'],120};121122public static async invoke(ref: McpPickRef, args: IQuickInputToolArgs): Promise<vscode.LanguageModelToolResult> {123const input = await ref.input();124input.title = args.title;125input.placeholder = args.placeholder;126if (args.value) {127input.value = args.value;128}129input.ignoreFocusOut = true;130131const result = await new Promise<string | undefined>((resolve) => {132input.onDidAccept(() => {133const value = input.value;134resolve(value);135});136137input.onDidHide(() => {138resolve(undefined);139});140141input.show();142});143144ref.reset();145146if (result === undefined) {147throw new CancellationError();148}149150ref.picks.push({ id: args.id, title: args.title, choice: result });151return new vscode.LanguageModelToolResult([new vscode.LanguageModelTextPart(`${args.title}: ${result}`)]);152}153}154155interface IQuickPickToolArgs {156title: string;157placeholder?: string;158canPickMany?: boolean;159choices: { label: string; description?: string }[];160}161162export class QuickPickTool {163public static readonly ID = 'getChoice';164public static readonly description = 'Prompts the user to select from a list of choices. It returns the label or labels of the choices that were selected';165public static readonly schema: JsonSchema = {166type: 'object',167properties: {168title: {169type: 'string',170description: 'The title of the pick box.',171},172placeholder: {173type: 'string',174description: 'The placeholder text for the pick box.',175},176canPickMany: {177type: 'boolean',178description: 'If true, the user can select multiple choices.',179},180choices: {181type: 'array',182items: {183type: 'object',184properties: {185label: { type: 'string', description: 'The primary label of the choice of the choice.' },186description: { type: 'string', description: 'A brief extra description.' },187}188},189minItems: 1,190},191},192required: ['title', 'choices'],193};194195public static async invoke(ref: McpPickRef, args: IQuickPickToolArgs): Promise<vscode.LanguageModelToolResult> {196const pick = await ref.pick();197pick.title = args.title;198pick.placeholder = args.placeholder;199pick.items = args.choices;200pick.canSelectMany = args.canPickMany ?? false;201pick.ignoreFocusOut = true;202203let result = await new Promise<string | string[] | undefined>((resolve) => {204pick.onDidAccept(() => {205const value = args.canPickMany ? pick.selectedItems.map(i => i.label) : pick.selectedItems[0]?.label;206resolve(value);207});208209pick.onDidHide(() => {210resolve(undefined);211});212213pick.show();214});215216ref.reset();217218if (result === undefined) {219throw new CancellationError();220}221if (Array.isArray(result)) {222result = '- ' + result.join('\n- ');223}224225return new vscode.LanguageModelToolResult([new vscode.LanguageModelTextPart(`${args.title}: ${result}`)]);226}227}228229230