참고
GitHub Copilot 명령 줄 인터페이스 (CLI) 확장은 현재 실험적 기능이며 변경될 수 있습니다.
확장 프로그램을 사용하면 코파일럿 CLI에 자체 기능을 추가할 수 있습니다. 각 확장은 대화형 세션과 함께 별도의 프로세스로 실행되고 다시 연결하는 작은 Node.js 모듈입니다. 해당 연결을 통해 확장은 사용자를 대신해 작업하는 동안 Copilot가 호출할 수 있는 도구와 사용자가 직접 실행하는 슬래시 명령어를 추가할 수 있습니다.
이 자습서에서는 수행할 수 있는 작업의 예로 두 개의 간단한 확장을 빌드합니다.
tool-time라고 하는 도구로, Copilot가 세션에서 지금까지 해당 도구 호출에 걸린 시간을 보고하기 위해 호출할 수 있습니다.- 계산을 시작한 이후 사용한 토큰 수를 보고하는 슬래시 명령입니다
/tokencount.
두 예제 모두 함께 제공되는 코파일럿 CLISDK에만 의존하므로 설치할 추가 항목이 없습니다. 확장 작동 방식에 대한 배경 정보는 About extensions for GitHub Copilot 명령 줄 인터페이스 (CLI)을 참조하세요.
경고
확장은 사용자 권한으로 컴퓨터에서 실행됩니다. 신뢰하는 확장 코드만 로드합니다. 동일한 방식으로 직접 작성하지 않은 다른 스크립트만 실행합니다.
필수 조건
- GitHub Copilot 명령 줄 인터페이스 (CLI): 설치하고 설정해야 합니다 코파일럿 CLI . GitHub Copilot CLI 시작하기을(를) 참조하세요.
- 실험적 기능 사용: 확장은 현재 실험적 기능입니다. 이 자습서의 단계에서는 명령줄 옵션을 사용하여
--experimentalCLI를 시작할 때마다 실험적 기능을 설정합니다. - JavaScript: 확장은 JavaScript로 작성되므로 고유한 확장을 만들려면 이 언어를 잘 알고 있어야 합니다.
- 리포지토리: 두 번째 예제에서는 프로젝트 수준 확장을 추가하므로 확장을 추가할 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/있으므로 하나의 리포지토리가 아닌 모든 디렉터리의 모든 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 는 CLI와 함께 번들로 제공되는 확장 SDK입니다. CLI는 확장을 실행할 때 이 가져오기를 자동으로 해결하므로 패키지 관리자에 package.json 추가하거나 실행할 필요가 없습니다.
*
startTimes, totalMs, callCount 값은 모듈 스코프에 있습니다. 확장은 전체 세션에 대해 수명이 긴 단일 프로세스로 실행되므로 세션이 열려 있는 동안 누적됩니다.
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 한 번의 호출이 일반 Node.js 파일을 코파일럿 CLI 확장으로 바꾸는 요소입니다. 실행 중인 프로세스를 세션에 연결하고 확장이 CLI에 추가하는 모든 항목(이 경우 단일 도구)을 등록합니다.
도구는 다음 필드에 의해 정의됩니다.
name- 식별자가 Copilot 도구를 호출하는 데 사용합니다.description- 도구가 수행하는 기능 모델은 이 텍스트를 사용하여 도구를 호출할 시기를 결정하므로 명시적일 가치가 있습니다. 이 설명은 모델에 이러한 타이밍 자체가 전달되지 않는다는 것을 알려주며, 이는 다른 방법으로 타이밍을 계산하는 대신 도구를 호출하는 쪽으로 조정합니다.parameters- 도구의 인수를 설명하는 JSON 스키마입니다. 이 도구는 아무 것도 사용하지 않으므로 스키마는 속성이 없는 개체입니다.handler- 도구를 호출할 때 Copilot 실행되는 비동기 함수입니다. 반환되는 문자열은 모델이 읽는 도구의 결과가 됩니다.
도구가 모델에 제공되는 방식을 셰이프하는 두 개의 추가 필드:
-
defer: "never"- 항상 모델의 도구 목록에 도구 설명을 유지합니다. 기본적으로 많은 도구를 사용할 수 있는 경우 CLI는 거의 사용되지 않는 도구를 연기하고 모델이 요청 시 해당 도구를 검색하도록 할 수 있습니다.defer을"never"로 설정하면 이 도구가 해당 동작의 적용 대상에서 제외되므로, Copilot에서 항상 이를 볼 수 있습니다.중요
defer: "never"은(는) 단순히 도구를 Copilot에 제공할 뿐입니다. 그것은 그것을 호출하도록 _강요_Copilot 하지 않습니다. 프롬프트에 대한 적절한 응답을 생성하는 다른 방법이 있는 경우 확장에서 특정 도구를 항상 사용하도록 위임할 수 있는 방법은 없습니다. 모델은 항상 사용할 도구를 스스로 결정합니다. 이 예제에서는 모델이 도구에서 제공하는 정보를 알 수 있는 다른 방법이 없기 때문에 새 도구가 안정적으로 사용됩니다. -
skipPermission: true를 사용하면 각 호출을 승인하도록 요청하지 않고 도구를 실행할 수 있습니다. 도구는 확장이 이미 수집한 합계만 읽기 때문에 여기에 적합합니다. 파일을 터치하거나 명령을 실행하지 않습니다.
타이밍 정보는 세션을 모니터링하여 수집됩니다. 확장은 CLI가 모든 도구 호출을 중심으로 내보내는 두 개의 이벤트를 구독합니다.
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에서 Copilot와 상호 작용할 때 사용한 토큰 수를 확인하는 데 사용할 수 있는 /tokencount 슬래시 명령어를 추가합니다. 토큰 사용량 측정을 시작하려는 경우 실행 /tokencount start 한 다음 나중에 실행 /tokencount 하여 개수를 시작한 후 사용한 토큰 수를 확인할 수 있습니다.
확장은 일회성 셸 명령이 수행할 수 없는 CLI에서 내보낸 이벤트를 구독하여 세션에서 사용되는 총 토큰을 유지합니다.
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 그룹 아래에 나열되어야 합니다.
Esc 키를 눌러 관리자를 닫습니다.
확장을 포함하여 세션에 로드된 모든 항목에 대한 요약을 보려면 실행할 /env 수도 있습니다.
4단계: 사용해 보기
도구와 달리 슬래시 명령을 직접 호출합니다. 카운터를 시작하세요:
/tokencount start
/tokencount start
Copilot에 프롬프트를 한두 개 보내 토큰을 조금 사용하게 하세요. 예를 들어 파일을 설명해 달라고 요청하세요. 그런 다음 사용한 토큰 수를 확인합니다.
/tokencount
/tokencount
다시 실행 /tokencount start 하면 0부터 카운트가 다시 시작됩니다.
예제의 작동 방식
joinSession에 대한 호출은 확장이 CLI에 추가하는 모든 것을 등록합니다. 이 경우에는 단일 슬래시 명령 하나입니다.
슬래시 명령은 다음 세 개의 필드로 정의됩니다.
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/씁니다.
다음 단계
- 이러한 예제 중 하나를 사용자 고유의 요구에 맞게 조정합니다. CLI에서 예제 확장 중 하나의 동작을 수정하도록 요청 Copilot 합니다.
- 사용자 수준 확장을 공유합니다. 예를 들어 확장을 리포지토리의
tool-time디렉터리로 이동하여.github/extensions/해당 리포지토리에서 작동하는 모든 사용자와 공유합니다. - Copilot에게 처음부터 새 확장 프로그램을 만들어 달라고 하세요. 코파일럿 CLI 확장은 코필로트 SDK에 의해 구동되므로 SDK로 가능한 모든 작업을 수행할 수 있으며, 버튼과 양식이 포함된 대화형 뷰는 물론 새로운 도구와 명령도 추가할 수 있습니다. 코필로트 SDK을(를) 참조하세요.