diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 3caf8e8556a..23ef3d71187 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1438,7 +1438,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'languageModels'); return extHostLanguageModels.onDidChangeProviders(listener, thisArgs, disposables); }, - sendChatRequest(languageModel: string, messages: vscode.LanguageModelChatMessage[], options: vscode.LanguageModelChatRequestOptions, token: vscode.CancellationToken) { + sendChatRequest(languageModel: string, messages: (vscode.LanguageModelChatMessage | vscode.LanguageModelChatMessage2)[], options: vscode.LanguageModelChatRequestOptions, token: vscode.CancellationToken) { checkProposedApiEnabled(extension, 'languageModels'); return extHostLanguageModels.sendChatRequest(extension, languageModel, messages, options, token); }, @@ -1707,12 +1707,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatRequestTurn: extHostTypes.ChatRequestTurn, ChatResponseTurn: extHostTypes.ChatResponseTurn, ChatLocation: extHostTypes.ChatLocation, - LanguageModelChatSystemMessage: extHostTypes.LanguageModelChatSystemMessage, - LanguageModelChatUserMessage: extHostTypes.LanguageModelChatUserMessage, - LanguageModelChatAssistantMessage: extHostTypes.LanguageModelChatAssistantMessage, - LanguageModelSystemMessage: extHostTypes.LanguageModelChatSystemMessage, // TODO@jrieken REMOVE - LanguageModelUserMessage: extHostTypes.LanguageModelChatUserMessage, // TODO@jrieken REMOVE - LanguageModelAssistantMessage: extHostTypes.LanguageModelChatAssistantMessage, // TODO@jrieken REMOVE + LanguageModelChatMessageRole: extHostTypes.LanguageModelChatMessageRole, + LanguageModelChatMessage2: extHostTypes.LanguageModelChatMessage2, + LanguageModelChatSystemMessage: extHostTypes.LanguageModelChatSystemMessage,// TODO@jrieken REMOVE + LanguageModelChatUserMessage: extHostTypes.LanguageModelChatUserMessage,// TODO@jrieken REMOVE + LanguageModelChatAssistantMessage: extHostTypes.LanguageModelChatAssistantMessage,// TODO@jrieken REMOVE LanguageModelError: extHostTypes.LanguageModelError, NewSymbolName: extHostTypes.NewSymbolName, NewSymbolNameTag: extHostTypes.NewSymbolNameTag, diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index d0202a84451..4a707110621 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -7,7 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ExtHostLanguageModelsShape, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { LanguageModelError } from 'vs/workbench/api/common/extHostTypes'; +import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; @@ -22,6 +22,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; import { ILogService } from 'vs/platform/log/common/log'; import { Iterable } from 'vs/base/common/iterator'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export interface IExtHostLanguageModels extends ExtHostLanguageModels { } @@ -185,7 +186,11 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); }); - return data.provider.provideLanguageModelResponse2(messages.map(typeConvert.LanguageModelMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); + if (data.provider.provideLanguageModelResponse) { + return data.provider.provideLanguageModelResponse(messages.map(typeConvert.LanguageModelChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); + } else { + return data.provider.provideLanguageModelResponse2(messages.map(typeConvert.LanguageModelMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); + } } @@ -259,25 +264,27 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } } - async sendChatRequest(extension: IExtensionDescription, languageModelId: string, messages: vscode.LanguageModelChatMessage[], options: vscode.LanguageModelChatRequestOptions, token: CancellationToken) { + async sendChatRequest(extension: IExtensionDescription, languageModelId: string, messages: (vscode.LanguageModelChatMessage | vscode.LanguageModelChatMessage2)[], options: vscode.LanguageModelChatRequestOptions, token: CancellationToken) { + + const internalMessages: IChatMessage[] = this._convertMessages(extension, messages); const from = extension.identifier; const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, options.justification); if (!metadata || !this._allLanguageModelData.has(languageModelId)) { - throw LanguageModelError.NotFound(`Language model '${languageModelId}' is unknown.`); + throw extHostTypes.LanguageModelError.NotFound(`Language model '${languageModelId}' is unknown.`); } if (this._isUsingAuth(from, metadata)) { const success = await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, options.justification, options.silent); if (!success || !this._modelAccessList.get(from)?.has(metadata.extension)) { - throw LanguageModelError.NoPermissions(`Language model '${languageModelId}' cannot be used by '${from.value}'.`); + throw extHostTypes.LanguageModelError.NoPermissions(`Language model '${languageModelId}' cannot be used by '${from.value}'.`); } } const requestId = (Math.random() * 1e6) | 0; - const requestPromise = this._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.LanguageModelMessage.from), options.modelOptions ?? {}, token); + const requestPromise = this._proxy.$fetchResponse(from, languageModelId, requestId, internalMessages, options.modelOptions ?? {}, token); const barrier = new Barrier(); @@ -303,11 +310,11 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { await barrier.wait(); if (error) { - if (error.name === LanguageModelError.name) { + if (error.name === extHostTypes.LanguageModelError.name) { throw error; } - throw new LanguageModelError( + throw new extHostTypes.LanguageModelError( `Language model '${languageModelId}' errored, check cause for more details`, 'Unknown', error @@ -317,6 +324,24 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { return res.apiObject; } + private _convertMessages(extension: IExtensionDescription, messages: (vscode.LanguageModelChatMessage2 | vscode.LanguageModelChatMessage)[]) { + const internalMessages: IChatMessage[] = []; + for (const message of messages) { + if (message instanceof extHostTypes.LanguageModelChatMessage2) { + if (message.role as number === extHostTypes.LanguageModelChatMessageRole.System) { + checkProposedApiEnabled(extension, 'languageModelSystem'); + } + internalMessages.push(typeConvert.LanguageModelChatMessage.from(message)); + } else { + if (message instanceof extHostTypes.LanguageModelChatSystemMessage) { + checkProposedApiEnabled(extension, 'languageModelSystem'); + } + internalMessages.push(typeConvert.LanguageModelMessage.from(message)); + } + } + return internalMessages; + } + async $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise { const data = this._pendingRequest.get(requestId);//.report(chunk); if (data) { @@ -376,7 +401,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { const data = this._allLanguageModelData.get(languageModelId); if (!data) { - throw LanguageModelError.NotFound(`Language model '${languageModelId}' is unknown.`); + throw extHostTypes.LanguageModelError.NotFound(`Language model '${languageModelId}' is unknown.`); } const local = Iterable.find(this._languageModels.values(), candidate => candidate.languageModelId === languageModelId); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index d163386bf00..f9571a57858 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2239,6 +2239,29 @@ export namespace ChatFollowup { } } + +export namespace LanguageModelChatMessage { + + export function to(message: chatProvider.IChatMessage): vscode.LanguageModelChatMessage2 { + switch (message.role) { + case chatProvider.ChatMessageRole.System: return new types.LanguageModelChatMessage2(types.LanguageModelChatMessageRole.System, message.content); + case chatProvider.ChatMessageRole.User: return new types.LanguageModelChatMessage2(types.LanguageModelChatMessageRole.User, message.content); + case chatProvider.ChatMessageRole.Assistant: return new types.LanguageModelChatMessage2(types.LanguageModelChatMessageRole.Assistant, message.content); + } + } + + export function from(message: vscode.LanguageModelChatMessage2): chatProvider.IChatMessage { + switch (message.role as types.LanguageModelChatMessageRole) { + case types.LanguageModelChatMessageRole.System: return { role: chatProvider.ChatMessageRole.System, content: message.content }; + case types.LanguageModelChatMessageRole.User: return { role: chatProvider.ChatMessageRole.User, content: message.content }; + case types.LanguageModelChatMessageRole.Assistant: return { role: chatProvider.ChatMessageRole.Assistant, content: message.content }; + } + } +} + +/** + * @deprecated + */ export namespace LanguageModelMessage { export function to(message: chatProvider.IChatMessage): vscode.LanguageModelChatMessage { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 9a85e2f08e8..c52e1c26cc2 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4424,6 +4424,25 @@ export enum ChatLocation { Editor = 4, } +export enum LanguageModelChatMessageRole { + User = 1, + Assistant = 2, + System = 3 +} + +export class LanguageModelChatMessage2 implements vscode.LanguageModelChatMessage2 { + + role: vscode.LanguageModelChatMessageRole; + content: string; + name: string | undefined; + + constructor(role: vscode.LanguageModelChatMessageRole, content: string, name?: string) { + this.role = role; + this.content = content; + this.name = name; + } +} + export class LanguageModelChatSystemMessage { content: string; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 402560ad1bd..c8c0a5d0e74 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -74,6 +74,7 @@ export const allApiProposals = Object.freeze({ interactive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactive.d.ts', interactiveWindow: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts', ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts', + languageModelSystem: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModelSystem.d.ts', languageModels: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModels.d.ts', languageStatusText: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatusText.d.ts', mappedEditsProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 504027744c4..6651c4ff859 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -19,6 +19,8 @@ declare module 'vscode' { onDidReceiveLanguageModelResponse2?: Event<{ readonly extensionId: string; readonly participant?: string; readonly tokenCount?: number }>; + provideLanguageModelResponse?(messages: LanguageModelChatMessage2[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + provideLanguageModelResponse2(messages: LanguageModelChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; provideTokenCount(text: string | LanguageModelChatMessage, token: CancellationToken): Thenable; diff --git a/src/vscode-dts/vscode.proposed.languageModelSystem.d.ts b/src/vscode-dts/vscode.proposed.languageModelSystem.d.ts new file mode 100644 index 00000000000..54e1da27a5b --- /dev/null +++ b/src/vscode-dts/vscode.proposed.languageModelSystem.d.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/206265 + + // TODO@API don't have this dedicated type but as property, e.g anthropic doesn't have a system-role, see + // https://github.com/anthropics/anthropic-sdk-typescript/blob/c2da9604646ff103fbdbca016a9a9d49b03b387b/src/resources/messages.ts#L384 + // So, we could have `LanguageModelChatRequestOptions#system` which would be more limiting but also more natural? + + export enum LanguageModelChatMessageRole { + System = 3 + } +} diff --git a/src/vscode-dts/vscode.proposed.languageModels.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts index bd2977ba5ee..db75bbeb4ea 100644 --- a/src/vscode-dts/vscode.proposed.languageModels.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -5,6 +5,8 @@ declare module 'vscode' { + // https://github.com/microsoft/vscode/issues/206265 + /** * Represents a language model response. * @@ -12,11 +14,6 @@ declare module 'vscode' { */ export interface LanguageModelChatResponse { - //TODO@API give this some structure - // https://github.com/openai/openai-openapi/blob/master/openapi.yaml#L7700, https://platform.openai.com/docs/guides/text-generation/chat-completions-api - // https://github.com/ollama/ollama/blob/main/docs/api.md#response-7 - // https://docs.anthropic.com/claude/reference/messages_post - /** * An async iterable that is a stream of text chunks forming the overall response. * @@ -40,12 +37,35 @@ declare module 'vscode' { stream: AsyncIterable; } - export enum LanguageModelChatMessageRole { - User, - Assistant, - // System see https://github.com/anthropics/anthropic-sdk-typescript/blob/c2da9604646ff103fbdbca016a9a9d49b03b387b/src/resources/messages.ts#L384 + //TODO@API give this some structure + // https://github.com/openai/openai-openapi/blob/master/openapi.yaml#L7700, https://platform.openai.com/docs/guides/text-generation/chat-completions-api + // https://github.com/ollama/ollama/blob/main/docs/api.md#response-7 + // https://docs.anthropic.com/claude/reference/messages_post + export interface LanguageModelChatResponse2 { + + message: { + role: LanguageModelChatMessageRole; + content: AsyncIterable; + }; } + /** + * Represents the role of a chat message. This is either the user or the assistant/model. + */ + export enum LanguageModelChatMessageRole { + /** + * The user role, e.g the human interacting with a language model. + */ + User = 1, + + /** + * The assistant role, e.g. the language model generating responses. + */ + // TODO@API name: model? + Assistant = 2 + } + + // TODO@API name: LanguageModelChatMessage once the deprecated stuff is removed export class LanguageModelChatMessage2 { /** * The role of this message. @@ -71,26 +91,9 @@ declare module 'vscode' { constructor(role: LanguageModelChatMessageRole, content: string, name?: string); } - export interface LanguageModelChatResponse2 { - - message: { - role: LanguageModelChatMessageRole; - content: AsyncIterable; - }; - } - - // TODO@API have just LanguageModelChatMessage & LanguageModelChatMessageRole - // TODO@API don't have SystemChatMessage because it encourages to mix system and user messages - // but AFAIK system message should/must come first (https://github.com/anthropics/anthropic-sdk-typescript/blob/c2da9604646ff103fbdbca016a9a9d49b03b387b/src/resources/messages.ts#L384) /** - * A language model message that represents a system message. - * - * System messages provide instructions to the language model that define the context in - * which user messages are interpreted. - * - * *Note* that a language model may choose to add additional system messages to the ones - * provided by extensions. + * @deprecated */ export class LanguageModelChatSystemMessage { @@ -108,7 +111,7 @@ declare module 'vscode' { } /** - * A language model message that represents a user message. + * @deprecated */ export class LanguageModelChatUserMessage { @@ -132,8 +135,7 @@ declare module 'vscode' { } /** - * A language model message that represents an assistant message, usually in response to a user message - * or as a sample response/reply-pair. + * @deprecated */ export class LanguageModelChatAssistantMessage { @@ -158,6 +160,7 @@ declare module 'vscode' { /** * Different types of language model messages. + * @deprecated */ export type LanguageModelChatMessage = LanguageModelChatSystemMessage | LanguageModelChatUserMessage | LanguageModelChatAssistantMessage; @@ -263,7 +266,6 @@ declare module 'vscode' { // TODO@API Revisit this, how do you do the first request? silent?: boolean; - // TODO@API add system messages here? /** * A set of options that control the behavior of the language model. These options are specific to the language model @@ -317,7 +319,7 @@ declare module 'vscode' { * @param token A cancellation token which controls the request. See {@link CancellationTokenSource} for how to create one. * @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when the request couldn't be made. */ - export function sendChatRequest(languageModel: string, messages: LanguageModelChatMessage[], options: LanguageModelChatRequestOptions, token: CancellationToken): Thenable; + export function sendChatRequest(languageModel: string, messages: (LanguageModelChatMessage | LanguageModelChatMessage2)[], options: LanguageModelChatRequestOptions, token: CancellationToken): Thenable; /** * Uses the language model specific tokenzier and computes the length in token of a given message. @@ -329,7 +331,10 @@ declare module 'vscode' { * @param token Optional cancellation token. * @returns A thenable that resolves to the length of the message in tokens. */ - export function computeTokenLength(languageModel: string, text: string | LanguageModelChatMessage, token?: CancellationToken): Thenable; + // TODO@API optional? + // ollama has nothing + // anthropic suggests to count after the fact https://github.com/anthropics/anthropic-tokenizer-typescript?tab=readme-ov-file#anthropic-typescript-tokenizer + export function computeTokenLength(languageModel: string, text: string | LanguageModelChatMessage | LanguageModelChatMessage2, token?: CancellationToken): Thenable; } /**