diff --git a/chat-model-provider-sample/package-lock.json b/chat-model-provider-sample/package-lock.json index 8bab2ece..432a3c5f 100644 --- a/chat-model-provider-sample/package-lock.json +++ b/chat-model-provider-sample/package-lock.json @@ -12,14 +12,14 @@ "@eslint/js": "^9.13.0", "@stylistic/eslint-plugin": "^2.9.0", "@types/node": "^22", - "@types/vscode": "^1.102.0", + "@types/vscode": "^1.104.0", "@vscode/dts": "^0.4.1", "eslint": "^9.13.0", "typescript": "^5.9.2", "typescript-eslint": "^8.39.0" }, "engines": { - "vscode": "^1.103.0-20250721" + "vscode": "^1.104.0-20250828" } }, "node_modules/@eslint-community/eslint-utils": { @@ -360,9 +360,9 @@ } }, "node_modules/@types/vscode": { - "version": "1.102.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.102.0.tgz", - "integrity": "sha512-V9sFXmcXz03FtYTSUsYsu5K0Q9wH9w9V25slddcxrh5JgORD14LpnOA7ov0L9ALi+6HrTjskLJ/tY5zeRF3TFA==", + "version": "1.104.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.104.0.tgz", + "integrity": "sha512-0KwoU2rZ2ecsTGFxo4K1+f+AErRsYW0fsp6A0zufzGuhyczc2IoKqYqcwXidKXmy2u8YB2GsYsOtiI9Izx3Tig==", "dev": true, "license": "MIT" }, diff --git a/chat-model-provider-sample/package.json b/chat-model-provider-sample/package.json index b9be16fa..17704a3a 100644 --- a/chat-model-provider-sample/package.json +++ b/chat-model-provider-sample/package.json @@ -1,8 +1,5 @@ { "name": "chat-model-provider-sample", - "enabledApiProposals": [ - "chatProvider" - ], "publisher": "vscode-samples", "displayName": "Copilot Model Provider Sample", "description": "Sample extension which provides chat models via the LM API.", @@ -12,17 +9,14 @@ }, "version": "0.1.0", "engines": { - "vscode": "^1.103.0-20250721" + "vscode": "^1.104" }, "categories": [ "AI", "Chat" ], - "activationEvents": [ - "onStartupFinished" - ], "contributes": { - "languageModels": [ + "languageModelChatProviders": [ { "vendor": "sample", "displayName": "Sample Model Vendor" @@ -39,12 +33,13 @@ "lint": "eslint", "watch": "tsc -watch -p ./" }, + "dependencies": { }, "devDependencies": { "@eslint/js": "^9.13.0", "@stylistic/eslint-plugin": "^2.9.0", "@types/node": "^22", - "@types/vscode": "^1.102.0", "@vscode/dts": "^0.4.1", + "@types/vscode": "^1.104.0", "eslint": "^9.13.0", "typescript": "^5.9.2", "typescript-eslint": "^8.39.0" diff --git a/chat-model-provider-sample/src/extension.ts b/chat-model-provider-sample/src/extension.ts index 5975037d..114e9150 100644 --- a/chat-model-provider-sample/src/extension.ts +++ b/chat-model-provider-sample/src/extension.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import { SampleChatModelProvider } from './provider'; export function activate(_: vscode.ExtensionContext) { - vscode.lm.registerChatModelProvider('sample', new SampleChatModelProvider()); + vscode.lm.registerLanguageModelChatProvider('sample', new SampleChatModelProvider()); } export function deactivate() { } diff --git a/chat-model-provider-sample/src/provider.ts b/chat-model-provider-sample/src/provider.ts index 77531184..bd241585 100644 --- a/chat-model-provider-sample/src/provider.ts +++ b/chat-model-provider-sample/src/provider.ts @@ -1,35 +1,35 @@ -import { CancellationToken, ChatResponseFragment2, LanguageModelChatInformation, LanguageModelChatMessage, LanguageModelChatProvider2, LanguageModelChatRequestHandleOptions, LanguageModelTextPart, Progress, ProviderResult } from "vscode"; +import { CancellationToken, LanguageModelChatInformation, LanguageModelChatMessage, LanguageModelChatProvider, LanguageModelResponsePart, LanguageModelTextPart, Progress, ProvideLanguageModelChatResponseOptions, ProviderResult } from "vscode"; function getChatModelInfo(id: string, name: string): LanguageModelChatInformation { return { id, name, - description: "A sample chat model for demonstration purposes.", + tooltip: "A sample chat model for demonstration purposes.", family: "sample-family", maxInputTokens: 120000, maxOutputTokens: 8192, version: "1.0.0", capabilities: { toolCalling: true, - vision: true, + imageInput: true, } }; } -export class SampleChatModelProvider implements LanguageModelChatProvider2 { - prepareLanguageModelChat(_options: { silent: boolean; }, _token: CancellationToken): ProviderResult { +export class SampleChatModelProvider implements LanguageModelChatProvider { + provideLanguageModelChatInformation(_options: { silent: boolean; }, _token: CancellationToken): ProviderResult { return [ getChatModelInfo("sample-dog-model", "Dog Model"), getChatModelInfo("sample-cat-model", "Cat Model"), ]; } - async provideLanguageModelChatResponse(model: LanguageModelChatInformation, _messages: Array, _options: LanguageModelChatRequestHandleOptions, progress: Progress, _token: CancellationToken): Promise { + async provideLanguageModelChatResponse(model: LanguageModelChatInformation, _messages: Array, _options: ProvideLanguageModelChatResponseOptions, progress: Progress, _token: CancellationToken): Promise { if (model.id === "sample-dog-model") { - progress.report({ index: 0, part: new LanguageModelTextPart("Woof! This is a dog model response.") }); + progress.report(new LanguageModelTextPart("Woof! This is a dog model response.")); } else if (model.id === "sample-cat-model") { - progress.report({ index: 0, part: new LanguageModelTextPart("Meow! This is a cat model response.") }); + progress.report(new LanguageModelTextPart("Meow! This is a cat model response.")); } else { - progress.report({ index: 0, part: new LanguageModelTextPart("Unknown model.") }); + progress.report(new LanguageModelTextPart("Unknown model.")); } } async provideTokenCount(_model: LanguageModelChatInformation, _text: string | LanguageModelChatMessage, _token: CancellationToken): Promise { diff --git a/chat-model-provider-sample/tsconfig.json b/chat-model-provider-sample/tsconfig.json index 5ec9f55f..1b812a99 100644 --- a/chat-model-provider-sample/tsconfig.json +++ b/chat-model-provider-sample/tsconfig.json @@ -14,10 +14,5 @@ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ - }, - "include": [ - "src/**/*", - "vscode.d.ts", - "vscode.proposed.chatProvider.d.ts" - ] -} \ No newline at end of file + } +} diff --git a/lsp-embedded-request-forwarding/client/src/embeddedSupport.ts b/lsp-embedded-request-forwarding/client/src/embeddedSupport.ts index e9a4187a..cd7d4bae 100644 --- a/lsp-embedded-request-forwarding/client/src/embeddedSupport.ts +++ b/lsp-embedded-request-forwarding/client/src/embeddedSupport.ts @@ -3,24 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextDocument, Position, Range } from 'vscode-languageclient'; import { LanguageService, TokenType } from 'vscode-html-languageservice'; -export interface LanguageRange extends Range { - languageId: string | undefined; - attributeValue?: boolean; -} - -export interface HTMLDocumentRegions { - getEmbeddedDocument(languageId: string, ignoreAttributeValues?: boolean): TextDocument; - getLanguageRanges(range: Range): LanguageRange[]; - getLanguageAtPosition(position: Position): string | undefined; - getLanguagesInDocument(): string[]; - getImportedScripts(): string[]; -} - -export const CSS_STYLE_RULE = '__'; - interface EmbeddedRegion { languageId: string | undefined; start: number; @@ -143,269 +127,6 @@ export function getCSSVirtualContent( return content; } - -export function getDocumentRegions( - languageService: LanguageService, - document: TextDocument -): HTMLDocumentRegions { - const regions: EmbeddedRegion[] = []; - const scanner = languageService.createScanner(document.getText()); - let lastTagName = ''; - let lastAttributeName: string | null = null; - let languageIdFromType: string | undefined = undefined; - const importedScripts: string[] = []; - - let token = scanner.scan(); - while (token !== TokenType.EOS) { - switch (token) { - case TokenType.StartTag: - lastTagName = scanner.getTokenText(); - lastAttributeName = null; - languageIdFromType = 'javascript'; - break; - case TokenType.Styles: - regions.push({ - languageId: 'css', - start: scanner.getTokenOffset(), - end: scanner.getTokenEnd() - }); - break; - case TokenType.Script: - regions.push({ - languageId: languageIdFromType, - start: scanner.getTokenOffset(), - end: scanner.getTokenEnd() - }); - break; - case TokenType.AttributeName: - lastAttributeName = scanner.getTokenText(); - break; - case TokenType.AttributeValue: - if (lastAttributeName === 'src' && lastTagName.toLowerCase() === 'script') { - let value = scanner.getTokenText(); - if (value[0] === "'" || value[0] === '"') { - value = value.substr(1, value.length - 1); - } - importedScripts.push(value); - } else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') { - if ( - /["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test( - scanner.getTokenText() - ) - ) { - languageIdFromType = 'javascript'; - } else if (/["']text\/typescript["']/.test(scanner.getTokenText())) { - languageIdFromType = 'typescript'; - } else { - languageIdFromType = undefined; - } - } else { - const attributeLanguageId = getAttributeLanguage(lastAttributeName!); - if (attributeLanguageId) { - let start = scanner.getTokenOffset(); - let end = scanner.getTokenEnd(); - const firstChar = document.getText()[start]; - if (firstChar === "'" || firstChar === '"') { - start++; - end--; - } - regions.push({ - languageId: attributeLanguageId, - start, - end, - attributeValue: true - }); - } - } - lastAttributeName = null; - break; - } - token = scanner.scan(); - } - return { - getLanguageRanges: (range: Range) => getLanguageRanges(document, regions, range), - getEmbeddedDocument: (languageId: string, ignoreAttributeValues: boolean) => - getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues), - getLanguageAtPosition: (position: Position) => - getLanguageAtPosition(document, regions, position), - getLanguagesInDocument: () => getLanguagesInDocument(document, regions), - getImportedScripts: () => importedScripts - }; -} - -function getLanguageRanges( - document: TextDocument, - regions: EmbeddedRegion[], - range: Range -): LanguageRange[] { - const result: LanguageRange[] = []; - let currentPos = range ? range.start : Position.create(0, 0); - let currentOffset = range ? document.offsetAt(range.start) : 0; - const endOffset = range ? document.offsetAt(range.end) : document.getText().length; - for (const region of regions) { - if (region.end > currentOffset && region.start < endOffset) { - const start = Math.max(region.start, currentOffset); - const startPos = document.positionAt(start); - if (currentOffset < region.start) { - result.push({ - start: currentPos, - end: startPos, - languageId: 'html' - }); - } - const end = Math.min(region.end, endOffset); - const endPos = document.positionAt(end); - if (end > region.start) { - result.push({ - start: startPos, - end: endPos, - languageId: region.languageId, - attributeValue: region.attributeValue - }); - } - currentOffset = end; - currentPos = endPos; - } - } - if (currentOffset < endOffset) { - const endPos = range ? range.end : document.positionAt(endOffset); - result.push({ - start: currentPos, - end: endPos, - languageId: 'html' - }); - } - return result; -} - -function getLanguagesInDocument( - _document: TextDocument, - regions: EmbeddedRegion[] -): string[] { - const result = []; - for (const region of regions) { - if (region.languageId && result.indexOf(region.languageId) === -1) { - result.push(region.languageId); - if (result.length === 3) { - return result; - } - } - } - result.push('html'); - return result; -} - -function getLanguageAtPosition( - document: TextDocument, - regions: EmbeddedRegion[], - position: Position -): string | undefined { - const offset = document.offsetAt(position); - for (const region of regions) { - if (region.start <= offset) { - if (offset <= region.end) { - return region.languageId; - } - } else { - break; - } - } - return 'html'; -} - -function getEmbeddedDocument( - document: TextDocument, - contents: EmbeddedRegion[], - languageId: string, - ignoreAttributeValues: boolean -): TextDocument { - let currentPos = 0; - const oldContent = document.getText(); - let result = ''; - let lastSuffix = ''; - for (const c of contents) { - if (c.languageId === languageId && (!ignoreAttributeValues || !c.attributeValue)) { - result = substituteWithWhitespace( - result, - currentPos, - c.start, - oldContent, - lastSuffix, - getPrefix(c) - ); - result += oldContent.substring(c.start, c.end); - currentPos = c.end; - lastSuffix = getSuffix(c); - } - } - result = substituteWithWhitespace( - result, - currentPos, - oldContent.length, - oldContent, - lastSuffix, - '' - ); - return TextDocument.create(document.uri, languageId, document.version, result); -} - -function getPrefix(c: EmbeddedRegion) { - if (c.attributeValue) { - switch (c.languageId) { - case 'css': - return CSS_STYLE_RULE + '{'; - } - } - return ''; -} -function getSuffix(c: EmbeddedRegion) { - if (c.attributeValue) { - switch (c.languageId) { - case 'css': - return '}'; - case 'javascript': - return ';'; - } - } - return ''; -} - -function substituteWithWhitespace( - result: string, - start: number, - end: number, - oldContent: string, - before: string, - after: string -) { - let accumulatedWS = 0; - result += before; - for (let i = start + before.length; i < end; i++) { - const ch = oldContent[i]; - if (ch === '\n' || ch === '\r') { - // only write new lines, skip the whitespace - accumulatedWS = 0; - result += ch; - } else { - accumulatedWS++; - } - } - result = append(result, ' ', accumulatedWS - after.length); - result += after; - return result; -} - -function append(result: string, str: string, n: number): string { - while (n > 0) { - if (n & 1) { - result += str; - } - n >>= 1; - str += str; - } - return result; -} - function getAttributeLanguage(attributeName: string): string | null { const match = attributeName.match(/^(style)$|^(on\w+)$/i); if (!match) {