メモ
GitHub Copilot CLI(コマンドラインインターフェース) 拡張機能は現在試験的な機能であり、変更される可能性があります。
拡張機能を使用すると、独自の機能を Copilot CLI (コパイロット CLI)に追加できます。 各拡張機能は、対話型セッションと共に個別のプロセスとして実行され、それに接続する小さな Node.js モジュールです。 その接続を通じて、拡張機能は、**** がユーザーに代わって動作している間に呼び出すことができるCopilotと、自分で実行するスラッシュ コマンドを追加できます。
このチュートリアルでは、実行できる操作の例として、2 つの単純な拡張機能を構築します。
tool-timeと呼ばれるツールCopilot呼び出して、そのツール呼び出しがセッションでこれまでにかかった時間を報告できます。- カウントを開始してから使用したトークンの数を報告する、
/tokencountと呼ばれるスラッシュ コマンド。
どちらの例も、 Copilot CLI (コパイロット CLI)にバンドルされている SDK にのみ依存しているため、追加でインストールする必要はありません。 拡張機能のしくみの背景については、 About extensions for GitHub Copilot CLI(コマンドラインインターフェース) を参照してください。
警告
拡張機能は、お使いのコンピューターで特権を使用して実行されます。 信頼できる拡張コードのみを読み込みます。同じ方法で、自分で記述していない他のスクリプトのみを実行します。
前提条件
- GitHub Copilot CLI(コマンドラインインターフェース): Copilot CLI (コパイロット CLI) インストールしてセットアップする必要があります。 「GitHub Copilot CLI を始める」を参照してください。
- 試験的な機能が有効になっている: 拡張機能は現在、試験的な機能です。 このチュートリアルの手順では、
--experimentalコマンド ライン オプションを使用して、CLI を起動するたびに試験的な機能を有効にします。 - JavaScript: 拡張機能は JavaScript で記述されているため、独自の拡張機能を作成するには、この言語に精通している必要があります。
- リポジトリ: 2 番目の例ではプロジェクト レベルの拡張機能を追加するため、拡張機能を追加する Git リポジトリのローカル コピーが必要になります。
拡張機能の例 1: 「ツールタイム」ツール
この例では、**** というtool-time拡張機能を追加します。
session_tool_timeと呼ばれる新しいツールが追加され、Copilot呼び出して、このセッションにかかったツール呼び出しの所要時間と対象となる呼び出しの数を報告できます。
ツールが CLI で使用されるようにするには、CLI が単独で実行できないことをツールで実行する必要があります。 この場合、 session_tool_time ツールは、ツール呼び出しの開始と終了について CLI からイベントをリッスンし、タイミング自体を記録することで、ツール呼び出しにかかる時間を追跡します。 CLI は、Copilot が読み取れる場所にこれらのタイミング情報を記録しないため、Copilot がそれらを知る唯一の方法は、ツールを呼び出すことです。 ツールの説明では、これをモデルに説明します。これにより、ツール呼び出しのタイミングについて質問したときにツールの使用に向けて調整されます。
手順 1: 拡張ファイルを作成する
-
ホーム ディレクトリに次のディレクトリとファイルを作成します。
~/.copilot/extensions/tool-time/extension.mjsこれは
~/.copilot/extensions/下にあるため、拡張機能は、1 つのリポジトリだけでなく、 すべての ディレクトリのすべての CLI セッションで使用できます。 -
次のコードを
extension.mjsに追加します。JavaScript // Extension: tool-time // Adds a session_tool_time tool that reports how long Copilot's tool calls // have taken this session. Copilot is never told these timings and they // aren't written anywhere, so calling the tool is the only way to find // them out. import { joinSession } from "@github/copilot-sdk/extension"; // The tool's own name, so it can avoid timing its own calls. const TOOL_NAME = "session_tool_time"; // Module-level state persists for the whole session, because the extension // runs as a single long-lived process. const startTimes = new Map(); // tool call id -> Date.now() when call started let totalMs = 0; // total milliseconds spent across finished tool calls let callCount = 0; // number of finished tool calls measured const session = await joinSession({ tools: [ { name: TOOL_NAME, description: "Report how long your tool calls have taken in total so far " + "in THIS session, and how many calls that covers. You are " + "never told how long your tool calls take and the timings " + "aren't recorded anywhere you can read, so call this tool " + "whenever you are asked about it rather than estimating.", // Always keep this tool's description in the model's tool list, // even when tool search is active, so Copilot reliably sees it: defer: "never", // Force a permissions approval prompt once, for this extension, // rather than on every call of this tool: skipPermission: true, parameters: { type: "object", properties: {} }, handler: async () => { const seconds = (totalMs / 1000).toFixed(1); return `So far this session, Copilot's tool calls have taken ${seconds}s in total across ${callCount} call(s).`; }, }, ], }); // When a tool starts, record the time, keyed by the tool call id so the // matching completion can be found later. The tool's own calls are skipped. session.on("tool.execution_start", (event) => { const data = event.data ?? {}; if (data.toolName && data.toolName !== TOOL_NAME) { startTimes.set(data.toolCallId, Date.now()); } }); // When a tool finishes, add the elapsed time to the running total. session.on("tool.execution_complete", (event) => { const data = event.data ?? {}; const startedAt = startTimes.get(data.toolCallId); if (startedAt === undefined) { return; } startTimes.delete(data.toolCallId); totalMs += Date.now() - startedAt; callCount += 1; });// Extension: tool-time // Adds a session_tool_time tool that reports how long Copilot's tool calls // have taken this session. Copilot is never told these timings and they // aren't written anywhere, so calling the tool is the only way to find // them out. import { joinSession } from "@github/copilot-sdk/extension"; // The tool's own name, so it can avoid timing its own calls. const TOOL_NAME = "session_tool_time"; // Module-level state persists for the whole session, because the extension // runs as a single long-lived process. const startTimes = new Map(); // tool call id -> Date.now() when call started let totalMs = 0; // total milliseconds spent across finished tool calls let callCount = 0; // number of finished tool calls measured const session = await joinSession({ tools: [ { name: TOOL_NAME, description: "Report how long your tool calls have taken in total so far " + "in THIS session, and how many calls that covers. You are " + "never told how long your tool calls take and the timings " + "aren't recorded anywhere you can read, so call this tool " + "whenever you are asked about it rather than estimating.", // Always keep this tool's description in the model's tool list, // even when tool search is active, so Copilot reliably sees it: defer: "never", // Force a permissions approval prompt once, for this extension, // rather than on every call of this tool: skipPermission: true, parameters: { type: "object", properties: {} }, handler: async () => { const seconds = (totalMs / 1000).toFixed(1); return `So far this session, Copilot's tool calls have taken ${seconds}s in total across ${callCount} call(s).`; }, }, ], }); // When a tool starts, record the time, keyed by the tool call id so the // matching completion can be found later. The tool's own calls are skipped. session.on("tool.execution_start", (event) => { const data = event.data ?? {}; if (data.toolName && data.toolName !== TOOL_NAME) { startTimes.set(data.toolCallId, Date.now()); } }); // When a tool finishes, add the elapsed time to the running total. session.on("tool.execution_complete", (event) => { const data = event.data ?? {}; const startedAt = startTimes.get(data.toolCallId); if (startedAt === undefined) { return; } startTimes.delete(data.toolCallId); totalMs += Date.now() - startedAt; callCount += 1; });
メモ
*
@github/copilot-sdk/extension は拡張機能 SDK であり、CLI にバンドルされています。 CLI は拡張機能の実行時にこのインポートを自動的に解決するため、 package.json に追加したり、パッケージ マネージャーを実行したりする必要はありません。
*
startTimes、totalMs、およびcallCountの値は、モジュール スコープで有効です。 拡張機能はセッション全体に対して 1 つの有効期間の長いプロセスとして実行されるため、セッションが開いている限り累積されます。
手順 2: 拡張機能を読み込む
-
試験的な機能を有効にして対話型セッションを開始します。
Shell copilot --experimental
copilot --experimental拡張機能は
~/.copilot/extensions/下にあるため、任意のディレクトリから CLI を開始でき、拡張機能を使用できるようになります。既にセッションを開いている場合は、
/clearを実行して新しいセッションを開始し、ディスクから拡張機能を再読み込みします。 -
新しい拡張機能、すべての拡張機能、またはすべてのツールに昇格されたアクセス許可を付与しない場合は、新しい拡張機能にツールのアクセス許可プロンプトをスキップするように求められます。 [はい] または [はい] を選択し、このディレクトリで常に "user:tool-time" を許可します。
メモ
CLI の起動時にこのメッセージが表示されないようにするための最小限の昇格されたアクセス許可については、CLI スタートアップ コマンドに追加します。
Shell --allow-tool='extension-permission-access(user:tool-time)'
--allow-tool='extension-permission-access(user:tool-time)'詳細については、「ツールの使用の許可と拒否」を参照してください。
手順 3: 拡張機能が実行されていることを確認する
/extensions manage コマンドを実行して拡張機能マネージャーを開きます。
tool-time拡張機能は、実行中の状態のユーザー グループの下に表示されます。
Esc キーを押してマネージャーを閉じます。
手順 4: 試してみる
-
スラッシュ コマンドとは異なり、ツールは自分で呼び出しません。Copilot 便利なときに呼び出します。 まず、 Copilot いくつかのツール呼び出しを含むいくつかの作業を行います。次に例を示します。
Copilot prompt Explore the files in the current directory and give me a short summary of what's here.
Explore the files in the current directory and give me a short summary of what's here. -
Copilot応答が完了したら、次の質問をします。
Copilot prompt How long have tool calls taken so far this session?
How long have tool calls taken so far this session?エージェントは、新しい拡張機能から
session_tool_timeツールを呼び出し、それを使用して質問に回答します。ヒント
エージェントが新しいツールを使用したことを確認するには、 Copilotの応答の開始を確認します。 応答の先頭には、使用されたツールの名前を付ける必要があります。この場合は、
session_tool_time。
ツールのしくみ
joinSessionを 1 回呼び出すと、プレーンな Node.js ファイルがCopilot CLI (コパイロット CLI)拡張子に変換されます。 実行中のプロセスをセッションに接続し、拡張機能が CLI に追加するすべてのものを登録します 。この場合は、1 つのツールです。
ツールは、次のフィールドによって定義されます。
name—ツールの呼び出しに使用 Copilot 識別子。description—ツールの機能。 モデルは、このテキストに依存してツールを呼び出すタイミングを決定するため、明示的に説明する価値があります。 この説明は、これらのタイミング自体が伝えられていないことをモデルに指示します。これにより、何らかの方法でタイミングを解決しようとするのではなく、ツールの呼び出しに向かうようになります。parameters—ツールの引数を記述する JSON スキーマ。 このツールは何も受け取らないので、スキーマはプロパティのないオブジェクトです。handler— Copilot がツールを呼び出すときに実行される非同期関数。 返される文字列が何であれ、モデルが読み取るツールの結果になります。
さらに 2 つのフィールドによって、ツールがモデルに提供される方法が形成されます。
-
defer: "never"— ツールの説明は、常にモデルのツール リストに保持されます。 既定では、多くのツールが使用可能な場合、CLI は使用頻度の低いツールを延期し、モデルが必要に応じてそれらを検索できるようにします。deferを"never"に設定すると、このツールはその動作から除外されるため、Copilot常に表示されます。重要
defer: "never"ツールをCopilotで_利用可能_にするだけです。 呼び出しを _強制_Copilot しません。 プロンプトに対して適切な応答を生成する代替手段がある場合は、拡張機能で特定のツールを常に使用することを義務付ける方法はありません。 モデルは、使用するツールを常に自身で決定します。 この例では、ツールが提供する情報をモデルが認識する他の方法がないため、新しいツールが確実に使用されます。 -
skipPermission: trueを使用すると、各呼び出しの承認を求めずにツールを実行できます。 これは、拡張機能が既に収集した合計のみを読み取るため、ここで適切です。ファイルに触れたり、コマンドを実行したりすることはありません。
タイミング情報は、セッションを監視して収集されます。 拡張機能は、CLI がすべてのツール呼び出しを中心に生成する 2 つのイベントをサブスクライブします。
session.on("tool.execution_start", (event) => { /* ... */ });
session.on("tool.execution_complete", (event) => { /* ... */ });
tool.execution_start イベントには、ツールの名前 (event.data.toolName) が含まれます。
tool.execution_complete イベントには、success フラグが含まれます。 どちらのイベントも他のイベントの情報を持たないため、拡張機能は、それぞれに表示される toolCallId を使用してそれらを関連付けます。ツールが起動すると、その ID の下に現在の時刻が記録されます。 一致する完了が到着すると、経過時間 (ミリ秒) が実行中の合計に加算されます。 ツールは独自の呼び出しをスキップして、図に Copilotの実際の作業が反映されるようにします。
合計値は長時間存続する拡張機能プロセス内で保持されるため、セッション全体にわたります。 これは、セッション内で何が起きているかを監視し、複数の呼び出しにまたがって状態を維持するような、ジョブ拡張機能が得意とする作業です。
メモ
- 合計はメモリに保持されるため、拡張機能が再読み込みされるか、セッションが再起動されるたびにリセットされます (たとえば、
/clear後)。 - この値は、拡張機能側から見たウォールクロック時間、つまり各ツール呼び出しの開始イベントと完了イベントの間の間隔を測定したものです。したがって、呼び出しがあなたの承認待ちに費やした時間も含まれます。
拡張機能の例 2: トークン使用量のスラッシュコマンド
次の使用例は、 token-counterという名前のプロジェクト拡張機能を追加します。 拡張機能により、cli で/tokencountを操作するときに使用したトークンの数を確認するために使用できる、Copilotスラッシュ コマンドが追加されます。 トークンの使用状況の測定を開始する場合は、 /tokencount start を実行し、後で /tokencount を実行して、カウントを開始してから使用したトークンの数を確認できます。
拡張機能は、CLI によって生成されたイベントをサブスクライブすることで、セッションで使用されるトークンの合計を実行し続けます。これは、1 回限りのシェル コマンドでは実行できません。
手順 1: 拡張ファイルを作成する
-
プロジェクトの Git リポジトリのルートで、次のディレクトリとファイルを作成します。
.github/extensions/token-counter/extension.mjs -
次のコードを
extension.mjsに追加します。JavaScript // Extension: token-counter // Adds a /tokencount slash command that reports how many tokens you've used // since you started counting. import { joinSession } from "@github/copilot-sdk/extension"; // Module-level state. The extension runs as a single long-lived process for // the whole session, so these values persist between command invocations. let tokensUsed = 0; // Running total of tokens used so far this session. let startedAt = null; // tokensUsed when "/tokencount start" was last run. // null = not started. // startedAt allows you to count tokens multiple times in a session. const session = await joinSession({ commands: [ { name: "tokencount", description: "Report how many tokens you've used since " + "'/tokencount start'.", handler: async (ctx) => { const arg = (ctx.args ?? "").trim(); if (arg === "start") { // Reset: remember the current total as the new baseline. startedAt = tokensUsed; await session.log( "Token counter started. Run '/tokencount' later to " + "see how many tokens you've used.", { level: "info" }, ); return; } if (startedAt === null) { await session.log( "The token counter has not been started. Start by " + "entering '/tokencount start'.", { level: "info" }, ); return; } const used = tokensUsed - startedAt; await session.log( `You have used ${used} tokens since entering ` + "'/tokencount start'.", { level: "info" }, ); }, }, ], }); // The CLI emits an "assistant.usage" event after each assistant turn. Add the // tokens it reports to a running total kept in the extension's memory. session.on("assistant.usage", (event) => { const { inputTokens = 0, outputTokens = 0 } = event.data ?? {}; tokensUsed += inputTokens + outputTokens; });// Extension: token-counter // Adds a /tokencount slash command that reports how many tokens you've used // since you started counting. import { joinSession } from "@github/copilot-sdk/extension"; // Module-level state. The extension runs as a single long-lived process for // the whole session, so these values persist between command invocations. let tokensUsed = 0; // Running total of tokens used so far this session. let startedAt = null; // tokensUsed when "/tokencount start" was last run. // null = not started. // startedAt allows you to count tokens multiple times in a session. const session = await joinSession({ commands: [ { name: "tokencount", description: "Report how many tokens you've used since " + "'/tokencount start'.", handler: async (ctx) => { const arg = (ctx.args ?? "").trim(); if (arg === "start") { // Reset: remember the current total as the new baseline. startedAt = tokensUsed; await session.log( "Token counter started. Run '/tokencount' later to " + "see how many tokens you've used.", { level: "info" }, ); return; } if (startedAt === null) { await session.log( "The token counter has not been started. Start by " + "entering '/tokencount start'.", { level: "info" }, ); return; } const used = tokensUsed - startedAt; await session.log( `You have used ${used} tokens since entering ` + "'/tokencount start'.", { level: "info" }, ); }, }, ], }); // The CLI emits an "assistant.usage" event after each assistant turn. Add the // tokens it reports to a running total kept in the extension's memory. session.on("assistant.usage", (event) => { const { inputTokens = 0, outputTokens = 0 } = event.data ?? {}; tokensUsed += inputTokens + outputTokens; });
手順 2: 拡張機能を読み込む
実験用機能を有効にして、同じリポジトリから対話型セッションを開始します。
copilot --experimental
copilot --experimental
既にセッションを開いている場合は、 /clear を実行して、ディスクから拡張機能を再読み込みする新しいセッションを開始できます。
手順 3: 拡張機能が実行されていることを確認する
/extensions manage コマンドを実行して拡張機能マネージャーを開きます。
token-counter拡張機能は、Project グループの配下に、ステータスが running の状態で表示されるはずです。
Esc キーを押してマネージャーを閉じます。
/envを実行して、拡張機能を含むセッションに読み込まれたすべての概要を表示することもできます。
手順 4: 試してみる
ツールとは異なり、スラッシュ コマンドを自分で呼び出します。 カウンターを開始:
/tokencount start
/tokencount start
プロンプト Copilot 1 つまたは 2 つを送信して、一部のトークンを使用するようにします。たとえば、ファイルの説明を求めます。 次に、使用したトークンの数を確認します。
/tokencount
/tokencount
/tokencount startをもう一度実行すると、カウントは 0 から再開されます。
この例のしくみ
joinSessionを呼び出すと、拡張機能が CLI に追加するすべてのものが登録されます。この場合は、1 つのスラッシュ コマンドです。
スラッシュ コマンドは、次の 3 つのフィールドで定義されます。
name—コマンド名。先頭にスラッシュを付けずに指定します。tokencountを登録すると、セッションで/tokencountを使用できるようになります。description—スラッシュ コマンド ピッカーのコマンドの横に表示されるテキスト。handler—コマンドを呼び出すときに実行される非同期関数。 コマンド名の後に入力された生テキストを保持するargsプロパティを持つコンテキスト オブジェクトを受け取ります。/tokencount startの場合、ctx.argsは"start"。ベア/tokencountの場合は空の文字列です。
ハンドラーは、 session.log(message, { level: "info" })を使用してセッションに出力を書き戻し、トランスクリプトにメッセージを出力します。
使用されたトークンの数を知るために、拡張機能はセッション イベントをサブスクライブします。
session.on("assistant.usage", (event) => {
const { inputTokens = 0, outputTokens = 0 } = event.data ?? {};
tokensUsed += inputTokens + outputTokens;
});
CLI は、各アシスタント ターンの後に assistant.usage イベントを生成し、そのターンの入力トークンと出力トークン数を伝達します。 拡張機能が、それらをtokensUsedの累計に追加します。
/tokencount startを実行すると、ハンドラーは現在の合計をstartedAtに記録します。 セッションの後半で引数なしで /tokencount を入力すると、その違いが報告されます。 両方の変数は有効期間の長い拡張プロセスに含まれているため、セッション全体に対して保持されます。
メモ
合計はメモリに保持されるため、拡張機能が再読み込みされるか、セッションが再起動されるたびにリセットされます (たとえば、 /clear後)。
拡張機能の編集と再読み込み
拡張機能を開発するときに、 extension.mjs を編集し、変更内容を確認します。 ファイルを保存した後、次のいずれかの方法で新しいバージョンを取得できます。
- 拡張機能 (Copilot など) を再読み込みするように
Reload my extensionsに依頼します。 /clearを実行して、ディスクから拡張機能を再読み込みする新しいセッションを開始します。- CLI を再起動します。
拡張機能の起動に失敗した場合、または予期しない動作が発生した場合は、 /extensions manage 実行し、拡張機能を調べて、その状態とログ ファイルへのパスを確認します。 各拡張機能は、 ~/.copilot/logs/extensions/の下にログを書き込みます。これは、問題が発生したときに最適な場所です。
次のステップ
- これらの例の 1 つを自分のニーズに合わせて調整します。 CLI で、いずれかの拡張機能の例の動作を変更するように Copilot に依頼します。
- ユーザー レベルの拡張機能を共有します。 たとえば、
tool-time拡張機能をリポジトリの.github/extensions/ディレクトリに移動して、そのリポジトリで作業するすべてのユーザーと共有します。 - Copilotに新しい拡張機能を最初から作成するように依頼します。 Copilot CLI (コパイロット CLI) 拡張機能は Copilot SDKを利用するため、拡張機能は SDK で可能な限り何でも実行できるため、ボタンやフォーム、新しいツールやコマンドを含む対話型ビューを追加できます。 「Copilot SDK」を参照してください。