TL;DR
- VSCodeには、Language ModelやChatを利用するためのAPIが生えており、このAPIを利用することで、生成AI・GitHub Copilotを利用したExtensionの開発が可能
- TISとしてPromptisというExtensionをVS Code Marketplaceに公開、OSSとしても公開したものの、メンテできる人を増やさないといけないので、ここに学び方みたいなやつを書いた
- 俺たちの明日はどっちだ
VS Code Chat Extension を作るためには
まずは公式ドキュメントを読みましょう。
一次情報源にあたれっていうのはおじさんが新入社員の頃からよく言われました。おじさんとの約束だぞ。
読むべきドキュメント
前提として、VS Codeは開発者用のドキュメントも充実しています。こういうQiitaとかより、まずは本家にあたりましょう。Extension GuideにExtensionに関するドキュメントがあるので、まずはこの辺りを読み進めると良いでしょう。
その中でも、GitHub Copilotを利用していくために重要なのは、Language Modelの扱い方と、GitHub Copilot ChatのようなChatを実現するアーキテクチャ。
それぞれのドキュメントはここにまとまっています。
Language Model
生成AIを利用するということは大体にしてモデルを利用することになるわけですが、VS Code APIの1つ、 selectChatModels
を呼び出すことで利用可能なモデルを取得できます。
モデルはLanguageModelChat
として表現されており、そのクラスが持つメソッドsendRequest
経由で、モデルに対してプロンプトや会話履歴が送信できます。
この辺りは、チュートリアルにある以下のスニペットでイメージが掴めると思います。力強く掴もう、未来をその手で。
const disposable = vscode.commands.registerTextEditorCommand(
'code-tutor.annotate',
async (textEditor: vscode.TextEditor) => {
// Get the code with line numbers from the current editor
const codeWithLineNumbers = getVisibleCodeWithLineNumbers(textEditor);
// select the 4o chat model
let [model] = await vscode.lm.selectChatModels({
vendor: 'copilot',
family: 'gpt-4o'
});
// init the chat message
const messages = [
vscode.LanguageModelChatMessage.User(ANNOTATION_PROMPT),
vscode.LanguageModelChatMessage.User(codeWithLineNumbers)
];
// make sure the model is available
if (model) {
// send the messages array to the model and get the response
let chatResponse = await model.sendRequest(
messages,
{},
new vscode.CancellationTokenSource().token
);
// handle chat response
await parseChatResponse(chatResponse, textEditor);
}
}
);
Chat
チャットについても様々な観点がありますが、ユーザからチャット経由でリクエストが送信されたら、それに応答しないといけません。これは、はい、あなたの想像通りリクエストハンドラとして実装する形になります。
Chatのリクエストハンドラ
ハンドラの型はChatRequestHandler
として定義されているので、これをそのまま実装することになります。ユーザがチャットで入力した内容は、ハンドラの第一引数であるrequest
に含まれているし、その応答は第3引数であるstreamに書き出すことでユーザに届けられます。基本的には、requestをゴニョゴニョして結果をstreamに書き出せば良いわけで、その"ゴニョゴニョ"がLLMの呼び出しとかになるわけですね。
もちろんハンドラを定義するだけでは、VS Codeにハンドラの存在を認識させられません。ハンドラの登録メソッドも当然用意されていて、その勇猛たる名称をcreateChatParticipant
といいます。このメソッドを利用して、こんな感じで、VS Codeに登録すれば良い。
// Participantと、当該Participantへのメンションに反応するハンドラを設定
const promptis = vscode.chat.createChatParticipant(PARTICIPANT_ID, chatHandler);
Participantってなんだよ
ここでいきなりParticipant
というキーワードが現れるけど、これはチャットのメンション先を表現します。例えばみなさんがよくお使いの@workspace
とか@vscode
とかがParticipantに該当します。ChatタイプのVS Code Exntensionは、基本的にこのParticipantを新たに定義することになり、Promptisなら@promptis
というParticipantを定義しています。
では定義はどこにあるかというと、package.json
にあります。Promptisではここですね。
これに限らず、Extensionに必要な設定項目や、チャット上で利用できるコマンド(/tests
や/explain
)など、VS Codeに認識させないといけないような内容は、package.json上で定義する仕様になっています。
この辺りのリファレンスはExtension Manifestにあるので読んでみると良いでしょう。
参考になるソースはあるんか?
この手のChat Extensionについては、Microsoftが公開しているchat-exampleを見るのが早いです。
VS Codeは進化が早く、月次リリースに伴って様々なAPIが生えていきますが、上記exampleも頻繁にアップデートされていきます。素晴らしい。前に読んだ内容が今や消失しています。苦しい。
例えば最近だと、VS CodeにTool CallingのAPIが生えたわけですが、chat-exampleにはもう反映されています。ソースを読むと使い方のイメージがわかりやすくて良いですね。
Extensionのテストどうするのよ
Extensionのテストどうやるんか、という疑問を呈する諸兄に朗報ですが、テスト実行方法の整備はTesting Extensionsに書いてあるので、まずはこれを読みましょう。
テストの仕組み
テスト自体は、別のVS Codeインスタンスを起動した後、そこに開発したExtensionを読み込ませて、テストコードを実行するという仕組みなっています。この VS Codeインスタンスは「Extension Development Host」と呼ばれています。
まぁ、Microsoftがvscode-test-cliというCLIツールを用意してくれているので、難しいことはあまり考えなくて良いでしょう。
実際、Promptisでも単にvscode-test
を呼び出すことで、用意したテストコードを実行するだけとなっています。以下のような感じ。実行したら、指定したバージョンのVS Codeをダウンロードしてくれて、それをExtension Development Hostとして起動してくれるという感じ。
"test:linux": "xvfb-run -a vscode-test --coverage --coverage-output --coverage-reporter html json-summary",
"test:win32:darwin": "vscode-test --coverage --coverage-output --coverage-reporter html json-summary",
上記npm scriptを見ていただければわかるように、coverageを計測しレポートしてくれる機能も内包しているので至れり尽くせりですね。
苦労したところ
実は結構苦労したという実感があるのは、カバレッジのバッジ実装でした。
以下の画像はREADME.mdにあるバッジだけど、Testのバッジと比べて、coverageのバッジの解像度の低さがお分かりになると思います。実はTest
のバッジはSVGなんだけど、coverageのバッジはPNGになっている。
これには悲しい裏話がありまして、VS Code Marketplace上でSVGを表示できるのは、Marketplace側のホワイトリストで許可されたドメイン配下のみという仕様になっています。もしホワイトリスト以外のドメイン配下でSVGをホストし、それをREADMEに配置しようとすると、ビルドが失敗する。この仕様に気づくのに、結構かかった覚えがある。
coverageのバッジはgithub.io配下に配置しているんだけど、github.ioはホワイトリストに入っていない。なので、GitHub Actionsの中でSVGをPNGに変換するという謎の処理を挟むことになりました。
Coverallsなどの外部サービスを利用するとこの辺り楽になるはずなのだけれど、突貫で整備が必要だったので、そういった外部サービスのリスクアセスメントをし会社に認めてもらうよりも自分で変換したほうが早かった。助けてくれ誰か…。
蛇足ですが
先に述べたようにVS Codeの進化は相当早くて、Extension用のTool CallingのAPIは生えたし、Terminalの内容も読み取れるので、ターミナル上のエラーを読んでコードを自動修正したりするエージェント的な拡張が次々に出てくるでしょう。すでに出てきてますね。はい。
で、生成AIを中心にしたこのようなExtensionには、現時点では人の介在・AIへのフィードバックは必須となっており、そのインタフェースとしてチャットが用いられる時代はまだまだ続くと思われます。
この辺りはMicrosoftも意識しているようで、今回エントリで書いたようなChat Extensionを簡単に作れるようなユーテリティが公開されておりました。これでさらにChat Extensionの数は増えていくのではないでしょうか。
Promptisもこちらを利用するようにしないといつしか追随ができなくなると思われますので、Pull Requestお待ちしております。