😽

GitHub Copilot Extension を .NET で作成する

2025/01/21に公開

ごきげんよう!mihohoi です。

GitHub Copilot Extension を作りたいなと思って早数か月…
ふと .NET チャンネルで GitHub Copilot Extension を作る動画がアップロードされていることに気づき「これはキタ!!」ということで、.NET 初心者が動画を見ながら作ってみました。

Build Your First GitHub Copilot Extension - YouTube
https://www.youtube.com/watch?v=4h5ke0B9RHc

余談ですが、この間参加した Microsoft Ignite 2024 で、この方のセッション参加したことを思い出しました。お茶目な素敵な方でした。(プレゼンが激ツヨ)

GitHub Copilot Extension をつくるために必要準備しておくこと

そもそも、 GitHub Copilot Extensions とは何者か?というところですが、私の理解では「AI を使ったコード提案や生成などの機能を独自に構築することができる拡張機能」です。GitHub Copilot を自分好みの拡張機能に出来る、と言えば簡潔でしょうか。

その GitHub Copilot Extension を作るために必要な用語として以下があります。

Copilot agent
Copilot エージェントについて - GitHub Enterprise Cloud Docs

抜粋します。

Copilot agents は、Copilot Extensions に埋め込まれたカスタム ツールです。 これらは Copilot Chat と統合され、特定のニーズに合わせて調整された追加機能が提供されます。

GitHub Apps
作成したアプリケーションを GitHub 上で使えるように登録するものです。
GitHub App の使用について - GitHub Docs

抜粋します。

GitHub Apps は、GitHub の機能を拡張するツールです。 GitHub Apps を使うと、GitHub で、issue を開く、pull request にコメントする、プロジェクトを管理する、といったことを行うことができます。 また、GitHub で発生するイベントに基づいて、GitHub の外部で何かを行うこともできます。 たとえば、GitHub で issue が開かれたときに、GitHub App で Slack に投稿できます。

GitHub Copilot Agent を作成し、それを GitHub Apps として登録することで、GitHub Copilot Extension に統合できる機能を提供することができます。Copilot Agent は、Extension を強化するためのバックエンドモジュールのような役割を果たし、ユーザーが必要とする高度なタスクを実行する仕組みを追加します。

実際に作ってみよう

元ネタは、冒頭で紹介した URL になります。動画の長さは 30 分ほどなので、見ながら書いても小1時間もあれば大丈夫だと思いますが、備忘録と理解もかねて書いていきます。ちなみに私は1時間半かかりました、2回見たので。
紹介した動画はとってもわかりやすいので、元の動画もぜひ見て欲しいです。

最初の準備

Visual Studio 2022 を開き、Create Project で "ASP.NET Core Web API" にしてプロジェクトを作成します。
使わない機能はオフにしておきます。

プロジェクトの作成

プロジェクトが出来上がったら、 Programs.cs を更地にします。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/info", () => "Hello Copilot!");

app.Run();

launchSettings.json の https を /info で開きたいため、以下のように変更します。

"https": {
~略~
  "launchUrl": "info",
~略~
},

この状態でまずは稼働することを確認します。
ブラウザが立ち上がり、"Hello Copilot!" と表示されればOKです。

この後、プロジェクトの開始設定を「Dev Tunnels」>「Create」して、Access を Public にします。
Dev Tunnels の設定

再度 Start して、localhost ではなく 限定的な URL が付与されたのを確認して、表示されている URL をメモしておきます。

書いていく(語彙力)

ベースができたので、ここから書いていきます。

まずは必要なものを入れていきます。Nuget〜

dotnet add package Octokit --version 13.0.1

using を記載します。

using Microsoft.AspNetCore.Mvc;
using Octokit;
using System.Diagnostics;
using System.Net.Http.Headers;

Callback を簡易的に入れる。

app.MapGet("/callback", () => "Hello Copilot!");

Copilot と Web サービスの間でメッセージをやり取りする事になるため、2つの追加のサポート タイプが必要になるので用意。app.Run(); 配下にクラスを作成します。

必要なクラス

  • Message: 役割とコンテンツで、Copilot との会話の一部として使用するもの
  • Payload: GitHub システムから Copilot , Web サービスに渡されるもの
internal class Message
{
    public required string Role { get; set; }
    public required string Content { get; set; }
}

internal class Payload
{
    public bool Stream { get; set; }
    public List<Message> Messages { get; set; }
}

実際の処理を書いていきます。一旦以下で仮置き。

app.MapPost(
    "/", async ([FromHeader(Name = "X-GitHub-Token")] string githubToken,
    [FromBody] Payload payload) =>
    {
        // 処理を書いていく
    });

ユーザーが誰であるかを検証する必要があるので、ユーザーに関する情報を取得します。

var octokitClient =
    new GitHubClient(new Octokit.ProductHeaderValue(appName))
    {
      // Token を取得してユーザー情報を Get する
        Credentials = new Credentials(githubToken)
    };

var user = await octokitClient.User.Current();
Console.WriteLine($"User: {user.Login}");

その下に、Copilot にお仕事をさせる追加のコンテキストを書いていきます。

// Insert special pirate-y system messages in the message list.
payload.Messages.Insert(0, new Message
{
    Role = "system",
    Content = $"Start every response with the user's name, which is @{user.Login}"
});
payload.Messages.Insert(0, new Message
{
    Role = "system",
    Content = "// ここに何か書く"
});

私は大好きなキャラクター…いやキャラクターではない、神ですね、彼を降臨させるためプロンプトを書きました。

その後ろに、書いたメッセージを取得する必要があります。ユーザーが聞いた内容を GitHub Copilot に渡して HTTP クライアントをインスタンス化してアクセストークンを提供します。
最後に payload が準備出来たらメッセージを送信します。

var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", githubToken);
payload.Stream = true;

で、ユーザーにメッセージを投稿するコードを書きます。

var copilotLLMResponse = await httpClient.PostAsJsonAsync(
    "https://api.githubcopilot.com/chat/completions", payload);

// Stream the response straight back to the user.
var responseStream = await copilotLLMResponse.Content.ReadAsStreamAsync();
return Results.Stream(responseStream, "application/json");

これで実装は完了です。

GitHub App への登録

コードが完成したら、GitHub App へ登録します。

Copilot 拡張機能用の GitHub アプリの作成 - GitHub Enterprise Cloud Docs

登録が完了したら、メニューの「Copilot」> 「URL」に Dev Tunnels で発行された URL を記載します。リリース範囲も自分だけにしておきましょう。
GitHub App 登録

実際に試す

それでは、GitHub 画面を使って実際に試していきます。
@ を入力すると、作成した GitHub Copilot Extension が出てきます。
呼び出し

それでは実際に聞いてみましょう。
呼び出し

無事に返答が返ってきました!いい...!!!

呼び出し

まとめ

簡単な応答をためすことが出来ました。これを発展すれば、自分専用にカスタマイズしたものを作ることができます。
プロジェクトで使用する場合は、コードの規約を書いておけば、出力する内容をそれにそった形で回答させることも出来そうです。(コツがいりそう)

おまけ(使用したコード)

Program.cs のコードを置いておきます。

using Microsoft.AspNetCore.Mvc;
using Octokit;
using System.Diagnostics;
using System.Net.Http.Headers;

var appName = "name";
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();


app.MapGet("/info", () => "Hello Copilot!");
app.MapGet("/callback", () => "Hello Copilot!");

app.MapPost(
    "/", async ([FromHeader(Name = "X-GitHub-Token")] string githubToken,
    [FromBody] Payload payload) =>
    {
        var octokitClient =
            new GitHubClient(new Octokit.ProductHeaderValue(appName))
            {
                Credentials = new Credentials(githubToken)
            };

        var user = await octokitClient.User.Current();
        Console.WriteLine($"User: {user.Login}");

        // Insert special pirate-y system messages in the message list.
        payload.Messages.Insert(0, new Message
        {
            Role = "system",
            Content = $"Start every response with the user's name, which is @{user.Login}"
        });
        payload.Messages.Insert(0, new Message
        {
            Role = "system",
            Content = "<何か書く>"
        });

        // Use Copilot's LLM to generate a response to the user's messages.
        var httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue("Bearer", githubToken);
        payload.Stream = true;

        var copilotLLMResponse = await httpClient.PostAsJsonAsync(
            "https://api.githubcopilot.com/chat/completions", payload);

        // Stream the response straight back to the user.
        var responseStream = await copilotLLMResponse.Content.ReadAsStreamAsync();
        return Results.Stream(responseStream, "application/json");
    });

    app.Run();

internal class Message
{
    public required string Role { get; set; }
    public required string Content { get; set; }
}

internal class Payload
{
    public bool Stream { get; set; }
    public List<Message> Messages { get; set; }
}
GitHubで編集を提案
Microsoft (有志)

Discussion