Path: blob/main/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts
3296 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 { Separator } from '../../../../base/common/actions.js';6import { VSBuffer } from '../../../../base/common/buffer.js';7import { CancellationToken } from '../../../../base/common/cancellation.js';8import { Event } from '../../../../base/common/event.js';9import { IMarkdownString } from '../../../../base/common/htmlContent.js';10import { Iterable } from '../../../../base/common/iterator.js';11import { IJSONSchema } from '../../../../base/common/jsonSchema.js';12import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';13import { Schemas } from '../../../../base/common/network.js';14import { derived, IObservable, IReader, ITransaction, ObservableSet } from '../../../../base/common/observable.js';15import { ThemeIcon } from '../../../../base/common/themables.js';16import { URI } from '../../../../base/common/uri.js';17import { Location } from '../../../../editor/common/languages.js';18import { localize } from '../../../../nls.js';19import { ContextKeyExpression } from '../../../../platform/contextkey/common/contextkey.js';20import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';21import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';22import { IProgress } from '../../../../platform/progress/common/progress.js';23import { IVariableReference } from './chatModes.js';24import { IChatExtensionsContent, IChatTodoListContent, IChatToolInputInvocationData, type IChatTerminalToolInvocationData } from './chatService.js';25import { ChatRequestToolReferenceEntry } from './chatVariableEntries.js';26import { LanguageModelPartAudience } from './languageModels.js';27import { PromptElementJSON, stringifyPromptElementJSON } from './tools/promptTsxTypes.js';2829export interface IToolData {30id: string;31source: ToolDataSource;32toolReferenceName?: string;33icon?: { dark: URI; light?: URI } | ThemeIcon;34when?: ContextKeyExpression;35tags?: string[];36displayName: string;37userDescription?: string;38modelDescription: string;39inputSchema?: IJSONSchema;40canBeReferencedInPrompt?: boolean;41/**42* True if the tool runs in the (possibly remote) workspace, false if it runs43* on the host, undefined if known.44*/45runsInWorkspace?: boolean;46alwaysDisplayInputOutput?: boolean;47}4849export interface IToolProgressStep {50readonly message: string | IMarkdownString | undefined;51readonly increment?: number;52readonly total?: number;53}5455export type ToolProgress = IProgress<IToolProgressStep>;5657export type ToolDataSource =58| {59type: 'extension';60label: string;61extensionId: ExtensionIdentifier;62}63| {64type: 'mcp';65label: string;66serverLabel: string | undefined;67instructions: string | undefined;68collectionId: string;69definitionId: string;70}71| {72type: 'user';73label: string;74file: URI;75}76| {77type: 'internal';78label: string;79} | {80type: 'external';81label: string;82};8384export namespace ToolDataSource {8586export const Internal: ToolDataSource = { type: 'internal', label: 'Built-In' };8788/** External tools may not be contributed or invoked, but may be invoked externally and described in an IChatToolInvocationSerialized */89export const External: ToolDataSource = { type: 'external', label: 'External' };9091export function toKey(source: ToolDataSource): string {92switch (source.type) {93case 'extension': return `extension:${source.extensionId.value}`;94case 'mcp': return `mcp:${source.collectionId}:${source.definitionId}`;95case 'user': return `user:${source.file.toString()}`;96case 'internal': return 'internal';97case 'external': return 'external';98}99}100101export function equals(a: ToolDataSource, b: ToolDataSource): boolean {102return toKey(a) === toKey(b);103}104105export function classify(source: ToolDataSource): { readonly ordinal: number; readonly label: string } {106if (source.type === 'internal') {107return { ordinal: 1, label: localize('builtin', 'Built-In') };108} else if (source.type === 'mcp') {109return { ordinal: 2, label: localize('mcp', 'MCP Server: {0}', source.label) };110} else if (source.type === 'user') {111return { ordinal: 0, label: localize('user', 'User Defined') };112} else {113return { ordinal: 3, label: localize('ext', 'Extension: {0}', source.label) };114}115}116}117118export interface IToolInvocation {119callId: string;120toolId: string;121parameters: Object;122tokenBudget?: number;123context: IToolInvocationContext | undefined;124chatRequestId?: string;125chatInteractionId?: string;126toolSpecificData?: IChatTerminalToolInvocationData | IChatToolInputInvocationData | IChatExtensionsContent | IChatTodoListContent;127modelId?: string;128}129130export interface IToolInvocationContext {131sessionId: string;132}133134export function isToolInvocationContext(obj: any): obj is IToolInvocationContext {135return typeof obj === 'object' && typeof obj.sessionId === 'string';136}137138export interface IToolInvocationPreparationContext {139parameters: any;140chatRequestId?: string;141chatSessionId?: string;142chatInteractionId?: string;143}144145export type ToolInputOutputBase = {146/** Mimetype of the value, optional */147mimeType?: string;148/** URI of the resource on the MCP server. */149uri?: URI;150/** If true, this part came in as a resource reference rather than direct data. */151asResource?: boolean;152};153154export type ToolInputOutputEmbedded = ToolInputOutputBase & {155type: 'embed';156value: string;157/** If true, value is text. If false or not given, value is base64 */158isText?: boolean;159};160161export type ToolInputOutputReference = ToolInputOutputBase & { type: 'ref'; uri: URI };162163export interface IToolResultInputOutputDetails {164readonly input: string;165readonly output: (ToolInputOutputEmbedded | ToolInputOutputReference)[];166readonly isError?: boolean;167}168169export interface IToolResultOutputDetails {170readonly output: { type: 'data'; mimeType: string; value: VSBuffer };171}172173export function isToolResultInputOutputDetails(obj: any): obj is IToolResultInputOutputDetails {174return typeof obj === 'object' && typeof obj?.input === 'string' && (typeof obj?.output === 'string' || Array.isArray(obj?.output));175}176177export function isToolResultOutputDetails(obj: any): obj is IToolResultOutputDetails {178return typeof obj === 'object' && typeof obj?.output === 'object' && typeof obj?.output?.mimeType === 'string' && obj?.output?.type === 'data';179}180181export interface IToolResult {182content: (IToolResultPromptTsxPart | IToolResultTextPart | IToolResultDataPart)[];183toolResultMessage?: string | IMarkdownString;184toolResultDetails?: Array<URI | Location> | IToolResultInputOutputDetails | IToolResultOutputDetails;185toolResultError?: string;186}187188export function toolResultHasBuffers(result: IToolResult): boolean {189return result.content.some(part => part.kind === 'data');190}191192export interface IToolResultPromptTsxPart {193kind: 'promptTsx';194value: unknown;195}196197export function stringifyPromptTsxPart(part: IToolResultPromptTsxPart): string {198return stringifyPromptElementJSON(part.value as PromptElementJSON);199}200201export interface IToolResultTextPart {202kind: 'text';203value: string;204audience?: LanguageModelPartAudience[];205}206207export interface IToolResultDataPart {208kind: 'data';209value: {210mimeType: string;211data: VSBuffer;212};213audience?: LanguageModelPartAudience[];214}215216export interface IToolConfirmationMessages {217title: string | IMarkdownString;218message: string | IMarkdownString;219disclaimer?: string | IMarkdownString;220allowAutoConfirm?: boolean;221terminalCustomActions?: ToolConfirmationAction[];222}223224export interface IToolConfirmationAction {225label: string;226disabled?: boolean;227tooltip?: string;228data: any;229}230231export type ToolConfirmationAction = IToolConfirmationAction | Separator;232233export enum ToolInvocationPresentation {234Hidden = 'hidden',235HiddenAfterComplete = 'hiddenAfterComplete'236}237238export interface IPreparedToolInvocation {239invocationMessage?: string | IMarkdownString;240pastTenseMessage?: string | IMarkdownString;241originMessage?: string | IMarkdownString;242confirmationMessages?: IToolConfirmationMessages;243presentation?: ToolInvocationPresentation;244toolSpecificData?: IChatTerminalToolInvocationData | IChatToolInputInvocationData | IChatExtensionsContent | IChatTodoListContent;245}246247export interface IToolImpl {248invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise<IToolResult>;249prepareToolInvocation?(context: IToolInvocationPreparationContext, token: CancellationToken): Promise<IPreparedToolInvocation | undefined>;250}251252export type IToolAndToolSetEnablementMap = ReadonlyMap<IToolData | ToolSet, boolean>;253254export class ToolSet {255256protected readonly _tools = new ObservableSet<IToolData>();257258protected readonly _toolSets = new ObservableSet<ToolSet>();259260/**261* A homogenous tool set only contains tools from the same source as the tool set itself262*/263readonly isHomogenous: IObservable<boolean>;264265constructor(266readonly id: string,267readonly referenceName: string,268readonly icon: ThemeIcon,269readonly source: ToolDataSource,270readonly description?: string,271) {272273this.isHomogenous = derived(r => {274return !Iterable.some(this._tools.observable.read(r), tool => !ToolDataSource.equals(tool.source, this.source))275&& !Iterable.some(this._toolSets.observable.read(r), toolSet => !ToolDataSource.equals(toolSet.source, this.source));276});277}278279addTool(data: IToolData, tx?: ITransaction): IDisposable {280this._tools.add(data, tx);281return toDisposable(() => {282this._tools.delete(data);283});284}285286addToolSet(toolSet: ToolSet, tx?: ITransaction): IDisposable {287if (toolSet === this) {288return Disposable.None;289}290this._toolSets.add(toolSet, tx);291return toDisposable(() => {292this._toolSets.delete(toolSet);293});294}295296getTools(r?: IReader): Iterable<IToolData> {297return Iterable.concat(298this._tools.observable.read(r),299...Iterable.map(this._toolSets.observable.read(r), toolSet => toolSet.getTools(r))300);301}302}303304305export const ILanguageModelToolsService = createDecorator<ILanguageModelToolsService>('ILanguageModelToolsService');306307export type CountTokensCallback = (input: string, token: CancellationToken) => Promise<number>;308309export interface ILanguageModelToolsService {310_serviceBrand: undefined;311onDidChangeTools: Event<void>;312registerToolData(toolData: IToolData): IDisposable;313registerToolImplementation(id: string, tool: IToolImpl): IDisposable;314registerTool(toolData: IToolData, tool: IToolImpl): IDisposable;315getTools(): Iterable<Readonly<IToolData>>;316getTool(id: string): IToolData | undefined;317getToolByName(name: string, includeDisabled?: boolean): IToolData | undefined;318invokeTool(invocation: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise<IToolResult>;319setToolAutoConfirmation(toolId: string, scope: 'workspace' | 'profile' | 'session' | 'never'): void;320getToolAutoConfirmation(toolId: string): 'workspace' | 'profile' | 'session' | 'never';321resetToolAutoConfirmation(): void;322cancelToolCallsForRequest(requestId: string): void;323toToolEnablementMap(toolOrToolSetNames: Set<string>): Record<string, boolean>;324toToolAndToolSetEnablementMap(toolOrToolSetNames: readonly string[]): IToolAndToolSetEnablementMap;325toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[];326327readonly toolSets: IObservable<Iterable<ToolSet>>;328getToolSet(id: string): ToolSet | undefined;329getToolSetByName(name: string): ToolSet | undefined;330createToolSet(source: ToolDataSource, id: string, referenceName: string, options?: { icon?: ThemeIcon; description?: string }): ToolSet & IDisposable;331}332333export function createToolInputUri(toolOrId: IToolData | string): URI {334if (typeof toolOrId !== 'string') {335toolOrId = toolOrId.id;336}337return URI.from({ scheme: Schemas.inMemory, path: `/lm/tool/${toolOrId}/tool_input.json` });338}339340export function createToolSchemaUri(toolOrId: IToolData | string): URI {341if (typeof toolOrId !== 'string') {342toolOrId = toolOrId.id;343}344return URI.from({ scheme: Schemas.vscode, authority: 'schemas', path: `/lm/tool/${toolOrId}` });345}346347348