VS Code の拡張機能の公式サンプルに雑に「Language Model API」を組み込んで GitHub Copilot のレスポンスを得る

この記事の内容は、『以下の公式手順「Your First Extension」の内容をもとにしたシンプルな VS Code の拡張機能の作成』と、『作成した拡張機能に少しだけ手を加えて「[Language Model API](https://code.visualstudio.com/api/extension-guides/language-model)」を雑に組み込む』というのをやってみた話です。

●Your First Extension | Visual Studio Code Extension API
 https://code.visualstudio.com/api/get-started/your-first-extension

それで以下のように、API経由で簡易に GitHub Copilot の回答を得る、というお試しをやります。

https://x.com/youtoy/status/1879580271928025589

### 今回の内容をやってみたきっかけ

今回の内容をやった背景について書くと、以下の記事でとりあげた『拡張機能「Recline」の中で使われている VSCode Extension API の「Language Model API」を知って、実際に試してみたくなったことがきっかけ』です。

●「Recline」で使われている VSCode Extension API の「Language Model API」の公式情報： API の使い方など - Qiita
 https://qiita.com/youtoy/items/c4e70aa794b03d86ed08

この API を使うと、自作の VS Code の拡張機能から GitHub Copilot とのやりとりを扱うことができます。

●Language Model API | Visual Studio Code Extension API
 https://code.visualstudio.com/api/extension-guides/language-model

![2025-01-16_02-08-15.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/26c71b9f-8d76-2b8d-667f-0e287d8e016b.jpeg)

## まずは公式サンプルのシンプルな拡張機能を作ってみる
上記の API は拡張機能用の API のため拡張機能を作る必要があるのですが、今まで VS Code用の拡張機能を作ったことはありませんでした。

それで、まずは冒頭に掲載した公式手順を見て「Hello World」を出力するシンプルな拡張機能の作成から着手していきました。

### 下準備1

Yeoman をグローバルにインストールしないやり方で進めてみます。
具体的には以下のコマンドで下準備を進めます。

```zsh
npx --package yo --package generator-code -- yo code
```

上記のコマンドの実行後に出力された内容の最初の部分と、上記のコマンドについて書かれた公式ページをポストしていたものを掲載します。

https://x.com/youtoy/status/1879556476592914853

### 下準備2
この後は、以下のように設定してみました。

![2025-01-16_01-04-53.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/d5879cf7-d9a0-3d6a-f59e-cecc8c239d66.jpeg)

あまり深い意味はなく、自分の好みだったり適当に決めたりした感じです。

### 実行してみる
上記で作成されたものを VS Code で開くと、以下のようになりました（※ extension.js を開いた状態です）。

![2025-01-16_01-05-54.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/c4d4db6e-01ea-6d8c-347c-eba32315634d.jpeg)

まずは、今の状態のものを実行してみます。

この時は「F5」キーを押してデバッグ実行をしました。
（もちろん、以下のメニューをたどって実行したり、画面左の「実行とデバッグ」のアイコンから進めていったりしても大丈夫です）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/a67acf8c-79ee-223c-d4cb-0510c646e347.png)

デバック実行をした後、新しいウィンドウが開きます。
そのウィンドウの中で「Ctrl + Shift + P」のショートカットキーを使ってコマンドパレットを開き、以下のように「Hello World」のスペルを入力して処理を実行しました（※ 途中まで入力した状態で Hello World のコマンドが候補に出てくるので、それで実行すれば OK）。

![2025-01-16_01-13-31.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/18adcc8e-3a85-f027-31d2-84be5d63c4ea.jpeg)

その後、ウィンドウ右下に以下の表示が出て、拡張機能が正常に動作したことを確認できました。

![2025-01-16_01-17-16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/21c073d4-dd35-69a8-288b-bb0e8b690eda.jpeg)


## 雑に「Language Model API」を組み込む

それでは「Language Model API」を組み込んで GitHub Copilot のレスポンスを得られるようにしてみます。

Language Model API を使う処理では、「モデルの読み込み ⇒ プロンプトの送信 ⇒ レスポンスの受信」という流れを作れば試せそうでした。

上記サンプルは「処理の実行 ⇒ テキストの出力」という流れだったので、この処理の間に上記の Language Model API による処理を単純に組み込みます。

### コード
Language Model API を使った処理を追加実装したコードは、以下のとおりです。

```javascript
const vscode = require("vscode");

const MODEL_SELECTOR = {
  vendor: "copilot",
  //   family: "gpt-4o",
};

const craftedPrompt = [vscode.LanguageModelChatMessage.User("あなたは誰？")];

async function getFullResponse(chatResponse) {
  let fullText = "";
  for await (const fragment of chatResponse.text) {
    fullText += fragment;
  }
  return fullText;
}

function activate(context) {
  console.log('Congratulations, your extension "test" is now active!');

  const disposable = vscode.commands.registerCommand(
    "test.helloWorld",
    async () => {
      const [model] = await vscode.lm.selectChatModels(MODEL_SELECTOR);
      if (model) {
        const chatResponse = await model.sendRequest(
          craftedPrompt,
          {},
          new vscode.CancellationTokenSource().token
        );

        vscode.window.showInformationMessage("Hello World from test!");

        const fullResponse = await getFullResponse(chatResponse);
        vscode.window.showInformationMessage(fullResponse);
      } else {
        vscode.window.showErrorMessage("Failed to select model");
      }
    }
  );

  context.subscriptions.push(disposable);
}
function deactivate() {}

module.exports = {
  activate,
  deactivate,
};
```

いったん、モデル指定の部分は「`vendor: "copilot"`」のみのシンプルな記述で試してみました。

### 得られた結果
その結果、以下のレスポンスを得られたのを確認できました。

https://x.com/youtoy/status/1879571585235132804

Language Model API を使った処理を実行した際、以下のダイアログが出たので利用を許可する操作も行っています。

![2025-01-16_01-31-35.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/20fb0f0b-cfda-9d20-da46-0ef4992ab2a4.jpeg)

#### モデルを「claude-3.5-sonnet」にして実行してみる
前の記事でも書いたのですが、記事執筆時点で選べるモデルは以下のように複数あります。

![2025-01-16_02-10-34.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/9308818d-5f13-62a5-9352-811b21b2f8c8.jpeg)

この中の「claude-3.5-sonnet」を指定して、先ほどのプログラムを実行する、というのも試してみました。具体的には、モデル指定の部分を以下としました。

```javascript
const MODEL_SELECTOR = {
  vendor: "copilot",
  family: "claude-3.5-sonnet",
};
```

その結果、冒頭にも掲載していたように Anthropic・Claude の名前が含まれるレスポンスを得られました

![2025-01-16_02-20-40.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/43b8a717-41e8-6b48-b508-907c3778d7e8.jpeg)