Skip to content

GitHub Copilotのプログラムからの呼び出し

問題の説明

GitHub CopilotをIDEプラグインとして利用している多くの開発者が、プログラムから自動的にCopilotの機能を呼び出したいというニーズを持っています。具体的な課題として:

  1. Copilotが生成する上位のコードサジェストを自動取得したい
  2. 特定のコンテキストで提案されたコードをファイルに保存したい
  3. コード生成結果をバッチ処理や自動テストに統合したい

Copilotは内部でOpenAIモデルを使用しているにもかかわらず、公式のAPIが提供されていないため、多くの開発者が自動化手段を探しています。

解決策

推奨方法: 公式Language Serverの利用 (2024年更新)

GitHubが公開した公式の@github/copilot-language-serverパッケージを使用する方法:

bash
# npmで公式Language Serverをインストール
npm install @github/copilot-language-server

基本的な動作フロー:

具体的な実装手順

Node.jsを使用した最小限の実装例:

js
const { spawn } = require('child_process');

// 公式Language Serverを起動
const server = spawn('node', [
  './node_modules/@github/copilot-language-server/dist/language-server.js',
  '--stdio'
]);

// LSPメッセージ送信関数
const sendMessage = (data) => {
  const dataString = JSON.stringify({ ...data, jsonrpc: "2.0" });
  const contentLength = Buffer.byteLength(dataString, "utf8");
  const rpcString = `Content-Length: ${contentLength}\r\n\r\n${dataString}`;
  server.stdin.write(rpcString);
};

// リクエスト管理用Map
const resolveMap = new Map();
let requestId = 0;

// リクエスト送信関数
const sendRequest = (method, params) => {
  sendMessage({ id: ++requestId, method, params });
  return new Promise((resolve) => {
    resolveMap.set(requestId, resolve);
  });
};

// サーバーからのレスポンス処理
server.stdout.on('data', (data) => {
  const responses = data.toString().split(/Content-Length: \d+\r\n\r\n/);
  
  responses.forEach(raw => {
    if (raw.trim()) {
      try {
        const response = JSON.parse(raw);
        if (response.id && resolveMap.has(response.id)) {
          resolveMap.get(response.id)(response.result);
        }
      } catch(e) { /* エラー処理 */ }
    }
  });
});

// メイン処理
const main = async () => {
  // サーバーの初期化
  await sendRequest("initialize", {
    capabilities: { workspace: { workspaceFolders: true } }
  });

  // ファイルオープン通知
  sendMessage({
    method: "textDocument/didOpen",
    params: {
      textDocument: {
        uri: "file:///project/main.py",
        languageId: "python",
        text: "def calculate_sum(a, b):\n    return a +"
      }
    }
  });

  // 補完リクエスト
  const suggestions = await sendRequest("getCompletions", {
    doc: {
      uri: "file:///project/main.py",
      position: { line: 1, character: 14 }
    }
  });

  console.log("上位サジェスト:", suggestions.items.slice(0, 3));
};

main();

認証の準備

コード実行前にCopilotにログインしている必要があります

  • 初回実行時には認証コードが表示される
  • 指示に従ってGitHubで認証を完了

実行結果の例

json
{
  "items": [
    {
      "text": "b",
      "position": { "line": 1, "character": 14 },
      "uuid": "1a2b3c4d"
    },
    {
      "text": "b * 2",
      "position": { "line": 1, "character": 14 },
      "uuid": "5e6f7g8h"
    },
    {
      "text": "b if a > 0 else 0",
      "position": { "line": 1, "character": 14 },
      "uuid": "9i0j1k2l"
    }
  ]
}

非公式APIを使う代替方法

copilot-apiプロジェクトの利用方法:

bash
# リポジトリのクローン
git clone https://github.com/B00TK1D/copilot-api.git
cd copilot-api

# 依存関係のインストール
npm install

# サーバー起動
node server.js

API呼び出し例:

python
import requests

response = requests.post(
  "http://localhost:3000/completions",
  json={
    "code": "def calculate_sum(a, b):\n    return a +",
    "position": {"line": 1, "column": 14}
  }
)

print(response.json()["suggestions"])

注意点

非公式APIはGitHubの利用規約に抵触する可能性があるため、自己責任で使用してください

詳細解説

Language Server Protocol (LSP) の仕組み

GitHub CopilotはLanguage Server Protocolを通じて通信します。主要なメソッド:

メソッド名役割
initialize接続初期化
textDocument/didOpenファイルオープン通知
textDocument/didChangeコンテンツ変更通知
getCompletionsコード補完リクエスト (カスタム)
getCompletionsCycling代替候補取得 (カスタム)

位置指定の重要性

positionパラメータでは正確なカーソル位置を指定:

  • line: 0から始まる行番号
  • character: 行内の文字位置(スペースもカウント)
python
# サンプルコード
def example():
    print("Hello, World!")  # この行はline:1、最後の"の位置はcharacter:22

認証フロー

自動ログインを実装する場合は追加処理が必要:

js
// 認証開始リクエスト
const authData = await sendRequest("signInInitiate", {
  user: "github_username"
});

// ユーザーが認証コードをGitHubに入力
console.log("認証コード:", authData.userCode);

// 認証確認
const tokens = await sendRequest("signInConfirm", {
  deviceCode: authData.deviceCode,
  userCode: authData.userCode
});

console.log("アクセストークン:", tokens.token);

考慮事項

  1. レート制限: Copilotには不明確なレート制限が存在
  2. 機密情報: クライアント実装で取得したデータは慎重に扱う
  3. バージョン互換性: Language Serverの更新でAPIが変更される可能性
  4. 著作権: 生成されたコードのライセンスと著作権に注意
  5. 公式APIの将来: GitHubが公式APIを公開する可能性を監視

重要警告

この手法はGitHub Copilotの利用規約を厳密に解釈するとグレーゾーンです

最適な実装例(要約)

js
const runCopilot = async (filePath, code, position) => {
  const server = spawn('node', [copilotServerPath]);
  
  // ... LSP処理関数の定義 ...

  await sendRequest("initialize", { capabilities: {} });
  
  sendMessage({
    method: "textDocument/didOpen",
    params: { textDocument: { 
      uri: `file://${filePath}`, 
      text: code,
      languageId: detectLanguage(filePath)
    }}
  });
  
  return sendRequest("getCompletions", {
    doc: { uri: `file://${filePath}`, position }
  });
};

// 使用方法:
const results = await runCopilot(
  "/project/main.py", 
  "def example():\n    return '", 
  { line: 1, character: 14 }
);

console.log("上位3つのサジェスト:", results.items.slice(0, 3));

まとめ

GitHub Copilotのサジェストをプログラムから取得するには:

  1. 公式Language Serverを使用した方法が最も信頼性が高い
  2. Node.jsでLSPクライアントを実装し、getCompletionsリクエストを送信
  3. 認証状態を事前に確立しておく
  4. 公式APIが公開されるまで代替手段として活用可能
  5. 生成されたコードの品質管理は依然として重要

実際のプロダクション環境で使用する場合は、法的観点からの検討と継続的なメンテナンス計画を推奨します。