Add LM chat model provider sample (#1200)

This commit is contained in:
Logan Ramos
2025-07-24 12:30:34 -04:00
committed by GitHub
parent 12fc09f452
commit 0aef2e9e50
14 changed files with 23142 additions and 0 deletions

5
chat-model-provider-sample/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
out
dist
node_modules
.vscode-test/
*.vsix

View File

@ -0,0 +1,35 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js",
"${workspaceFolder}/dist/**/*.js"
],
"preLaunchTask": "tasks: watch-tests"
}
]
}

View File

@ -0,0 +1,9 @@
{
"search.exclude": {
"out": true
},
"git.branchProtection": [
"main"
],
"files.trimTrailingWhitespace": true
}

View File

@ -0,0 +1,40 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never",
"group": "watchers"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "watch-tests",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never",
"group": "watchers"
},
"group": "build"
},
{
"label": "tasks: watch-tests",
"dependsOn": [
"npm: watch",
"npm: watch-tests"
],
"problemMatcher": []
}
]
}

View File

@ -0,0 +1,11 @@
.vscode/**
.vscode-test/**
src/**
.gitignore
.yarnrc
webpack.config.js
vsc-extension-quickstart.md
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts

View File

@ -0,0 +1,101 @@
# Chat Model Provider Sample
This VS Code extension demonstrates how to implement a custom chat model provider using the Language Model (LM) API. It serves as a sample implementation for developers who want to integrate their own AI models with VS Code's chat functionality.
## Features
This extension provides:
- **Custom Chat Model Provider**: Implements the `LanguageModelChatProvider2` interface to register custom AI models
- **Multiple Model Support**: Demonstrates how to provide multiple models from a single provider
- **Sample Models**: Includes two example models:
- **Dog Model**: Responds with dog-themed messages ("Woof!")
- **Cat Model**: Responds with cat-themed messages ("Meow!")
## Architecture
The extension consists of two main components:
### Extension Activation (`src/extension.ts`)
- Registers the sample chat model provider with VS Code's LM API
- Uses the vendor ID `"sample"` to identify the provider
### Chat Model Provider (`src/provider.ts`)
- Implements `LanguageModelChatProvider2` interface
- Provides model information including capabilities (tool calling, vision support)
- Handles chat requests and returns appropriate responses
- Includes token counting functionality
## Model Capabilities
Each sample model declares the following capabilities:
- **Tool Calling**: ✅ Enabled
- **Vision**: ✅ Enabled
- **Max Input Tokens**: 120,000
- **Max Output Tokens**: 8,192
## Getting Started
### Prerequisites
- VS Code version 1.103.0 or higher
- Node.js and npm installed
### Installation and Development
1. Clone this repository
2. Navigate to the extension directory:
```bash
cd chat-model-provider-sample
```
3. Install dependencies:
```bash
npm install
```
4. Compile the extension:
```bash
npm run compile
```
5. Press `F5` to launch a new Extension Development Host window
6. The extension will be active and ready to provide chat models
### Building and Watching
- **Build once**: `npm run compile`
- **Watch mode**: `npm run watch` (automatically recompiles on file changes)
- **Lint code**: `npm run lint`
## Usage
Once the extension is active:
1. Open VS Code's chat interface
2. Click the model picker and click manage models
3. Select the sample provider
4. Check the models based on what you want in the model picker
5. Send a request to the model
## API Usage
This extension uses the proposed `chatProvider` API. The key components include:
- `vscode.lm.registerChatModelProvider()` - Registers the provider
- `LanguageModelChatProvider2` interface - Defines the provider contract
- `LanguageModelChatInformation` - Describes model capabilities
- `ChatResponseFragment2` - Handles streaming responses
## Customization
To create your own chat model provider:
1. Modify the `SampleChatModelProvider` class in `src/provider.ts`
2. Update the model information in `getChatModelInfo()`
3. Implement your custom logic in `provideLanguageModelChatResponse()`
4. Adjust the vendor ID and model IDs as needed
5. Update the `package.json` with your extension details
## Related
- [VS Code Extension API](https://code.visualstudio.com/api)
- [Language Model API Documentation](https://code.visualstudio.com/api/extension-guides/chat)
- [VS Code Extension Samples](https://github.com/Microsoft/vscode-extension-samples)

View File

@ -0,0 +1,49 @@
/**
* ESLint configuration for the project.
*
* See https://eslint.style and https://typescript-eslint.io for additional linting options.
*/
// @ts-check
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import stylistic from '@stylistic/eslint-plugin';
export default tseslint.config(
{
ignores: [
'.vscode-test',
'out',
'**/*.d.ts'
]
},
{
files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'],
},
js.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
{
plugins: {
'@stylistic': stylistic
},
rules: {
'curly': 'warn',
'@stylistic/semi': ['warn', 'always'],
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/array-type': 'off',
'@typescript-eslint/naming-convention': [
'warn',
{
'selector': 'import',
'format': ['camelCase', 'PascalCase']
}
],
'@typescript-eslint/no-unused-vars': [
'error',
{
'argsIgnorePattern': '^_'
}
]
}
}
);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
{
"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.",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vscode-extension-samples"
},
"version": "0.1.0",
"engines": {
"vscode": "^1.103.0-20250721"
},
"categories": [
"AI",
"Chat"
],
"activationEvents": [
"onStartupFinished"
],
"contributes": {
"languageModels": [
{
"vendor": "sample",
"displayName": "Sample Model Vendor"
}
]
},
"main": "./out/extension.js",
"scripts": {
"vscode:prepublish": "npm run compile",
"download-api": "dts dev",
"postdownload-api": "dts main",
"postinstall": "npm run download-api",
"compile": "tsc -p ./",
"lint": "eslint",
"watch": "tsc -watch -p ./"
},
"dependencies": {
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@stylistic/eslint-plugin": "^2.9.0",
"@types/node": "^22",
"@vscode/dts": "^0.4.1",
"@types/vscode": "^1.102.0",
"eslint": "^9.13.0",
"typescript": "^5.8.2",
"typescript-eslint": "^8.26.0"
}
}

View File

@ -0,0 +1,8 @@
import * as vscode from 'vscode';
import { SampleChatModelProvider } from './provider';
export function activate(_: vscode.ExtensionContext) {
vscode.lm.registerChatModelProvider('sample', new SampleChatModelProvider());
}
export function deactivate() { }

View File

@ -0,0 +1,39 @@
import { CancellationToken, ChatResponseFragment2, LanguageModelChatInformation, LanguageModelChatMessage, LanguageModelChatProvider2, LanguageModelChatRequestHandleOptions, LanguageModelTextPart, Progress, ProviderResult } from "vscode";
function getChatModelInfo(id: string, name: string): LanguageModelChatInformation {
return {
id,
name,
description: "A sample chat model for demonstration purposes.",
family: "sample-family",
maxInputTokens: 120000,
maxOutputTokens: 8192,
version: "1.0.0",
capabilities: {
toolCalling: true,
vision: true,
}
};
}
export class SampleChatModelProvider implements LanguageModelChatProvider2 {
prepareLanguageModelChat(_options: { silent: boolean; }, _token: CancellationToken): ProviderResult<LanguageModelChatInformation[]> {
return [
getChatModelInfo("sample-dog-model", "Dog Model"),
getChatModelInfo("sample-cat-model", "Cat Model"),
];
}
async provideLanguageModelChatResponse(model: LanguageModelChatInformation, _messages: Array<LanguageModelChatMessage>, _options: LanguageModelChatRequestHandleOptions, progress: Progress<ChatResponseFragment2>, _token: CancellationToken): Promise<void> {
if (model.id === "sample-dog-model") {
progress.report({index: 0, part: 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.") });
} else {
progress.report({ index: 0, part: new LanguageModelTextPart("Unknown model.") });
}
}
async provideTokenCount(_model: LanguageModelChatInformation, _text: string | LanguageModelChatMessage, _token: CancellationToken): Promise<number> {
return 42;
}
}

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"module": "Node16",
"target": "ES2022",
"lib": [
"ES2022"
],
"sourceMap": true,
"rootDir": "src",
"strict": true, /* enable all strict type-checking options */
"outDir": "out",
"skipLibCheck": true /* Skip type checking of declaration files */
/* Additional Checks */
// "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.proposed.chatProvider.d.ts"
],
"exclude": [
"vscode.d.ts"
]
}

20792
chat-model-provider-sample/vscode.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,144 @@
/*---------------------------------------------------------------------------------------------
* 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' {
// @API extension ship a d.ts files for their options
// @API the LanguageModelChatProvider2 is an alternative that combines a source, like ollama etc, with
// concrete models. The `provideLanguageModelChatData` would do the discovery and auth dances and later
// the model data is passed to the concrete function for making a requested or counting token
// TODO@API name scheme
export interface LanguageModelChatRequestHandleOptions {
// initiator
readonly extensionId: string;
/**
* A set of options that control the behavior of the language model. These options are specific to the language model
* and need to be looked up in the respective documentation.
*/
readonly modelOptions: { [name: string]: any };
/**
* An optional list of tools that are available to the language model. These could be registered tools available via
* {@link lm.tools}, or private tools that are just implemented within the calling extension.
*
* If the LLM requests to call one of these tools, it will return a {@link LanguageModelToolCallPart} in
* {@link LanguageModelChatResponse.stream}. It's the caller's responsibility to invoke the tool. If it's a tool
* registered in {@link lm.tools}, that means calling {@link lm.invokeTool}.
*
* Then, the tool result can be provided to the LLM by creating an Assistant-type {@link LanguageModelChatMessage} with a
* {@link LanguageModelToolCallPart}, followed by a User-type message with a {@link LanguageModelToolResultPart}.
*/
tools?: LanguageModelChatTool[];
/**
* The tool-selecting mode to use. {@link LanguageModelChatToolMode.Auto} by default.
*/
toolMode?: LanguageModelChatToolMode;
}
// TODO@API names: LanguageModelChatMetadata, LanguageModelChatItem
export interface LanguageModelChatInformation {
readonly id: string;
/**
* Human-readable name of the language model.
*/
readonly name: string;
/**
* Opaque family-name of the language model. Values might be `gpt-3.5-turbo`, `gpt4`, `phi2`, or `llama`
* but they are defined by extensions contributing languages and subject to change.
*/
readonly family: string;
/**
* An optional, human-readable description of the language model.
*/
readonly description?: string;
/**
* An optional, human-readable string representing the cost of using the language model.
*/
readonly cost?: string;
/**
* Opaque version string of the model. This is defined by the extension contributing the language model
* and subject to change while the identifier is stable.
*/
readonly version: string;
readonly maxInputTokens: number;
readonly maxOutputTokens: number;
/**
* When present, this gates the use of `requestLanguageModelAccess` behind an authorization flow where
* the user must approve of another extension accessing the models contributed by this extension.
* Additionally, the extension can provide a label that will be shown in the UI.
*/
auth?: true | { label: string };
// TODO@API maybe an enum, LanguageModelChatProviderPickerAvailability?
// TODO@API isPreselected proposed
readonly isDefault?: boolean;
// TODO@API nuke
readonly isUserSelectable?: boolean;
readonly capabilities?: {
// TODO@API have mimeTypes that you support
readonly vision?: boolean;
// TODO@API should be `boolean | number` so extensions can express how many tools they support
readonly toolCalling?: boolean | number;
// TODO@API DO NOT SUPPORT THIS
// readonly agentMode?: boolean;
// TODO@API support prompt TSX style messages, MAYBE leave it out for now
readonly promptTsx?: boolean;
};
/**
* Optional category to group models by in the model picker.
* The lower the order, the higher the category appears in the list.
* Has no effect if `isUserSelectable` is `false`.
* If not specified, the model will appear in the "Other Models" category.
*/
readonly category?: { label: string; order: number };
}
export interface LanguageModelChatProvider2<T extends LanguageModelChatInformation = LanguageModelChatInformation> {
// signals a change from the provider to the editor so that prepareLanguageModelChat is called again
onDidChange?: Event<void>;
// NOT cacheable (between reloads)
prepareLanguageModelChat(options: { silent: boolean }, token: CancellationToken): ProviderResult<T[]>;
provideLanguageModelChatResponse(model: T, messages: Array<LanguageModelChatMessage | LanguageModelChatMessage2>, options: LanguageModelChatRequestHandleOptions, progress: Progress<ChatResponseFragment2>, token: CancellationToken): Thenable<any>;
provideTokenCount(model: T, text: string | LanguageModelChatMessage | LanguageModelChatMessage2, token: CancellationToken): Thenable<number>;
}
export namespace lm {
export function registerChatModelProvider(vendor: string, provider: LanguageModelChatProvider2): Disposable;
}
export interface ChatResponseFragment2 {
index: number;
part: LanguageModelTextPart | LanguageModelToolCallPart;
}
}